diff --git a/.cursor/rules/rpl_guide.mdc b/.cursor/rules/rpl_guide.mdc index 733584ad0b..dea6beba47 100644 --- a/.cursor/rules/rpl_guide.mdc +++ b/.cursor/rules/rpl_guide.mdc @@ -151,7 +151,7 @@ RPL provides functions to create new producers by transforming existing ones: You can combine multiple producers into one: * `rpl::combine`: Combines the latest values from multiple producers whenever *any* of them emits a new value. Requires all producers to have emitted at least one value initially. - While it produces a `std::tuple`, subsequent operators like `map`, `filter`, and `start_with_next` can automatically unpack this tuple into separate lambda arguments. + While it produces a `std::tuple`, subsequent operators like `map`, `filter`, and `on_next` can automatically unpack this tuple into separate lambda arguments. ```cpp auto countProducer = rpl::single(1); // Type: rpl::producer auto textProducer = rpl::single(u"hello"_q); // Type: rpl::producer diff --git a/LEGAL b/LEGAL index 09f48ae723..09c9e383db 100644 --- a/LEGAL +++ b/LEGAL @@ -1,7 +1,7 @@ This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. -Copyright (c) 2014-2025 The Telegram Desktop Authors. +Copyright (c) 2014-2026 The Telegram Desktop Authors. Telegram Desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index f2f3028180..4b2afe9f2a 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1095,6 +1095,8 @@ PRIVATE history/view/history_view_sticker_toast.h history/view/history_view_subsection_tabs.cpp history/view/history_view_subsection_tabs.h + history/view/history_view_summary_header.cpp + history/view/history_view_summary_header.h history/view/history_view_text_helper.cpp history/view/history_view_text_helper.h history/view/history_view_transcribe_button.cpp @@ -1632,8 +1634,6 @@ PRIVATE platform/platform_tray.h platform/platform_webauthn.h platform/platform_window_title.h - profile/profile_back_button.cpp - profile/profile_back_button.h profile/profile_block_widget.cpp profile/profile_block_widget.h profile/profile_cover_drop_area.cpp @@ -1789,6 +1789,8 @@ PRIVATE tde2e/tde2e_integration.h ui/boxes/edit_invite_link_session.cpp ui/boxes/edit_invite_link_session.h + ui/boxes/emoji_stake_box.cpp + ui/boxes/emoji_stake_box.h ui/boxes/peer_qr_box.cpp ui/boxes/peer_qr_box.h ui/chat/attach/attach_item_single_file_preview.cpp diff --git a/Telegram/Resources/animations/cocoon.tgs b/Telegram/Resources/animations/cocoon.tgs new file mode 100644 index 0000000000..9eba8213ad Binary files /dev/null and b/Telegram/Resources/animations/cocoon.tgs differ diff --git a/Telegram/Resources/animations/dice/dice_6.tgs b/Telegram/Resources/animations/dice/dice_6.tgs new file mode 100644 index 0000000000..f151a226e7 Binary files /dev/null and b/Telegram/Resources/animations/dice/dice_6.tgs differ diff --git a/Telegram/Resources/art/cocoon.webp b/Telegram/Resources/art/cocoon.webp new file mode 100644 index 0000000000..060239730b Binary files /dev/null and b/Telegram/Resources/art/cocoon.webp differ diff --git a/Telegram/Resources/art/dice/dice1.svg b/Telegram/Resources/art/dice/dice1.svg new file mode 100644 index 0000000000..4ab6abbe61 --- /dev/null +++ b/Telegram/Resources/art/dice/dice1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/dice/dice2.svg b/Telegram/Resources/art/dice/dice2.svg new file mode 100644 index 0000000000..1e9e0c8534 --- /dev/null +++ b/Telegram/Resources/art/dice/dice2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/dice/dice3.svg b/Telegram/Resources/art/dice/dice3.svg new file mode 100644 index 0000000000..84ff3fcc13 --- /dev/null +++ b/Telegram/Resources/art/dice/dice3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/dice/dice4.svg b/Telegram/Resources/art/dice/dice4.svg new file mode 100644 index 0000000000..dc4af3c88f --- /dev/null +++ b/Telegram/Resources/art/dice/dice4.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/dice/dice5.svg b/Telegram/Resources/art/dice/dice5.svg new file mode 100644 index 0000000000..cdd1efd1db --- /dev/null +++ b/Telegram/Resources/art/dice/dice5.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/dice/dice6.svg b/Telegram/Resources/art/dice/dice6.svg new file mode 100644 index 0000000000..f024e88b05 --- /dev/null +++ b/Telegram/Resources/art/dice/dice6.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/day-custom-base.tdesktop-theme b/Telegram/Resources/day-custom-base.tdesktop-theme index d10f68c756..42d69a8d4f 100644 Binary files a/Telegram/Resources/day-custom-base.tdesktop-theme and b/Telegram/Resources/day-custom-base.tdesktop-theme differ diff --git a/Telegram/Resources/icons/chat/mini_roll.svg b/Telegram/Resources/icons/chat/mini_roll.svg new file mode 100644 index 0000000000..158f3d10b8 --- /dev/null +++ b/Telegram/Resources/icons/chat/mini_roll.svg @@ -0,0 +1,7 @@ + + + Mini / mini_roll + + + + \ No newline at end of file diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c2ae423cb3..f616f3eb40 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -709,6 +709,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_shortcuts_show_chat_preview" = "Show chat preview"; "lng_shortcuts_record_voice_message" = "Record Voice Message"; "lng_shortcuts_record_round_message" = "Record Round Message"; +"lng_shortcuts_admin_log" = "Group/Channel Recent Actions"; "lng_settings_chat_reactions_title" = "Quick Reaction"; "lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction"; @@ -2466,6 +2467,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_not_enough_funds" = "Transaction failed."; "lng_you_paid_stars#one" = "You paid {count} Star."; "lng_you_paid_stars#other" = "You paid {count} Stars."; +"lng_action_stake_game_nothing" = "{from} didn't win anything"; +"lng_action_stake_game_nothing_you" = "You didn't win anything"; +"lng_action_stake_game_won" = "{from} won {amount}"; +"lng_action_stake_game_won_you" = "You won {amount}"; +"lng_action_stake_game_lost" = "{from} lost {amount}"; +"lng_action_stake_game_lost_you" = "You lost {amount}"; + +"lng_stake_game_title" = "Emoji Stake"; +"lng_stake_game_beta" = "Beta"; +"lng_stake_game_about" = "A limited play-test of the upcoming emoji mini-game platform for a small group of users."; +"lng_stake_game_results" = "Results and Returns"; +"lng_stake_game_resets" = "A streak resets after 3 {emoji} or a stake change."; +"lng_stake_game_your" = "Your stake"; +"lng_stake_game_save_and_roll" = "Save and Roll"; "lng_you_joined_group" = "You joined this group"; @@ -2663,7 +2678,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_forwarded_story" = "Story from {user}"; "lng_forwarded_story_expired" = "This story has expired."; "lng_forwarded_date" = "Original: {date}"; -"lng_forwarded_saved_date" = "Saved date: {date}"; +"lng_forwarded_forwarded_date" = "Forwarded date: {date}"; "lng_forwarded_channel" = "Forwarded from {channel}"; "lng_forwarded_psa_default" = "Forwarded from {channel}"; "lng_forwarded_via" = "Forwarded from {user} via {inline_bot}"; @@ -3900,6 +3915,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_sell_small#other" = "sell for {count} Stars"; "lng_gift_upgrade_title" = "Upgrade Gift"; "lng_gift_upgrade_about" = "Turn your gift into a unique collectible\nthat you can transfer or auction."; +"lng_gift_upgrade_view_all" = "{emoji} View all variants {arrow}"; "lng_gift_upgrade_preview_title" = "Make Unique"; "lng_gift_upgrade_preview_about" = "Let {name} turn your gift into a unique collectible."; "lng_gift_upgrade_preview_about_channel" = "Let the admins of {name} turn your gift into a unique collectible."; @@ -4330,6 +4346,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_restrict_users_part_single_header" = "What can this user do?"; "lng_restrict_users_part_header#one" = "What can {count} selected user do?"; "lng_restrict_users_part_header#other" = "What can {count} selected users do?"; +"lng_restrict_users_kick_from_common_group" = "Also ban from:"; "lng_report_spam" = "Report Spam"; "lng_report_spam_and_leave" = "Report spam and leave"; "lng_report_spam_done" = "Thank you for your report"; @@ -4562,6 +4579,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_about_random" = "Send a {emoji} emoji to any chat to try your luck."; "lng_about_random_send" = "Send"; +"lng_about_random_stake" = "Stake: {amount}"; +"lng_about_random_stake_change" = "change"; "lng_open_this_link" = "Open this link?"; "lng_open_link" = "Open"; @@ -4954,6 +4973,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_translate_settings" = "Settings"; "lng_translate_undo" = "Undo"; +"lng_translate_cocoon_menu" = "Translations are powered by\n**{emoji} Cocoon**. {link}"; +"lng_translate_cocoon_link" = "How does it work?"; +"lng_translate_cocoon_title" = "Cocoon"; +"lng_translate_cocoon_subtitle" = "**Cocoon** ({text}) handles AI tasks **safely** and **efficiently**."; +"lng_translate_cocoon_explain" = "**Co**nfidential **Co**mpute **O**pen **N**etwork"; +"lng_translate_cocoon_private_title" = "Private"; +"lng_translate_cocoon_private_text" = "No third party can access any data, such as translations, inside {mention}."; +"lng_translate_cocoon_private_mention" = "@cocoon"; +"lng_translate_cocoon_efficient_title" = "Efficient"; +"lng_translate_cocoon_efficient_text" = "Cocoon has allowed Telegram to reduce translation costs by 6x."; +"lng_translate_cocoon_everyone_title" = "For Everyone"; +"lng_translate_cocoon_everyone_text" = "Any developer can use Cocoon for AI features. Learn more at {domain}."; +"lng_translate_cocoon_everyone_domain" = "cocoon.org"; +"lng_translate_cocoon_text" = "Want to integrate Cocoon into your projects?\nReach out at {link}"; +"lng_translate_cocoon_text_link" = "t.me/cocoon?direct"; +"lng_translate_cocoon_done" = "Understood"; + "lng_downloads_section" = "Downloads"; "lng_downloads_view_in_chat" = "View in chat"; "lng_downloads_view_in_section" = "View in downloads"; @@ -6027,7 +6063,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_title_all" = "All actions"; "lng_admin_log_title_selected" = "Selected actions"; -"lng_admin_log_filter" = "Filter"; "lng_admin_log_filter_title" = "Filter"; "lng_admin_log_filter_all_actions" = "All actions"; "lng_admin_log_filter_actions_type_subtitle" = "Filter actions by type"; @@ -6055,7 +6090,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_filter_all_admins" = "All users and admins"; "lng_admin_log_filter_actions_admins_subtitle" = "Filter actions by admins"; "lng_admin_log_filter_actions_admins_section" = "Show Actions by All Admins"; -"lng_admin_log_about" = "What is this?"; "lng_admin_log_about_text" = "This is a list of all notable actions by group members and admins in the last 48 hours."; "lng_admin_log_about_text_channel" = "This is a list of all service actions taken by the channel's admins in the last 48 hours."; "lng_admin_log_no_results_title" = "No actions found"; @@ -6125,6 +6159,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_stopped_poll" = "{from} stopped poll:"; "lng_admin_log_invited" = "invited {user}"; "lng_admin_log_banned" = "banned {user}"; +"lng_admin_log_banned_until" = "banned {user} {until}"; "lng_admin_log_unbanned" = "unbanned {user}"; "lng_admin_log_restricted" = "changed restrictions for {user} {until}"; "lng_admin_log_promoted" = "changed privileges for {user}"; @@ -7375,6 +7410,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_bank_card_copy" = "Copy Card Number"; "lng_context_bank_card_copied" = "Card number copied to clipboard."; +"lng_summarize_header_title" = "AI summary"; +"lng_summarize_header_about" = "Click to see original text"; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index 87ba31c8c9..a33a9ff016 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -52,6 +52,7 @@ ../../animations/show_or_premium_readtime.tgs ../../animations/passkeys.tgs ../../animations/ban.tgs + ../../animations/cocoon.tgs ../../animations/profile/profile_muting.tgs ../../animations/profile/profile_unmuting.tgs @@ -66,6 +67,7 @@ ../../animations/dice/slot_back.tgs ../../animations/dice/slot_pull.tgs ../../animations/dice/winners.tgs + ../../animations/dice/dice_6.tgs ../../animations/star_reaction/appear.tgs ../../animations/star_reaction/center.tgs diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index 8fcea19be9..0c9e0a701f 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -9,6 +9,7 @@ ../../art/logo_256_no_margin.png ../../art/themeimage.jpg ../../art/round_placeholder.jpg + ../../art/cocoon.webp ../../day-blue.tdesktop-theme ../../night.tdesktop-theme ../../night-green.tdesktop-theme @@ -36,6 +37,12 @@ ../../art/topic_icons/general.svg ../../icons/info/edit/links_subscription.svg ../../icons/plane_white.svg + ../../art/dice/dice1.svg + ../../art/dice/dice2.svg + ../../art/dice/dice3.svg + ../../art/dice/dice4.svg + ../../art/dice/dice5.svg + ../../art/dice/dice6.svg ../../icons/calls/hands.lottie diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 93dd063771..82833358d5 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="6.4.2.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index acf4d03dc9..8202421b82 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 6,3,10,0 - PRODUCTVERSION 6,3,10,0 + FILEVERSION 6,4,2,0 + PRODUCTVERSION 6,4,2,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop" - VALUE "FileVersion", "6.3.10.0" - VALUE "LegalCopyright", "Copyright (C) 2014-2025" + VALUE "FileVersion", "6.4.2.0" + VALUE "LegalCopyright", "Copyright (C) 2014-2026" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "6.3.10.0" + VALUE "ProductVersion", "6.4.2.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 85bb7378b3..b67f32c362 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 6,3,10,0 - PRODUCTVERSION 6,3,10,0 + FILEVERSION 6,4,2,0 + PRODUCTVERSION 6,4,2,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop Updater" - VALUE "FileVersion", "6.3.10.0" - VALUE "LegalCopyright", "Copyright (C) 2014-2025" + VALUE "FileVersion", "6.4.2.0" + VALUE "LegalCopyright", "Copyright (C) 2014-2026" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "6.3.10.0" + VALUE "ProductVersion", "6.4.2.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index 44233354be..3c5d35d2d8 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -28,11 +28,14 @@ struct SendOptions { TimeId scheduleRepeatPeriod = 0; BusinessShortcutId shortcutId = 0; EffectId effectId = 0; + QByteArray stakeSeedHash; + int64 stakeNanoTon = 0; int starsApproved = 0; bool silent = false; bool handleSupportSwitch = false; bool invertCaption = false; bool hideViaBot = false; + bool mediaSpoiler = false; crl::time ttlSeconds = 0; SuggestOptions suggest; diff --git a/Telegram/SourceFiles/api/api_report.cpp b/Telegram/SourceFiles/api/api_report.cpp index c9d8e43fac..05b6d93591 100644 --- a/Telegram/SourceFiles/api/api_report.cpp +++ b/Telegram/SourceFiles/api/api_report.cpp @@ -8,7 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_report.h" #include "apiwrap.h" +#include "data/data_session.h" #include "data/data_peer.h" +#include "data/data_channel.h" #include "data/data_photo.h" #include "data/data_report.h" #include "data/data_user.h" @@ -142,4 +144,29 @@ auto CreateReportMessagesOrStoriesCallback( }; } +void ReportSpam( + not_null sender, + const MessageIdsList &ids) { + if (ids.empty()) { + return; + } + const auto peer = sender->owner().peer(ids.front().peer); + const auto channel = peer->asChannel(); + if (!channel) { + return; + } + + auto msgIds = QVector(); + msgIds.reserve(ids.size()); + for (const auto &fullId : ids) { + msgIds.push_back(MTP_int(fullId.msg)); + } + + sender->session().api().request(MTPchannels_ReportSpam( + channel->inputChannel(), + sender->input(), + MTP_vector(msgIds) + )).send(); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_report.h b/Telegram/SourceFiles/api/api_report.h index 02d1c9befb..e503102ec5 100644 --- a/Telegram/SourceFiles/api/api_report.h +++ b/Telegram/SourceFiles/api/api_report.h @@ -53,4 +53,8 @@ void SendPhotoReport( not_null peer) -> Fn)>; +void ReportSpam( + not_null sender, + const MessageIdsList &ids); + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 6ec0688584..749beab362 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -252,6 +252,7 @@ void SendExistingMedia( .postAuthor = NewMessagePostAuthor(action), .effectId = action.options.effectId, .suggest = HistoryMessageSuggestInfo(action.options), + .mediaSpoiler = action.options.mediaSpoiler, }, media, caption); const auto performRequest = [=](const auto &repeatRequest) -> void { @@ -307,7 +308,9 @@ void SendExistingDocument( std::optional localMessageId) { const auto inputMedia = [=] { return MTP_inputMediaDocument( - MTP_flags(0), + MTP_flags(message.action.options.mediaSpoiler + ? MTPDinputMediaDocument::Flag::f_spoiler + : MTPDinputMediaDocument::Flags(0)), document->mtpInput(), MTPInputPhoto(), // video_cover MTPint(), // ttl_seconds @@ -377,7 +380,6 @@ bool SendDice(MessageToSend &message) { message.action.clearDraft = false; message.action.generateLocal = true; - auto &action = message.action; api->sendAction(action); @@ -433,6 +435,11 @@ bool SendDice(MessageToSend &message) { session->data().registerMessageRandomId(randomId, newId); + auto seed = QByteArray(32, Qt::Uninitialized); + base::RandomFill(bytes::make_detached_span(seed)); + const auto stake = action.options.stakeSeedHash.isEmpty() + ? 0 + : action.options.stakeNanoTon; history->addNewLocalMessage({ .id = newId.msg, .flags = flags, @@ -445,8 +452,15 @@ bool SendDice(MessageToSend &message) { .effectId = action.options.effectId, .suggest = HistoryMessageSuggestInfo(action.options), }, TextWithEntities(), MTP_messageMediaDice( + MTP_flags(stake + ? MTPDmessageMediaDice::Flag::f_game_outcome + : MTPDmessageMediaDice::Flag()), MTP_int(0), - MTP_string(emoji))); + MTP_string(emoji), + MTP_messages_emojiGameOutcome( + MTP_bytes(seed), + MTP_long(stake), + MTP_long(0)))); histories.sendPreparedMessage( history, action.replyTo, @@ -455,7 +469,12 @@ bool SendDice(MessageToSend &message) { MTP_flags(sendFlags), peer->input(), Data::Histories::ReplyToPlaceholder(), - MTP_inputMediaDice(MTP_string(emoji)), + (stake + ? MTP_inputMediaStakeDice( + MTP_bytes(action.options.stakeSeedHash), + MTP_long(stake), + MTP_bytes(seed)) + : MTP_inputMediaDice(MTP_string(emoji))), MTP_string(), MTP_long(randomId), MTPReplyMarkup(), diff --git a/Telegram/SourceFiles/api/api_suggest_post.cpp b/Telegram/SourceFiles/api/api_suggest_post.cpp index e3490be581..7c5a31b314 100644 --- a/Telegram/SourceFiles/api/api_suggest_post.cpp +++ b/Telegram/SourceFiles/api/api_suggest_post.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/choose_date_time.h" #include "ui/layers/generic_box.h" #include "ui/boxes/confirm_box.h" +#include "ui/boxes/emoji_stake_box.h" // InsufficientTonBox #include "ui/text/text_utilities.h" #include "ui/widgets/fields/input_field.h" #include "ui/widgets/popup_menu.h" @@ -101,9 +102,9 @@ void ConfirmApproval( credits->tonLoad(); return; } else if (price > credits->tonBalance()) { - const auto peer = item->history()->peer; + const auto session = &item->history()->session(); show->show( - Box(HistoryView::InsufficientTonBox, peer, price)); + Box(Ui::InsufficientTonBox, session, price)); return; } } else { diff --git a/Telegram/SourceFiles/api/api_transcribes.cpp b/Telegram/SourceFiles/api/api_transcribes.cpp index a5c6ab96fb..fae17a7566 100644 --- a/Telegram/SourceFiles/api/api_transcribes.cpp +++ b/Telegram/SourceFiles/api/api_transcribes.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_transcribes.h" #include "apiwrap.h" +#include "api/api_text_entities.h" #include "data/data_channel.h" #include "data/data_document.h" #include "data/data_peer.h" @@ -15,9 +16,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/history_item_helpers.h" +#include "lang/lang_keys.h" #include "main/main_app_config.h" #include "main/main_session.h" #include "main/main_session_settings.h" +#include "spellcheck/spellcheck_types.h" namespace Api { @@ -103,7 +106,6 @@ void Transcribes::toggle(not_null item) { auto i = _map.find(id); if (i == _map.end()) { load(item); - //_session->data().requestItemRepaint(item); _session->data().requestItemResize(item); } else if (!i->second.requestId) { i->second.shown = !i->second.shown; @@ -114,6 +116,30 @@ void Transcribes::toggle(not_null item) { } } +void Transcribes::toggleSummary( + not_null item, + Fn onPremiumRequired) { + const auto id = item->fullId(); + auto i = _summaries.find(id); + if (i == _summaries.end()) { + auto &entry = _summaries.emplace(id).first->second; + entry.onPremiumRequired = std::move(onPremiumRequired); + summarize(item); + } else if (!i->second.loading) { + auto &entry = i->second; + if (entry.result.empty()) { + entry.onPremiumRequired = std::move(onPremiumRequired); + summarize(item); + } else { + entry.shown = entry.premiumRequired ? false : !entry.shown; + _session->data().requestItemResize(item); + if (entry.shown) { + _session->data().requestItemShowHighlight(item); + } + } + } +} + const Transcribes::Entry &Transcribes::entry( not_null item) const { static auto empty = Entry(); @@ -121,6 +147,13 @@ const Transcribes::Entry &Transcribes::entry( return (i != _map.end()) ? i->second : empty; } +const SummaryEntry &Transcribes::summary( + not_null item) const { + static const auto empty = SummaryEntry(); + const auto i = _summaries.find(item->fullId()); + return (i != _summaries.end()) ? i->second : empty; +} + void Transcribes::apply(const MTPDupdateTranscribedAudio &update) { const auto id = update.vtranscription_id().v; const auto i = _ids.find(id); @@ -208,4 +241,78 @@ void Transcribes::load(not_null item) { entry.pending = false; } +void Transcribes::summarize(not_null item) { + if (!item->isHistoryEntry() || item->isLocal()) { + return; + } + + const auto id = item->fullId(); + const auto translatedTo = item->history()->translatedTo(); + const auto langCode = translatedTo + ? translatedTo.twoLetterCode() + : QString(); + const auto requestId = _api.request(MTPmessages_SummarizeText( + langCode.isEmpty() + ? MTP_flags(0) + : MTP_flags(MTPmessages_summarizeText::Flag::f_to_lang), + item->history()->peer->input(), + MTP_int(item->id), + langCode.isEmpty() ? MTPstring() : MTP_string(langCode) + )).done([=](const MTPTextWithEntities &result) { + const auto &data = result.data(); + auto &entry = _summaries[id]; + entry.requestId = 0; + entry.loading = false; + entry.premiumRequired = false; + entry.onPremiumRequired = nullptr; + entry.languageId = translatedTo; + entry.result = TextWithEntities( + qs(data.vtext()), + Api::EntitiesFromMTP(_session, data.ventities().v)); + if (const auto item = _session->data().message(id)) { + _session->data().requestItemTextRefresh(item); + _session->data().requestItemShowHighlight(item); + } + }).fail([=](const MTP::Error &error) { + auto &entry = _summaries[id]; + if (error.type() == u"SUMMARY_FLOOD_PREMIUM"_q) { + if (!entry.premiumRequired && entry.onPremiumRequired) { + entry.onPremiumRequired(); + } + entry.premiumRequired = true; + } + entry.requestId = 0; + entry.shown = false; + entry.loading = false; + entry.onPremiumRequired = nullptr; + if (const auto item = _session->data().message(id)) { + _session->data().requestItemTextRefresh(item); + } + }).send(); + + auto &entry = _summaries.emplace(id).first->second; + entry.requestId = requestId; + entry.shown = true; + entry.loading = true; + + item->setHasSummaryEntry(); + _session->data().requestItemResize(item); +} + +void Transcribes::checkSummaryToTranslate(FullMsgId id) { + const auto i = _summaries.find(id); + if (i == _summaries.end() || i->second.result.empty()) { + return; + } + const auto item = _session->data().message(id); + if (!item) { + return; + } + const auto translatedTo = item->history()->translatedTo(); + if (i->second.languageId != translatedTo) { + i->second.result = tr::lng_contacts_loading(tr::now, tr::italic); + summarize(item); + } +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_transcribes.h b/Telegram/SourceFiles/api/api_transcribes.h index 93b42401df..eb248e7cc5 100644 --- a/Telegram/SourceFiles/api/api_transcribes.h +++ b/Telegram/SourceFiles/api/api_transcribes.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "mtproto/sender.h" +#include "spellcheck/spellcheck_types.h" class ApiWrap; @@ -17,6 +18,16 @@ class Session; namespace Api { +struct SummaryEntry { + Fn onPremiumRequired = nullptr; + TextWithEntities result; + LanguageId languageId; + bool shown = false; + bool loading = false; + bool premiumRequired = false; + mtpRequestId requestId = 0; +}; + class Transcribes final { public: explicit Transcribes(not_null api); @@ -34,6 +45,13 @@ public: void toggle(not_null item); [[nodiscard]] const Entry &entry(not_null item) const; + void toggleSummary( + not_null item, + Fn onPremiumRequired); + [[nodiscard]] const SummaryEntry &summary( + not_null item) const; + void checkSummaryToTranslate(FullMsgId id); + void apply(const MTPDupdateTranscribedAudio &update); [[nodiscard]] bool freeFor(not_null item) const; @@ -47,6 +65,7 @@ public: private: void load(not_null item); + void summarize(not_null item); const not_null _session; MTP::Sender _api; @@ -58,6 +77,8 @@ private: base::flat_map _map; base::flat_map _ids; + base::flat_map _summaries; + }; } // namespace Api diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index d1fc2c54ec..08ae9ceb57 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/mtp_instance.h" #include "mtproto/mtproto_config.h" #include "mtproto/mtproto_dc_options.h" +#include "chat_helpers/stickers_dice_pack.h" #include "data/business/data_shortcut_messages.h" #include "data/components/credits.h" #include "data/components/gift_auctions.h" @@ -1153,7 +1154,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { MTPint(), // report_delivery_until_date MTPlong(), // paid_message_stars MTPSuggestedPost(), - MTPint()), // schedule_repeat_period + MTPint(), // schedule_repeat_period + MTPstring()), // summary_from_language MessageFlags(), NewMessageType::Unread); } break; @@ -1194,7 +1196,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { MTPint(), // report_delivery_until_date MTPlong(), // paid_message_stars MTPSuggestedPost(), - MTPint()), // schedule_repeat_period + MTPint(), // schedule_repeat_period + MTPstring()), // summary_from_language MessageFlags(), NewMessageType::Unread); } break; @@ -2687,6 +2690,11 @@ void Updates::feedUpdate(const MTPUpdate &update) { const auto &data = update.c_updateStarGiftAuctionUserState(); _session->giftAuctions().apply(data); } break; + + case mtpc_updateEmojiGameInfo: { + const auto &data = update.c_updateEmojiGameInfo(); + _session->diceStickersPacks().apply(data); + } break; } } diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 2660e4a5f9..9081f3a806 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -566,6 +566,8 @@ void ApiWrap::sendMessageFail( } } peer->updateFull(); + } else if (show) { + show->showToast(error); } if (const auto item = _session->data().message(itemId)) { Assert(randomId != 0); @@ -1882,12 +1884,14 @@ void ApiWrap::requestNotifySettings(const MTPInputNotifyPeer &peer) { return PeerId(0); }, [](const MTPDinputPeerChannel &data) { return peerFromChannel(data.vchannel_id()); + }, [](const MTPDinputPeerChannelFromMessage &data) { + return peerFromChannel(data.vchannel_id()); }, [](const MTPDinputPeerChat &data) { return peerFromChat(data.vchat_id()); }, [](const MTPDinputPeerUser &data) { return peerFromUser(data.vuser_id()); - }, [](const auto &) -> PeerId { - Unexpected("Type in ApiRequest::requestNotifySettings peer."); + }, [](const MTPDinputPeerUserFromMessage &data) { + return peerFromUser(data.vuser_id()); }); }; const auto key = peer.match([](const MTPDinputNotifyUsers &) { diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index a557fbb9ef..fd14f2043a 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -960,6 +960,7 @@ void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) { _chatStyle.get(), rect(), rect(), + rect(), _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer)); p.translate(0, textsTop()); if (_service) { diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 49012bac39..3658f8c24e 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -655,8 +655,8 @@ proxyCheckingAnimation: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) thickness: 1px; size: size(8px, 8px); } -proxyDropdownDownPosition: point(-2px, 35px); -proxyDropdownUpPosition: point(-2px, 20px); +proxyDropdownDownPosition: point(2px, 35px); +proxyDropdownUpPosition: point(2px, 20px); proxyAboutPadding: margins(22px, 7px, 22px, 14px); proxyAboutSponsorPadding: margins(22px, 7px, 22px, 0px); @@ -686,7 +686,7 @@ themesMenuToggle: IconButton(defaultIconButton) { rippleAreaSize: 36px; ripple: defaultRippleAnimationBgOver; } -themesMenuPosition: point(-2px, 25px); +themesMenuPosition: point(2px, 25px); createPollField: InputField(defaultInputField) { textMargins: margins(0px, 4px, 0px, 4px); @@ -1165,3 +1165,20 @@ fakeUserpicButton: UserpicButton(defaultUserpicButton) { changeIcon: icon {{ "settings/photo", transparent }}; uploadBg: transparent; } + +moderateCommonGroupsCheckbox: RoundImageCheckbox(defaultPeerListCheckbox) { + imageRadius: 12px; + imageSmallRadius: 11px; + selectWidth: 2px; + check: RoundCheckbox(defaultPeerListCheck) { + size: 16px; + sizeSmall: 0.3; + bgInactive: overviewCheckBg; + bgActive: overviewCheckBgActive; + check: icon {{ + "default_checkbox_check", + overviewCheckFgActive, + point(1px, 4px) + }}; + } +} diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp index b6925504a7..af83512057 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp @@ -99,8 +99,7 @@ protected: p.drawImage( width() - size.width() - - st::menuWithIcons.itemPadding.right() - - st::popupMenuWithIcons.shadow.extend.right(), + - st::menuWithIcons.itemPadding.right(), (height() - size.height()) / 2, _icon); } @@ -286,7 +285,7 @@ void FillChooseFilterMenu( const auto contains = filter.contains(history); const auto title = filter.title(); auto item = base::make_unique_q( - menu.get(), + menu->menu(), menu->st().menu, Ui::Menu::CreateAction( menu.get(), diff --git a/Telegram/SourceFiles/boxes/delete_messages_box.cpp b/Telegram/SourceFiles/boxes/delete_messages_box.cpp index 15880765c9..29e94379d0 100644 --- a/Telegram/SourceFiles/boxes/delete_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/delete_messages_box.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "api/api_chat_participants.h" #include "api/api_messages_search.h" +#include "api/api_report.h" #include "base/unixtime.h" #include "core/application.h" #include "core/core_settings.h" @@ -640,12 +641,7 @@ void DeleteMessagesBox::deleteAndClear() { ChatRestrictionsInfo()); } if (_reportSpam->checked()) { - _moderateInChannel->session().api().request( - MTPchannels_ReportSpam( - _moderateInChannel->inputChannel(), - _moderateFrom->input(), - MTP_vector(1, MTP_int(_ids[0].msg))) - ).send(); + Api::ReportSpam(_moderateFrom, { _ids[0] }); } if (_deleteAll && _deleteAll->checked()) { _moderateInChannel->session().api().deleteAllFromParticipant( diff --git a/Telegram/SourceFiles/boxes/moderate_messages_box.cpp b/Telegram/SourceFiles/boxes/moderate_messages_box.cpp index 13d6276ceb..a95b0436b9 100644 --- a/Telegram/SourceFiles/boxes/moderate_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/moderate_messages_box.cpp @@ -10,8 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_blocked_peers.h" #include "api/api_chat_participants.h" #include "api/api_messages_search.h" +#include "api/api_report.h" #include "apiwrap.h" #include "base/event_filter.h" +#include "base/options.h" #include "base/timer.h" #include "boxes/delete_messages_box.h" #include "boxes/peers/edit_peer_permissions_box.h" @@ -30,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "lang/lang_keys.h" +#include "lottie/lottie_icon.h" #include "main/main_session.h" #include "ui/boxes/confirm_box.h" #include "ui/controls/userpic_button.h" @@ -38,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/painter.h" #include "ui/rect.h" #include "ui/rect_part.h" +#include "ui/text/text_lottie_custom_emoji.h" #include "ui/text/text_utilities.h" #include "ui/vertical_list.h" #include "ui/widgets/checkbox.h" @@ -49,6 +53,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_layers.h" #include "styles/style_window.h" +#include "boxes/peer_list_box.h" +#include "main/main_session_settings.h" +#include "ui/painter.h" +#include "ui/rect.h" +#include "ui/widgets/menu/menu_multiline_action.h" +#include "ui/widgets/popup_menu.h" +#include "ui/widgets/menu/menu_action.h" +#include "ui/effects/round_checkbox.h" +#include "styles/style_chat.h" +#include "styles/style_info.h" + +base::options::toggle ModerateCommonGroups({ + .id = kModerateCommonGroups, + .name = "Ban users from several groups at once.", +}); + +const char kModerateCommonGroups[] = "moderate-common-groups"; + namespace { struct ModerateOptions final { @@ -132,6 +154,155 @@ ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) { }; } +using CommonGroups = std::vector>; +using CollectCommon = std::shared_ptr>; + +void FillMenuModerateCommonGroups( + not_null menu, + CommonGroups common, + CollectCommon collectCommon) { + const auto resultList + = menu->lifetime().make_state>(); + const auto rememberCheckbox = Ui::CreateChild( + menu, + QString()); + auto multiline = base::make_unique_q( + menu->menu(), + menu->st().menu, + st::historyHasCustomEmoji, + st::historyHasCustomEmojiPosition, + tr::lng_restrict_users_kick_from_common_group(tr::now, tr::rich)); + multiline->setDisabled(true); + multiline->setAttribute(Qt::WA_TransparentForMouseEvents); + menu->addAction(std::move(multiline)); + const auto session = &common.front()->session(); + const auto save = [=] { + auto result = std::vector( + resultList->begin(), + resultList->end()); + if (rememberCheckbox->checked()) { + session->settings().setModerateCommonGroups(result); + session->saveSettingsDelayed(); + } + *collectCommon = std::move(result); + }; + for (const auto &group : common) { + struct State { + std::optional checkbox; + Ui::RpWidget *checkboxWidget = nullptr; + }; + auto item = base::make_unique_q( + menu->menu(), + menu->st().menu, + Ui::Menu::CreateAction( + menu->menu(), + group->name(), + [] {}), + nullptr, + nullptr); + const auto state = item->lifetime().make_state(); + item->AbstractButton::setDisabled(true); + item->setActionTriggered([=, peerId = group->id] { + state->checkbox->setChecked(!state->checkbox->checked()); + if (state->checkbox->checked()) { + resultList->insert(peerId); + } else { + resultList->erase(peerId); + } + save(); + }); + const auto raw = item.get(); + state->checkboxWidget = Ui::CreateChild(raw); + state->checkboxWidget->setAttribute(Qt::WA_TransparentForMouseEvents); + state->checkboxWidget->resize(item->width() * 2, item->height()); + state->checkboxWidget->show(); + state->checkbox.emplace( + st::moderateCommonGroupsCheckbox, + [=] { state->checkboxWidget->update(); }, + PaintUserpicCallback(group, true), + [=](int size) { return (group->isForum() || group->isMonoforum()) + ? int(size * Ui::ForumUserpicRadiusMultiplier()) + : std::optional(); }); + state->checkbox->setChecked( + ranges::contains( + session->settings().moderateCommonGroups(), + group->id) + || (collectCommon + && ranges::contains(*collectCommon, group->id)), + anim::type::instant); + state->checkboxWidget->paintOn([=](QPainter &p) { + auto pp = Painter(state->checkboxWidget); + state->checkbox->paint( + pp, + st::menuWithIcons.itemIconPosition.x(), + st::menuWithIcons.itemIconPosition.y(), + raw->width()); + }); + menu->addAction(std::move(item)); + } + menu->addSeparator(); + { + auto item = base::make_unique_q( + menu->menu(), + menu->st().menu, + Ui::Menu::CreateAction( + menu->menu(), + tr::lng_remember(tr::now), + [] {}), + nullptr, + nullptr); + item->AbstractButton::setDisabled(true); + item->setActionTriggered([=] { + rememberCheckbox->setChecked(!rememberCheckbox->checked()); + }); + rememberCheckbox->setParent(item.get()); + rememberCheckbox->setAttribute(Qt::WA_TransparentForMouseEvents); + rememberCheckbox->move(st::lineWidth * 8, -st::lineWidth * 2); + rememberCheckbox->show(); + menu->addAction(std::move(item)); + } +} + +void ProccessCommonGroups( + const HistoryItemsList &items, + Fn processHas) { + const auto moderateOptions = CalculateModerateOptions(items); + if (moderateOptions.participants.size() != 1 + || !moderateOptions.allCanBan) { + return; + } + const auto participant = moderateOptions.participants.front(); + const auto user = participant->asUser(); + if (!user) { + return; + } + const auto currentGroupId = items.front()->history()->peer->id; + user->session().api().requestBotCommonGroups(user, [=] { + const auto commonGroups = user->session().api().botCommonGroups(user); + if (!commonGroups || commonGroups->empty()) { + return; + } + + auto filtered = CommonGroups(); + for (const auto &group : *commonGroups) { + if (group->id == currentGroupId) { + continue; + } + const auto channel = group->asChannel(); + if (channel && channel->canRestrictParticipant(user)) { + if (channel->isGroupAdmin(user) && !channel->amCreator()) { + continue; + } + filtered.push_back(group); + } + } + + if (!filtered.empty()) { + processHas(filtered); + } + }); +} + } // namespace void CreateModerateMessagesBox( @@ -149,6 +320,7 @@ void CreateModerateMessagesBox( Assert(!participants.empty()); const auto confirms = inner->lifetime().make_state>(); + const auto collectCommon = std::make_shared>(); const auto isSingle = participants.size() == 1; const auto buttonPadding = isSingle @@ -167,29 +339,66 @@ void CreateModerateMessagesBox( const auto historyPeerId = history->peer->id; const auto ids = session->data().itemsToIds(items); + if (ModerateCommonGroups.value() || session->supportMode()) { + ProccessCommonGroups( + items, + crl::guard(box, [=](CommonGroups groups) { + using namespace Ui; + const auto top = box->addTopButton(st::infoTopBarMenu); + auto &lifetime = top->lifetime(); + const auto menu + = lifetime.make_state>(); + + top->setClickedCallback([=] { + top->setForceRippled(true); + *menu = base::make_unique_q( + top, + st::popupMenuExpandedSeparator); + (*menu)->setDestroyedCallback([=, weak = top] { + if (const auto strong = weak.data()) { + strong->setForceRippled(false); + } + }); + FillMenuModerateCommonGroups(*menu, groups, collectCommon); + (*menu)->setForcedOrigin(PanelAnimation::Origin::TopRight); + const auto point = QPoint(top->width(), top->height()); + (*menu)->popup(top->mapToGlobal(point)); + }); + })); + } + using Request = Fn, not_null)>; const auto sequentiallyRequest = [=]( Request request, - Participants participants) { + Participants participants, + std::optional> channelIds = {}) { constexpr auto kSmallDelayMs = 5; const auto participantIds = ranges::views::all( participants ) | ranges::views::transform([](not_null peer) { return peer->id; }) | ranges::to_vector; + const auto channelIdList = channelIds.value_or( + std::vector{ historyPeerId }); const auto lifetime = std::make_shared(); - const auto counter = lifetime->make_state(0); + const auto participantIndex = lifetime->make_state(0); + const auto channelIndex = lifetime->make_state(0); const auto timer = lifetime->make_state(); timer->setCallback(crl::guard(session, [=] { - if ((*counter) < participantIds.size()) { - const auto peer = session->data().peer(historyPeerId); - const auto channel = peer ? peer->asChannel() : nullptr; - const auto from = session->data().peer( - participantIds[*counter]); - if (channel && from) { - request(from, channel); + if ((*participantIndex) < participantIds.size()) { + if ((*channelIndex) < channelIdList.size()) { + const auto from = session->data().peer( + participantIds[*participantIndex]); + const auto channel = session->data().peer( + channelIdList[*channelIndex])->asChannel(); + if (from && channel) { + request(from, channel); + } + (*channelIndex)++; + } else { + (*participantIndex)++; + *channelIndex = 0; } - (*counter)++; } else { lifetime->destroy(); } @@ -273,17 +482,7 @@ void CreateModerateMessagesBox( handleConfirmation(report, controller, [=]( not_null p, not_null c) { - auto filtered = ranges::views::all( - ids - ) | ranges::views::transform([](const FullMsgId &id) { - return MTP_int(id.msg); - }) | ranges::to>(); - c->session().api().request( - MTPchannels_ReportSpam( - c->inputChannel(), - p->input(), - MTP_vector(std::move(filtered))) - ).send(); + Api::ReportSpam(p, ids); }); } @@ -319,43 +518,68 @@ void CreateModerateMessagesBox( const auto &item) { return item->from()->id; }) | ranges::to_vector; - tr::lng_selected_delete_sure( - lt_count, - rpl::combine( - std::move(messagesCounts), - isSingle - ? deleteAll->checkedValue() - : rpl::merge( - controller->toggleRequestsFromInner.events(), - controller->checkAllRequests.events()) - ) | rpl::map([=](const auto &map, bool c) { - const auto checked = (isSingle && !c) - ? Participants() - : controller->collectRequests - ? controller->collectRequests() - : Participants(); - auto result = 0; - for (const auto &[peerId, count] : map) { - for (const auto &peer : checked) { - if (peer->id == peerId) { - result += count; - break; - } + + rpl::combine( + std::move(messagesCounts), + isSingle + ? deleteAll->checkedValue() + : rpl::merge( + controller->toggleRequestsFromInner.events(), + controller->checkAllRequests.events()) + ) | rpl::map([=](const auto &map, bool c) { + const auto checked = (isSingle && !c) + ? Participants() + : controller->collectRequests + ? controller->collectRequests() + : Participants(); + auto result = 0; + for (const auto &[peerId, count] : map) { + for (const auto &peer : checked) { + if (peer->id == peerId) { + result += count; + break; } } - for (const auto &fromId : itemFromIds) { - for (const auto &peer : checked) { - if (peer->id == fromId) { - result--; - break; - } + } + for (const auto &fromId : itemFromIds) { + for (const auto &peer : checked) { + if (peer->id == fromId) { + result--; + break; } - result++; } - return float64(result); - }) - ) | rpl::on_next([=](const QString &text) { - title->setText(text); + result++; + } + return float64(result); + }) | rpl::on_next([=](int amount) { + auto text = tr::lng_selected_delete_sure( + tr::now, + lt_count, + float64(amount)); + if (amount > 0) { + title->setText(std::move(text)); + } else { + const auto zeroIndex = text.indexOf('0'); + if (zeroIndex != -1) { + auto descriptor = Lottie::IconDescriptor{ + .name = u"transcribe_loading"_q, + .color = &st::attentionButtonFg, // Any contrast. + .sizeOverride = Size( + st::historyTranscribeLoadingSize), + .colorizeUsingAlpha = true, + }; + auto result = TextWithEntities() + .append(text.mid(0, zeroIndex)) + .append(Ui::Text::LottieEmoji(descriptor)) + .append(text.mid(zeroIndex + 1)); + using namespace Ui::Text; + title->setMarkedText( + std::move(result), + LottieEmojiContext(std::move(descriptor))); + } else { + title->setText(std::move(text)); + } + } title->resizeToWidth(inner->width() - rect::m::sum::h(st::boxRowPadding)); }, title->lifetime()); @@ -548,7 +772,16 @@ void CreateModerateMessagesBox( } } }; - sequentiallyRequest(request, controller->collectRequests()); + if (collectCommon && !collectCommon->empty()) { + sequentiallyRequest( + request, + controller->collectRequests(), + *collectCommon); + } else { + sequentiallyRequest( + request, + controller->collectRequests()); + } } }, ban->lifetime()); } diff --git a/Telegram/SourceFiles/boxes/moderate_messages_box.h b/Telegram/SourceFiles/boxes/moderate_messages_box.h index 64e544e4b3..92307ce347 100644 --- a/Telegram/SourceFiles/boxes/moderate_messages_box.h +++ b/Telegram/SourceFiles/boxes/moderate_messages_box.h @@ -17,6 +17,8 @@ namespace Ui { class GenericBox; } // namespace Ui +extern const char kModerateCommonGroups[]; + void CreateModerateMessagesBox( not_null box, const HistoryItemsList &items, diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 32c968d630..9ed40d3e02 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -214,6 +214,15 @@ void PeerListBox::prepare() { if (_init) { _init(this); } + + { + setDimensions( + _controller->contentWidth(), + std::clamp( + content()->height(), + st::boxMaxListHeight, + st::boxMaxListHeight * 3)); + } } void PeerListBox::keyPressEvent(QKeyEvent *e) { diff --git a/Telegram/SourceFiles/boxes/peer_lists_box.cpp b/Telegram/SourceFiles/boxes/peer_lists_box.cpp index 8a8a71ec9b..4506075cec 100644 --- a/Telegram/SourceFiles/boxes/peer_lists_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_lists_box.cpp @@ -178,7 +178,14 @@ void PeerListsBox::prepare() { } rows->resizeToWidth(firstController()->contentWidth()); - setDimensions(firstController()->contentWidth(), st::boxMaxListHeight); + { + setDimensions( + firstController()->contentWidth(), + std::clamp( + rows->height(), + st::boxMaxListHeight, + st::boxMaxListHeight * 3)); + } if (_select) { _select->finishAnimating(); Ui::SendPendingMoveResizeEvents(_select); diff --git a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp index 9e81cff414..ddd6e61b22 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp @@ -440,7 +440,8 @@ void Controller::setupNotesField() { _box, _window, _emojiPanel.get(), - st::sendGifWithCaptionEmojiPosition); + st::sendGifWithCaptionEmojiPosition, + false); emojiButton->show(); using Limit = HistoryView::Controls::CharactersLimitLabel; diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp index fec694f122..66de189874 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp @@ -1698,7 +1698,7 @@ base::unique_qptr ParticipantsBoxController::rowContextMenu( st::historyHasCustomEmojiPosition, std::move(text)); if (const auto n = _navigation) { - button->setClickedCallback([=] { + button->setActionTriggered([=] { n->parentController()->show(PrepareShortInfoBox(by, n)); }); } @@ -1902,6 +1902,9 @@ void ParticipantsBoxController::editRestrictedDone( void ParticipantsBoxController::kickParticipant(not_null participant) { const auto user = participant->asUser(); + if (user && user->isInaccessible()) { + return kickParticipantSure(participant); + } const auto text = ((_peer->isChat() || _peer->isMegagroup()) ? tr::lng_profile_sure_kick : tr::lng_profile_sure_kick_channel)( @@ -2128,7 +2131,12 @@ auto ParticipantsBoxController::computeType( : (user && _additional.adminRights(user).has_value()) ? Rights::Admin : Rights::Normal; - result.adminRank = user ? _additional.adminRank(user) : QString(); + result.canRemove = _additional.canRemoveParticipant(participant) + && user + && user->isInaccessible(); + if (!result.canRemove) { + result.adminRank = user ? _additional.adminRank(user) : QString(); + } return result; } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index 5b43008548..c950c1f19f 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -335,8 +335,8 @@ PreviewWrap::PreviewWrap( const auto session = &_history->session(); session->data().viewRepaintRequest( - ) | rpl::on_next([=](not_null view) { - if (view == _element.get()) { + ) | rpl::on_next([=](Data::RequestViewRepaint data) { + if (data.view == _element.get()) { update(); } }, lifetime()); @@ -364,6 +364,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { auto context = _theme->preparePaintContext( _style.get(), rect(), + rect(), clip, !window()->isActiveWindow()); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp index 495b257664..63d4220843 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp @@ -340,6 +340,7 @@ struct ReactionsSelectorArgs { int customAllowed = 0; int customHardLimit = 0; bool all = false; + bool isGroup = false; }; object_ptr AddReactionsSelector( @@ -457,7 +458,7 @@ object_ptr AddReactionsSelector( using SelectorState = ReactionsSelectorState; std::move( args.stateValue - ) | rpl::on_next([=](SelectorState value) { + ) | rpl::on_next([=, all = args.all](SelectorState value) { switch (value) { case SelectorState::Active: state->overlay = nullptr; @@ -466,8 +467,10 @@ object_ptr AddReactionsSelector( raw->setTextWithTags( ComposeEmojiList( reactions, - CollectAvailableReactions( - &args.controller->session()))); + all + ? CollectAvailableReactions( + &args.controller->session()) + : DefaultSelected())); } raw->setDisabled(false); raw->setFocusFast(); @@ -756,9 +759,7 @@ void EditAllowedReactionsBox( const auto all = args.list; auto selected = (allowed.type != AllowedReactionsType::Some) - ? (all - | ranges::views::transform(&Data::Reaction::id) - | ranges::to_vector) + ? std::vector() : allowed.some; if (allowed.paidEnabled) { selected.insert(begin(selected), Data::ReactionId::Paid()); @@ -779,10 +780,12 @@ void EditAllowedReactionsBox( } }; changed( - selected.empty() + !selected.empty() + ? std::move(selected) + : !isGroup ? CollectAvailableReactions( &args.navigation->parentController()->session()) - : std::move(selected), + : DefaultSelected(), {}); Ui::AddSubsectionTitle( reactions, diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp index 44ccdd39ac..13710e41b7 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp @@ -154,6 +154,7 @@ void AddMessage( state->style.get(), widget->rect(), widget->rect(), + widget->rect(), controller->isGifPausedAtLeastFor( Window::GifPauseReason::Layer)); context.outbg = view->hasOutLayout(); diff --git a/Telegram/SourceFiles/boxes/send_gif_with_caption_box.cpp b/Telegram/SourceFiles/boxes/send_gif_with_caption_box.cpp index e7f779c05e..33635b45ba 100644 --- a/Telegram/SourceFiles/boxes/send_gif_with_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/send_gif_with_caption_box.cpp @@ -36,31 +36,39 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "menu/menu_send.h" #include "ui/controls/emoji_button.h" #include "ui/controls/emoji_button_factory.h" +#include "ui/effects/spoiler_mess.h" +#include "ui/image/image_prepare.h" #include "ui/layers/generic_box.h" #include "ui/rect.h" #include "ui/text/text_entity.h" #include "ui/ui_utility.h" #include "ui/vertical_list.h" #include "ui/widgets/fields/input_field.h" +#include "ui/widgets/popup_menu.h" #include "window/window_controller.h" #include "window/window_session_controller.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" #include "styles/style_layers.h" +#include "styles/style_menu_icons.h" namespace Ui { namespace { -[[nodiscard]] not_null AddGifWidget( +struct State final { + std::shared_ptr mediaView; + ::Media::Clip::ReaderPointer gif; + std::unique_ptr spoiler; + QImage firstFrame; + QImage blurredFrame; + bool hasSpoiler = false; + rpl::lifetime loadingLifetime; +}; + +[[nodiscard]] not_null AddGifWidget( not_null container, not_null document, int width) { - struct State final { - std::shared_ptr mediaView; - ::Media::Clip::ReaderPointer gif; - rpl::lifetime loadingLifetime; - }; - const auto state = container->lifetime().make_state(); state->mediaView = document->createMediaView(); state->mediaView->automaticLoad(Data::FileOriginSavedGifs(), nullptr); @@ -75,9 +83,40 @@ namespace { std::numeric_limits::max(), Qt::KeepAspectRatio).height()), st::boxRowPadding); - widget->paintRequest( - ) | rpl::on_next([=] { - auto p = QPainter(widget); + widget->paintOn([=](QPainter &p) { + if (state->hasSpoiler) { + if (state->firstFrame.isNull() + && state->gif + && state->gif->ready()) { + state->firstFrame = state->gif->current( + { .frame = widget->size() }, + crl::now()); + state->blurredFrame = Images::BlurLargeImage( + base::duplicate(state->firstFrame), + 24); + } + if (!state->blurredFrame.isNull()) { + p.drawImage(0, 0, state->blurredFrame); + } else if (const auto thumb = state->mediaView->thumbnail()) { + p.drawImage( + widget->rect(), + thumb->pixNoCache( + widget->size() * style::DevicePixelRatio(), + { + .options = Images::Option::Blur, + .outer = widget->size(), + }).toImage()); + } + if (!state->spoiler) { + state->spoiler = std::make_unique( + [=] { widget->update(); }); + } + const auto now = crl::now(); + const auto index = state->spoiler->index(now, false); + const auto frame = Ui::DefaultImageSpoiler().frame(index); + Ui::FillSpoilerRect(p, widget->rect(), frame); + return; + } if (state->gif && state->gif->started()) { p.drawImage( 0, @@ -99,7 +138,7 @@ namespace { .outer = widget->size(), }).toImage()); } - }, widget->lifetime()); + }); const auto updateThumbnail = [=] { if (document->dimensions.isEmpty()) { @@ -130,7 +169,37 @@ namespace { }, state->loadingLifetime); } - return widget; + base::install_event_filter(widget, [=](not_null e) { + if (e->type() == QEvent::ContextMenu) { + const auto menu = Ui::CreateChild( + widget, + st::popupMenuWithIcons); + menu->addAction( + state->hasSpoiler + ? tr::lng_context_disable_spoiler(tr::now) + : tr::lng_context_spoiler_effect(tr::now), + [=] { + state->hasSpoiler = !state->hasSpoiler; + if (!state->hasSpoiler) { + state->spoiler = nullptr; + state->firstFrame = QImage(); + state->blurredFrame = QImage(); + if (state->gif && state->gif->ready()) { + state->gif->start({ .frame = widget->size() }); + } + } + widget->update(); + }, + state->hasSpoiler + ? &st::menuIconSpoilerOff + : &st::menuIconSpoiler); + menu->popup(QCursor::pos()); + return base::EventFilterResult::Cancel; + } + return base::EventFilterResult::Continue; + }); + + return state; } [[nodiscard]] not_null AddInputField( @@ -354,12 +423,17 @@ void SendGifWithCaptionBox( const SendMenu::Details &details, Fn c) { box->setTitle(tr::lng_send_gif_with_caption()); - [[maybe_unused]] const auto gifWidget = AddGifWidget( + const auto state = AddGifWidget( box->verticalLayout(), document, st::boxWidth); Ui::AddSkip(box->verticalLayout()); - CaptionBox(box, tr::lng_send_button(), {}, peer, details, std::move(c)); + const auto d = [=](Api::SendOptions o, TextWithTags t) { + o.mediaSpoiler = state->hasSpoiler; + document->owner().stickers().notifyGifWithCaptionSent(); + c(std::move(o), std::move(t)); + }; + CaptionBox(box, tr::lng_send_button(), {}, peer, details, std::move(d)); } void EditCaptionBox( diff --git a/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp b/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp index b0f212d94a..29960cb216 100644 --- a/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp @@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_credits_graphics.h" #include "storage/storage_account.h" #include "ui/boxes/confirm_box.h" +#include "ui/boxes/emoji_stake_box.h" // AddStarsInputField #include "ui/controls/button_labels.h" #include "ui/controls/feature_list.h" #include "ui/controls/table_rows.h" @@ -603,7 +604,7 @@ void EditCustomBid( box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); - const auto starsField = HistoryView::AddStarsInputField(container, { + const auto starsField = AddStarsInputField(container, { .value = current, }); @@ -954,30 +955,6 @@ void AuctionBidBox(not_null box, AuctionBidBoxArgs &&args) { helper.context()); } -[[nodiscard]] std::vector RandomIndicesSubset(int total, int subset) { - const auto take = std::min(total, subset); - if (!take) { - return {}; - } - auto result = std::vector(); - auto taken = base::flat_set(); - result.reserve(take); - taken.reserve(take); - for (auto i = 0; i < take; ++i) { - auto index = base::RandomIndex(total - i); - for (const auto already : taken) { - if (index >= already) { - ++index; - } else { - break; - } - } - taken.emplace(index); - result.push_back(index); - } - return result; -} - [[nodiscard]] object_ptr AuctionInfoTable( not_null parent, not_null container, @@ -1381,7 +1358,7 @@ void AuctionInfoBox( }); AddSkip(container, st::defaultVerticalListSkip * 2); - AddUniqueCloseButton( + Settings::AddUniqueCloseMoreButton( box, {}, now.finished() ? nullptr : MakeAuctionFillMenuCallback(show, now)); @@ -1606,7 +1583,7 @@ base::weak_qptr ChooseAndShowAuctionBox( rpl::lifetime ShowStarGiftAuction( not_null controller, PeerData *peer, - QString slug, + uint64 giftId, Fn finishRequesting, Fn boxClosed) { const auto weak = base::make_weak(controller); @@ -1617,7 +1594,7 @@ rpl::lifetime ShowStarGiftAuction( }; const auto state = std::make_shared(); auto result = session->giftAuctions().state( - slug + giftId ) | rpl::on_next([=](Data::GiftAuctionState &&value) { if (const auto onstack = finishRequesting) { onstack(); @@ -1911,7 +1888,7 @@ rpl::producer ActiveAuctionsButton( } struct Single { - QString slug; + uint64 giftId = 0; not_null document; int round = 0; int total = 0; @@ -1925,7 +1902,7 @@ object_ptr MakeActiveAuctionRow( not_null parent, not_null window, not_null document, - const QString &slug, + uint64 giftId, rpl::producer value) { auto result = object_ptr(parent); const auto raw = result.data(); @@ -2022,7 +1999,7 @@ object_ptr MakeActiveAuctionRow( Ui::Text::Colorized(NiceCountdownText(seconds))); })); button->setClickedCallback([=] { - window->showStarGiftAuction(slug); + window->showStarGiftAuction(giftId); }); button->setFullRadius(true); raw->widthValue() | rpl::on_next([=](int width) { @@ -2038,9 +2015,9 @@ Fn ActiveAuctionsCallback( const auto &list = auctions.list; const auto count = int(list.size()); if (count == 1) { - const auto slug = list.front()->gift->auctionSlug; + const auto giftId = list.front()->gift->id; return [=] { - window->showStarGiftAuction(slug); + window->showStarGiftAuction(giftId); }; } struct Auctions { @@ -2049,7 +2026,7 @@ Fn ActiveAuctionsCallback( const auto state = std::make_shared(); const auto singleFrom = [](const Data::GiftAuctionState &state) { return Single{ - .slug = state.gift->auctionSlug, + .giftId = state.gift->id, .document = state.gift->document, .round = state.currentRound, .total = state.totalRounds, @@ -2079,7 +2056,7 @@ Fn ActiveAuctionsCallback( const auto &now = entry.current(); entry = auctions->state( - now.slug + now.giftId ) | rpl::filter([=](const GiftAuctionState &state) { return state.my.bid != 0; }) | rpl::map(singleFrom); @@ -2090,12 +2067,12 @@ Fn ActiveAuctionsCallback( box, window, now.document, - now.slug, + now.giftId, entry.value()), st::boxRowPadding + QMargins(0, skip, 0, skip)); auctions->state( - now.slug + now.giftId ) | rpl::on_next([=](const GiftAuctionState &state) { if (!state.my.bid) { delete row; @@ -2114,4 +2091,28 @@ Fn ActiveAuctionsCallback( }; } +std::vector RandomIndicesSubset(int total, int subset) { + const auto take = std::min(total, subset); + if (!take) { + return {}; + } + auto result = std::vector(); + auto taken = base::flat_set(); + result.reserve(take); + taken.reserve(take); + for (auto i = 0; i < take; ++i) { + auto index = base::RandomIndex(total - i); + for (const auto already : taken) { + if (index >= already) { + ++index; + } else { + break; + } + } + taken.emplace(index); + result.push_back(index); + } + return result; +} + } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/star_gift_auction_box.h b/Telegram/SourceFiles/boxes/star_gift_auction_box.h index 880abafb89..d72bbcd8b8 100644 --- a/Telegram/SourceFiles/boxes/star_gift_auction_box.h +++ b/Telegram/SourceFiles/boxes/star_gift_auction_box.h @@ -34,7 +34,7 @@ class GenericBox; [[nodiscard]] rpl::lifetime ShowStarGiftAuction( not_null controller, PeerData *peer, - QString slug, + uint64 giftId, Fn finishRequesting, Fn boxClosed); @@ -77,4 +77,6 @@ struct ManyAuctionsState { not_null window, const Data::ActiveAuctions &auctions); +[[nodiscard]] std::vector RandomIndicesSubset(int total, int subset); + } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index a4b03d421f..5e1d66f77a 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/premium_preview_box.h" #include "boxes/send_credits_box.h" #include "boxes/star_gift_auction_box.h" +#include "boxes/star_gift_preview_box.h" #include "boxes/star_gift_resale_box.h" #include "boxes/transfer_gift_box.h" #include "chat_helpers/emoji_suggestions_widget.h" @@ -76,8 +77,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_credits.h" #include "settings/settings_credits_graphics.h" #include "settings/settings_premium.h" +#include "ui/boxes/about_cocoon_box.h" // Ui::AddUniqueCloseButton #include "ui/boxes/boost_box.h" #include "ui/boxes/confirm_box.h" +#include "ui/boxes/emoji_stake_box.h" // InsufficientTonBox #include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" #include "ui/controls/button_labels.h" @@ -593,8 +596,8 @@ PreviewWrap::PreviewWrap( using namespace HistoryView; _history->owner().viewRepaintRequest( - ) | rpl::on_next([=](not_null view) { - if (view == _item.get()) { + ) | rpl::on_next([=](Data::RequestViewRepaint data) { + if (data.view == _item.get()) { update(); } }, lifetime()); @@ -795,6 +798,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { auto context = _theme->preparePaintContext( _style.get(), rect(), + rect(), e->rect(), !window()->isActiveWindow()); p.translate(_position); @@ -1848,7 +1852,7 @@ void CheckMaybeGiftLocked( state->resaleLifetime = ShowStarGiftAuction( window, peer, - star->info.auctionSlug, + id, [=] { state->resaleRequestingId = 0; }, crl::guard(raw, [=] { state->resaleLifetime.destroy(); @@ -2699,7 +2703,7 @@ void ShowStarGiftBox( auto was = std::move(entry); entry = Session(); if (const auto strong = weak.get()) { - if (const auto user = peer->asUser()) { + if (const auto user = peer->asUser(); user && !user->isSelf()) { using Type = Api::DisallowedGiftType; const auto disallowedTypes = user->disallowedGiftTypes(); const auto premium = (disallowedTypes & Type::Premium) @@ -2862,6 +2866,7 @@ void AddUniqueGiftCover( base::unique_qptr subtitle; base::unique_qptr subtitleButton; rpl::variable subtitleHeight; + bool outlined = false; QColor bg; QColor fg; }; @@ -3275,15 +3280,19 @@ void AddUniqueGiftCover( tr::marked); }); released->by.value() | rpl::on_next([=](PeerData *by) { - released->st = by + released->outlined = by || args.subtitleOutlined; + released->st = released->outlined ? st::uniqueGiftReleasedBy : st::uniqueGiftSubtitle; released->st.palette.linkFg = released->link.color(); + const auto session = &state->now.gift->model.document->session(); released->subtitle = base::make_unique_q( cover, released->subtitleText.value(), - released->st); + released->st, + st::defaultPopupMenu, + Core::TextContext({ .session = session })); const auto subtitle = released->subtitle.get(); subtitle->show(); @@ -3298,12 +3307,13 @@ void AddUniqueGiftCover( }, subtitle->lifetime()); released->subtitleHeight = subtitle->heightValue(); - if (const auto handler = args.subtitleClick) { + if (!args.subtitleOutlined && args.subtitleClick) { + const auto handler = args.subtitleClick; subtitle->setClickHandlerFilter([=](const auto &...) { handler(); return false; }); - } else if (by) { + } else if (released->outlined) { released->subtitleButton = base::make_unique_q( cover); const auto button = released->subtitleButton.get(); @@ -3312,9 +3322,9 @@ void AddUniqueGiftCover( subtitle->raise(); subtitle->setAttribute(Qt::WA_TransparentForMouseEvents); - button->setClickedCallback([=] { - GiftReleasedByHandler(by); - }); + button->setClickedCallback(args.subtitleClick + ? args.subtitleClick + : [=] { GiftReleasedByHandler(by); }); subtitle->geometryValue( ) | rpl::on_next([=](QRect geometry) { button->setGeometry( @@ -3986,7 +3996,7 @@ void AttachGiftSenderBadge( PostponeCall(badge, updateGeometry); } return base::EventFilterResult::Continue; - }); + }, badge->lifetime()); } base::install_event_filter(widget, [=](not_null e) { @@ -4001,7 +4011,7 @@ void AttachGiftSenderBadge( PostponeCall(badge, [=] { badge->raise(); }); } return base::EventFilterResult::Continue; - }); + }, badge->lifetime()); badge->setVisible(!widget->isHidden()); base::install_event_filter(parent, [=](not_null e) { @@ -4012,7 +4022,7 @@ void AttachGiftSenderBadge( PostponeCall(badge, updateGeometry); } return base::EventFilterResult::Continue; - }); + }, badge->lifetime()); badge->raise(); box->boxClosing() | rpl::on_next([=] { @@ -4158,7 +4168,7 @@ void ShowUniqueGiftWearBox( session, st::creditsBoxButtonLabel, &st::giftBox.button.textFg); - AddUniqueCloseButton(box, {}); + Ui::AddUniqueCloseButton(box); })); } @@ -4618,6 +4628,7 @@ struct UpgradeArgs : StarGiftUpgradeArgs { std::vector backdrops; std::vector prices; std::vector nextPrices; + Data::UniqueGiftAttributes all; }; [[nodiscard]] rpl::producer MakeUpgradeGiftStream( @@ -4718,12 +4729,34 @@ void AddUpgradeGiftCover( }) : MakeUpgradeGiftStream(args); }) | rpl::flatten_latest(); + + auto emoji = tr::marked(); + if (const auto &models = args.all.models; !models.empty()) { + const auto indices = RandomIndicesSubset(models.size(), 3); + for (const auto index : indices) { + auto single = Data::SingleCustomEmoji(models[index].document); + single.entities.front() = EntityInText( + single.entities.front().type(), + single.entities.front().offset(), + single.entities.front().length(), + u"scaled-custom:"_q + single.entities.front().data()); + emoji.append(single).append(' '); + } + } + auto subtitle = state->upgraded.value( ) | rpl::map([=](std::shared_ptr upgraded) { return upgraded ? rpl::single(tr::marked()) : args.savedId - ? tr::lng_gift_upgrade_about(tr::marked) + ? (emoji.empty() + ? tr::lng_gift_upgrade_about(tr::marked) + : tr::lng_gift_upgrade_view_all( + lt_emoji, + rpl::single(emoji), + lt_arrow, + rpl::single(Text::IconEmoji(&st::textMoreIconEmoji)), + tr::link)) : (args.peer->isBroadcast() ? tr::lng_gift_upgrade_preview_about_channel : tr::lng_gift_upgrade_preview_about)( @@ -4731,8 +4764,18 @@ void AddUpgradeGiftCover( rpl::single(tr::marked(args.peer->shortName())), tr::marked); }) | rpl::flatten_latest(); + const auto hasAll = !args.all.models.empty(); + const auto title = args.stargift.resellTitle; + const auto showAll = [=, list = args.all] { + const auto type = Data::GiftAttributeIdType::Model; + const auto null = nullptr; + show->show(Box(StarGiftPreviewBox, title, list, type, null)); + }; AddUniqueGiftCover(container, std::move(gifts), { .subtitle = std::move(subtitle), + .subtitleClick = hasAll ? showAll : Fn(), + .subtitleLinkColored = hasAll, + .subtitleOutlined = hasAll, .resalePrice = std::move(resalePrice), .resaleClick = std::move(resaleClick), .upgradeSpinner = upgradeSpinner, @@ -5268,7 +5311,7 @@ void UpgradeBox( args.addDetailsDefault), st::defaultCheckbox.margin, style::al_top); - checkbox->checkedChanges() | rpl::on_next([=](bool checked) { + checkbox->checkedValue() | rpl::on_next([=](bool checked) { state->preserveDetails = checked; }, checkbox->lifetime()); } @@ -5484,7 +5527,7 @@ void UpgradeBox( }); } - AddUniqueCloseButton(box, {}); + Ui::AddUniqueCloseButton(box); spinner->state.value() | rpl::filter( rpl::mappers::_1 == Data::GiftUpgradeSpinner::State::Finished @@ -5493,6 +5536,32 @@ void UpgradeBox( }, box->lifetime()); } +void GetVariantsAndShowUpgradeBox(UpgradeArgs &&args) { + const auto weak = base::make_weak(args.controller); + const auto session = &args.peer->session(); + const auto auctions = &session->giftAuctions(); + const auto guard = base::make_weak(args.controller); + const auto done = crl::guard(args.controller, [](UpgradeArgs &&args) { + const auto onstack = args.ready; + const auto window = args.controller; + window->show(Box(UpgradeBox, window, std::move(args))); + if (onstack) { + onstack(true); + } + }); + const auto giftId = args.stargift.id; + if (auto attributes = auctions->attributes(args.stargift.id)) { + args.all = std::move(*attributes); + done(std::move(args)); + } else { + auctions->requestAttributes(giftId, crl::guard(guard, [=] { + auto copy = args; + copy.all = std::move(*auctions->attributes(giftId)); + done(std::move(copy)); + })); + } +} + void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) { const auto weak = base::make_weak(args.controller); const auto session = &args.peer->session(); @@ -5519,10 +5588,7 @@ void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) { upgrade.backdrops.push_back(Api::FromTL(data)); }, [](const auto &) {}); } - strong->show(Box(UpgradeBox, strong, std::move(upgrade))); - if (const auto onstack = args.ready) { - onstack(true); - } + GetVariantsAndShowUpgradeBox(std::move(upgrade)); }).fail([=](const MTP::Error &error) { if (const auto strong = weak.get()) { strong->showToast(error.type()); @@ -5533,53 +5599,6 @@ void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) { }).send(); } -void AddUniqueCloseButton( - not_null box, - Settings::CreditsEntryBoxStyleOverrides st, - Fn)> fillMenu) { - const auto close = Ui::CreateChild( - box, - st::uniqueCloseButton); - const auto menu = fillMenu - ? Ui::CreateChild(box, st::uniqueMenuButton) - : nullptr; - close->show(); - close->raise(); - if (menu) { - menu->show(); - menu->raise(); - } - box->widthValue() | rpl::on_next([=](int width) { - close->moveToRight(0, 0, width); - close->raise(); - if (menu) { - menu->moveToRight(close->width(), 0, width); - menu->raise(); - } - }, close->lifetime()); - close->setClickedCallback([=] { - box->closeBox(); - }); - if (menu) { - const auto state = menu->lifetime().make_state< - base::unique_qptr - >(); - menu->setClickedCallback([=] { - if (*state) { - *state = nullptr; - return; - } - *state = base::make_unique_q( - menu, - st.menu ? *st.menu : st::popupMenuWithIcons); - fillMenu(state->get()); - if (!(*state)->empty()) { - (*state)->popup(QCursor::pos()); - } - }); - } -} - void SubmitStarsForm( std::shared_ptr show, MTPInputInvoice invoice, @@ -5623,7 +5642,7 @@ void SubmitTonForm( state->lifetime.destroy(); if (session->credits().tonBalance() < ton) { - show->show(Box(InsufficientTonBox, session->user(), ton)); + show->show(Box(Ui::InsufficientTonBox, session, ton)); } else { ready(); } diff --git a/Telegram/SourceFiles/boxes/star_gift_box.h b/Telegram/SourceFiles/boxes/star_gift_box.h index a05f6ec354..81e77a9c1c 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.h +++ b/Telegram/SourceFiles/boxes/star_gift_box.h @@ -70,6 +70,7 @@ struct UniqueGiftCoverArgs { rpl::producer subtitle; Fn subtitleClick; bool subtitleLinkColored = false; + bool subtitleOutlined = false; rpl::producer resalePrice; Fn resaleClick; bool attributesInfo = false; @@ -141,11 +142,6 @@ struct StarGiftUpgradeArgs { }; void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args); -void AddUniqueCloseButton( - not_null box, - Settings::CreditsEntryBoxStyleOverrides st, - Fn)> fillMenu = nullptr); - void SubmitStarsForm( std::shared_ptr show, MTPInputInvoice invoice, diff --git a/Telegram/SourceFiles/boxes/star_gift_preview_box.cpp b/Telegram/SourceFiles/boxes/star_gift_preview_box.cpp index 1f5b04b524..879bc91270 100644 --- a/Telegram/SourceFiles/boxes/star_gift_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_preview_box.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_sticker_player.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "ui/boxes/about_cocoon_box.h" // Ui::AddUniqueCloseButton #include "ui/layers/generic_box.h" #include "ui/text/text_custom_emoji.h" #include "ui/widgets/buttons.h" @@ -1375,7 +1376,7 @@ void StarGiftPreviewBox( .repaintedHook = repaintedHook, }); - AddUniqueCloseButton(box, {}); + Ui::AddUniqueCloseButton(box); const auto container = box->verticalLayout(); state->list = container->add(object_ptr( diff --git a/Telegram/SourceFiles/boxes/star_gift_resale_box.cpp b/Telegram/SourceFiles/boxes/star_gift_resale_box.cpp index 25ce424929..0c1cdf84dd 100644 --- a/Telegram/SourceFiles/boxes/star_gift_resale_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_resale_box.cpp @@ -152,14 +152,14 @@ struct ResaleTabs { not_null icon, bool checked = false) { auto action = base::make_unique_q( - menu, + menu->menu(), menu->st().menu, TextWithEntities{ text }, Ui::Text::MarkedContext(), QString(), icon); action->setChecked(checked); - action->setClickedCallback(std::move(callback)); + action->setActionTriggered(std::move(callback)); menu->addAction(std::move(action)); }; auto context = Core::TextContext({ .session = &show->session() }); @@ -176,14 +176,14 @@ struct ResaleTabs { QString data, bool checked) { auto action = base::make_unique_q( - menu, + menu->menu(), menu->st().menu, std::move(text), context, data, nullptr); action->setChecked(checked); - action->setClickedCallback(std::move(callback)); + action->setActionTriggered(std::move(callback)); menu->addAction(std::move(action)); }; const auto actionWithDocument = [=]( diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 61f3397e69..482a1a2aeb 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -671,7 +671,7 @@ groupCallInnerDropdown: InnerDropdown(defaultInnerDropdown) { animation: groupCallPanelAnimation; bg: groupCallMenuBg; scroll: defaultSolidScroll; - scrollPadding: margins(0px, 4px, 0px, 4px); + scrollPadding: margins(0px, 7px, 0px, 7px); } groupCallDropdownMenu: DropdownMenu(defaultDropdownMenu) { wrap: groupCallInnerDropdown; diff --git a/Telegram/SourceFiles/calls/group/calls_cover_item.cpp b/Telegram/SourceFiles/calls/group/calls_cover_item.cpp index 103ac7f64e..4d8ddbb9ab 100644 --- a/Telegram/SourceFiles/calls/group/calls_cover_item.cpp +++ b/Telegram/SourceFiles/calls/group/calls_cover_item.cpp @@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Calls { CoverItem::CoverItem( - not_null parent, + not_null parent, const style::Menu &stMenu, const style::ShortInfoCover &st, rpl::producer name, @@ -32,7 +32,7 @@ CoverItem::CoverItem( , _st(st) { setPointerCursor(false); - initResizeHook(parent->sizeValue()); + fitToMenuWidth(); enableMouseSelecting(); enableMouseSelecting(_cover.widget()); @@ -54,7 +54,7 @@ int CoverItem::contentHeight() const { } AboutItem::AboutItem( - not_null parent, + not_null parent, const style::Menu &st, TextWithEntities &&about) : Ui::Menu::ItemBase(parent, st) @@ -66,7 +66,7 @@ AboutItem::AboutItem( , _dummyAction(new QAction(parent)) { setPointerCursor(false); - initResizeHook(parent->sizeValue()); + fitToMenuWidth(); enableMouseSelecting(); enableMouseSelecting(_text.get()); diff --git a/Telegram/SourceFiles/calls/group/calls_cover_item.h b/Telegram/SourceFiles/calls/group/calls_cover_item.h index 1d737db691..5636897dbe 100644 --- a/Telegram/SourceFiles/calls/group/calls_cover_item.h +++ b/Telegram/SourceFiles/calls/group/calls_cover_item.h @@ -28,7 +28,7 @@ struct ParticipantState; class CoverItem final : public Ui::Menu::ItemBase { public: CoverItem( - not_null parent, + not_null parent, const style::Menu &stMenu, const style::ShortInfoCover &st, rpl::producer name, @@ -50,7 +50,7 @@ private: class AboutItem final : public Ui::Menu::ItemBase { public: AboutItem( - not_null parent, + not_null parent, const style::Menu &st, TextWithEntities &&about); diff --git a/Telegram/SourceFiles/calls/group/calls_group_menu.cpp b/Telegram/SourceFiles/calls/group/calls_group_menu.cpp index f9df5b7803..354e9c0dea 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_menu.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_menu.cpp @@ -36,7 +36,7 @@ namespace { class JoinAsAction final : public Ui::Menu::ItemBase { public: JoinAsAction( - not_null parent, + not_null parent, const style::Menu &st, not_null peer, Fn callback); @@ -72,7 +72,7 @@ private: class RecordingAction final : public Ui::Menu::ItemBase { public: RecordingAction( - not_null parent, + not_null parent, const style::Menu &st, rpl::producer text, rpl::producer startAtValues, @@ -116,7 +116,7 @@ TextParseOptions MenuTextOptions = { }; JoinAsAction::JoinAsAction( - not_null parent, + not_null parent, const style::Menu &st, not_null peer, Fn callback) @@ -128,8 +128,8 @@ JoinAsAction::JoinAsAction( + st::groupCallJoinAsPhotoSize + st::groupCallJoinAsPadding.bottom()) { setAcceptBoth(true); - initResizeHook(parent->sizeValue()); - setClickedCallback(std::move(callback)); + fitToMenuWidth(); + setActionTriggered(std::move(callback)); paintRequest( ) | rpl::on_next([=] { @@ -235,7 +235,7 @@ void JoinAsAction::handleKeyPress(not_null e) { } RecordingAction::RecordingAction( - not_null parent, + not_null parent, const style::Menu &st, rpl::producer text, rpl::producer startAtValues, @@ -262,8 +262,8 @@ RecordingAction::RecordingAction( }, lifetime()); setAcceptBoth(true); - initResizeHook(parent->sizeValue()); - setClickedCallback(std::move(callback)); + fitToMenuWidth(); + setActionTriggered(std::move(callback)); paintRequest( ) | rpl::on_next([=] { diff --git a/Telegram/SourceFiles/calls/group/calls_group_messages_ui.cpp b/Telegram/SourceFiles/calls/group/calls_group_messages_ui.cpp index 00eae1040f..45d74c9569 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_messages_ui.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_messages_ui.cpp @@ -380,7 +380,7 @@ void MessagesUi::setupBadges() { auto helper = Ui::Text::CustomEmojiHelper(); const auto liveText = helper.paletteDependent( Ui::Text::CustomEmojiTextBadge( - tr::lng_video_stream_live(tr::now), + tr::lng_video_stream_live(tr::now).toUpper(), st::groupCallMessageBadge, st::groupCallMessageBadgeMargin)); _liveBadge.setMarkedText( diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp index 2e7b7c1b0e..810b798e11 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -767,19 +767,21 @@ void SettingsBox( Ui::AddSkip(layout); } if (rtmp) { - const auto volumeItem = layout->add( - object_ptr( - layout, - st::groupCallVolumeSettings, - st::groupCallVolumeSettingsSlider, - call->otherParticipantStateValue( - ) | rpl::filter([=](const Group::ParticipantState &data) { - return data.peer == peer; - }), - call->rtmpVolume(), - Group::kMaxVolume, - false, - st::groupCallVolumeSettingsPadding)); + const auto fakeMenu = layout->add(object_ptr( + layout, + st::groupCallVolumeSettings)); + auto volumeItem = base::make_unique_q( + fakeMenu, + st::groupCallVolumeSettings, + st::groupCallVolumeSettingsSlider, + call->otherParticipantStateValue( + ) | rpl::filter([=](const Group::ParticipantState &data) { + return data.peer == peer; + }), + call->rtmpVolume(), + Group::kMaxVolume, + false, + st::groupCallVolumeSettingsPadding); const auto toggleMute = crl::guard(layout, [=](bool m, bool local) { if (call) { @@ -809,6 +811,8 @@ void SettingsBox( ) | rpl::on_next([=](int volume) { changeVolume(volume, true); }, volumeItem->lifetime()); + + fakeMenu->addAction(std::move(volumeItem)); } if (call->canManage()) { diff --git a/Telegram/SourceFiles/calls/group/calls_volume_item.cpp b/Telegram/SourceFiles/calls/group/calls_volume_item.cpp index 90dd3aaa07..59e301f92a 100644 --- a/Telegram/SourceFiles/calls/group/calls_volume_item.cpp +++ b/Telegram/SourceFiles/calls/group/calls_volume_item.cpp @@ -41,7 +41,7 @@ constexpr auto kVolumeStickedValues } // namespace MenuVolumeItem::MenuVolumeItem( - not_null parent, + not_null parent, const style::Menu &st, const style::MediaSlider &stSlider, rpl::producer participantState, @@ -65,7 +65,7 @@ MenuVolumeItem::MenuVolumeItem( _localMuted ? 0. : (startVolume / float(maxVolume)), Ui::Paint::ArcsAnimation::Direction::Right)) { - initResizeHook(parent->sizeValue()); + fitToMenuWidth(); enableMouseSelecting(); enableMouseSelecting(_slider.get()); diff --git a/Telegram/SourceFiles/calls/group/calls_volume_item.h b/Telegram/SourceFiles/calls/group/calls_volume_item.h index af2d38d40a..72ee6816a3 100644 --- a/Telegram/SourceFiles/calls/group/calls_volume_item.h +++ b/Telegram/SourceFiles/calls/group/calls_volume_item.h @@ -29,7 +29,7 @@ struct ParticipantState; class MenuVolumeItem final : public Ui::Menu::ItemBase { public: MenuVolumeItem( - not_null parent, + not_null parent, const style::Menu &st, const style::MediaSlider &stSlider, rpl::producer participantState, diff --git a/Telegram/SourceFiles/calls/ui/calls_device_menu.cpp b/Telegram/SourceFiles/calls/ui/calls_device_menu.cpp index bcbb91918f..6b32450756 100644 --- a/Telegram/SourceFiles/calls/ui/calls_device_menu.cpp +++ b/Telegram/SourceFiles/calls/ui/calls_device_menu.cpp @@ -25,7 +25,7 @@ namespace { class Subsection final : public Ui::Menu::ItemBase { public: Subsection( - not_null parent, + not_null parent, const style::Menu &st, const QString &text); @@ -44,7 +44,7 @@ private: class Selector final : public Ui::Menu::ItemBase { public: Selector( - not_null parent, + not_null parent, const style::Menu &st, rpl::producer> devices, rpl::producer chosen, @@ -66,7 +66,7 @@ private: }; Subsection::Subsection( - not_null parent, + not_null parent, const style::Menu &st, const QString &text) : Ui::Menu::ItemBase(parent, st) @@ -75,10 +75,10 @@ Subsection::Subsection( this, text, st::callDeviceSelectionLabel)) -, _dummyAction(new QAction(parent)) { +, _dummyAction(Ui::CreateChild(parent)) { setPointerCursor(false); - initResizeHook(parent->sizeValue()); + fitToMenuWidth(); _text->resizeToWidth(st::callDeviceSelectionLabel.minWidth); _text->moveToLeft(st.itemPadding.left(), st.itemPadding.top()); @@ -99,7 +99,7 @@ int Subsection::contentHeight() const { } Selector::Selector( - not_null parent, + not_null parent, const style::Menu &st, rpl::producer> devices, rpl::producer chosen, @@ -107,10 +107,10 @@ Selector::Selector( : Ui::Menu::ItemBase(parent, st) , _scroll(base::make_unique_q(this)) , _list(_scroll->setOwnedWidget(object_ptr(this))) -, _dummyAction(new QAction(parent)) { +, _dummyAction(Ui::CreateChild(parent)) { setPointerCursor(false); - initResizeHook(parent->sizeValue()); + fitToMenuWidth(); const auto padding = st.itemPadding; const auto group = std::make_shared(); @@ -214,10 +214,13 @@ void AddDeviceSelection( Unexpected("Type in AddDeviceSelection."); }(); menu->addAction( - base::make_unique_q(menu, menu->st().menu, title)); + base::make_unique_q( + menu->menu(), + menu->st().menu, + title)); menu->addAction( base::make_unique_q( - menu, + menu->menu(), menu->st().menu, environment->devicesValue(type.type), std::move(type.chosen), diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index a15a4e302b..cce2e5bf54 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -518,7 +518,7 @@ emojiPanRemoveSkip: 10px; emojiPanRemoveTop: 10px; emojiPanColorAllSkip: 9px; -emojiColorsPadding: 5px; +emojiColorsPadding: 8px; emojiColorsSep: 1px; emojiColorsSepColor: shadowFg; @@ -1250,6 +1250,7 @@ historyAttachEmoji: EmojiButton { lineFgOver: historyEmojiCircleFgOver; } boxAttachEmoji: EmojiButton(historyAttachEmoji) { + bg: transparent; inner: IconButton(historyAttachEmojiInner) { width: 30px; height: 30px; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp index 00e0c0e243..696541172c 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp @@ -168,7 +168,7 @@ SuggestionsWidget::SuggestionsWidget( , _session(session) , _suggestCustomEmoji(suggestCustomEmoji) , _allowCustomWithoutPremium(std::move(allowCustomWithoutPremium)) -, _overRect(st::roundRadiusSmall, _st.overBg) +, _overRect(st::roundRadiusLarge, _st.overBg) , _oneWidth(st::emojiSuggestionSize) , _padding(st::emojiSuggestionsPadding) { resize( diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp index 786320234e..4d41fc38cc 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -531,7 +531,8 @@ void GifsListWidget::selectInlineResult( || (media && media->image(PhotoSize::Large))) { _photoChosen.fire({ .photo = photo, - .options = options }); + .options = options + }); } else if (!photo->loading(PhotoSize::Thumbnail)) { photo->load(PhotoSize::Thumbnail, Data::FileOrigin()); } diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp index 8941f35aa8..3c986eadfa 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "chat_helpers/stickers_lottie.h" +#include "data/data_media_types.h" #include "data/data_session.h" #include "data/data_document.h" #include "base/unixtime.h" @@ -144,4 +145,54 @@ DocumentData *DicePacks::lookup(const QString &emoji, int value) { ).first->second->lookup(value); } +void DicePacks::resolveGameOptions( + Fn done) { + _resolveGameOptionsCallback = std::move(done); + if (_resolveGameOptionsRequestId) { + return; + } + + _resolveGameOptionsRequestId = _session->api().request( + MTPmessages_GetEmojiGameInfo() + ).done([=](const MTPmessages_EmojiGameInfo &result) { + _resolveGameOptionsRequestId = 0; + if (const auto onstack = base::take(_resolveGameOptionsCallback)) { + onstack(result.match([&]( + const MTPDmessages_emojiGameUnavailable &) { + return Data::DiceGameOptions(); + }, [&](const MTPDmessages_emojiGameDiceInfo &data) { + auto jackpot = 0; + auto rewards = std::array{}; + const auto ¶ms = data.vparams().v; + const auto count = int(params.size()); + for (auto i = 0; i != count; ++i) { + if (i < 6) { + rewards[i] = params[i].v; + } else if (i == 6) { + jackpot = params[i].v; + } else { + break; + } + } + return Data::DiceGameOptions{ + .seedHash = data.vgame_hash().v, + .previousSteakNanoTon = int64(data.vprev_stake().v), + .milliRewards = rewards, + .jackpotMilliReward = jackpot, + .currentStreak = data.vcurrent_streak().v, + .playsLeft = data.vplays_left().value_or_empty(), + }; + })); + } + }).fail([=] { + _resolveGameOptionsRequestId = 0; + if (const auto onstack = base::take(_resolveGameOptionsCallback)) { + onstack({}); + } + }).send(); +} + +void DicePacks::apply(const MTPDupdateEmojiGameInfo &update) { +} + } // namespace Stickers diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h index cb5210c351..249467e7f2 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class DocumentData; +namespace Data { +struct DiceGameOptions; +} // namespace Data + namespace Main { class Session; } // namespace Main @@ -51,11 +55,17 @@ public: [[nodiscard]] DocumentData *lookup(const QString &emoji, int value); + void resolveGameOptions(Fn done); + void apply(const MTPDupdateEmojiGameInfo &update); + private: const not_null _session; base::flat_map> _packs; + mtpRequestId _resolveGameOptionsRequestId = 0; + Fn _resolveGameOptionsCallback; + }; } // namespace Stickers diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 13e6bf3101..4e899a8bad 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -37,7 +37,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "inline_bots/inline_bot_result.h" #include "storage/storage_account.h" #include "lang/lang_keys.h" -#include "mainwindow.h" #include "dialogs/ui/dialogs_layout.h" #include "boxes/sticker_set_box.h" #include "boxes/stickers_box.h" @@ -382,8 +381,16 @@ void StickersListWidget::checkVisibleFeatured( } const auto rowHeight = featuredRowHeight(); - const auto destroyAbove = floorclamp(visibleTop - visibleHeight, rowHeight, 0, _officialSets.size()); - const auto destroyBelow = ceilclamp(visibleBottom + visibleHeight, rowHeight, 0, _officialSets.size()); + const auto destroyAbove = floorclamp( + visibleTop - visibleHeight, + rowHeight, + 0, + _officialSets.size()); + const auto destroyBelow = ceilclamp( + visibleBottom + visibleHeight, + rowHeight, + 0, + _officialSets.size()); for (auto i = 0; i != destroyAbove; ++i) { clearHeavyIn(_officialSets[i]); } @@ -430,14 +437,23 @@ void StickersListWidget::readVisibleFeatured( int visibleTop, int visibleBottom) { const auto rowHeight = featuredRowHeight(); - const auto rowFrom = floorclamp(visibleTop, rowHeight, 0, _featuredSetsCount); - const auto rowTo = ceilclamp(visibleBottom, rowHeight, 0, _featuredSetsCount); + const auto rowFrom = floorclamp( + visibleTop, + rowHeight, + 0, + _featuredSetsCount); + const auto rowTo = ceilclamp( + visibleBottom, + rowHeight, + 0, + _featuredSetsCount); for (auto i = rowFrom; i < rowTo; ++i) { auto &set = _officialSets[i]; if (!(set.flags & SetFlag::Unread)) { continue; } - if (i * rowHeight < visibleTop || (i + 1) * rowHeight > visibleBottom) { + if (i * rowHeight < visibleTop + || (i + 1) * rowHeight > visibleBottom) { continue; } int count = qMin(int(set.stickers.size()), _columnCount); @@ -482,10 +498,15 @@ bool StickersListWidget::enumerateSections(Callback callback) const { info.rowsBottom = info.top + featuredRowHeight(); } else if (set.id == Data::Stickers::MegagroupSetId && !info.count) { info.rowsCount = 0; - info.rowsBottom = info.rowsTop + _megagroupSetButtonRect.y() + _megagroupSetButtonRect.height() + st::stickerGroupCategoryAddMargin.bottom(); + info.rowsBottom = info.rowsTop + + _megagroupSetButtonRect.y() + + _megagroupSetButtonRect.height() + + st::stickerGroupCategoryAddMargin.bottom(); } else { - info.rowsCount = (info.count / _columnCount) + ((info.count % _columnCount) ? 1 : 0); - info.rowsBottom = info.rowsTop + info.rowsCount * _singleSize.height(); + info.rowsCount = (info.count / _columnCount) + + ((info.count % _columnCount) ? 1 : 0); + info.rowsBottom = info.rowsTop + + info.rowsCount * _singleSize.height(); } if (!callback(info)) { return false; @@ -495,11 +516,13 @@ bool StickersListWidget::enumerateSections(Callback callback) const { return true; } -StickersListWidget::SectionInfo StickersListWidget::sectionInfo(int section) const { +StickersListWidget::SectionInfo StickersListWidget::sectionInfo( + int section) const { Expects(section >= 0 && section < shownSets().size()); auto result = SectionInfo(); - enumerateSections([searchForSection = section, &result](const SectionInfo &info) { + enumerateSections([searchForSection = section, &result]( + const SectionInfo &info) { if (info.section == searchForSection) { result = info; return false; @@ -509,10 +532,12 @@ StickersListWidget::SectionInfo StickersListWidget::sectionInfo(int section) con return result; } -StickersListWidget::SectionInfo StickersListWidget::sectionInfoByOffset(int yOffset) const { +StickersListWidget::SectionInfo StickersListWidget::sectionInfoByOffset( + int yOffset) const { auto result = SectionInfo(); enumerateSections([this, &result, yOffset](const SectionInfo &info) { - if (yOffset < info.rowsBottom || info.section == shownSets().size() - 1) { + if (yOffset < info.rowsBottom + || info.section == shownSets().size() - 1) { result = info; return false; } @@ -528,7 +553,8 @@ int StickersListWidget::countDesiredHeight(int newWidth) { if (newWidth < 2 * minSize) { return 0; } - auto availableWidth = newWidth - (st::stickerPanPadding - st().margin.left()); + auto availableWidth = newWidth + - (st::stickerPanPadding - st().margin.left()); auto columnCount = availableWidth / minSize; auto singleWidth = availableWidth / columnCount; auto fullWidth = (st().margin.left() + newWidth + st::emojiScroll.width); @@ -560,38 +586,56 @@ int StickersListWidget::countDesiredHeight(int newWidth) { } void StickersListWidget::sendSearchRequest() { - if (_searchRequestId || _searchNextQuery.isEmpty() || _isEffects) { + if (_searchSetsRequestId + || _searchStickersRequestId + || _searchNextQuery.isEmpty() + || _isEffects) { return; } _searchRequestTimer.cancel(); _searchQuery = _searchNextQuery; - auto it = _searchCache.find(_searchQuery); - if (it != _searchCache.cend()) { + auto it = _searchStickersCache.find(_searchQuery); + if (it != _searchStickersCache.cend()) { toggleSearchLoading(false); return; } toggleSearchLoading(true); if (_searchQuery == Ui::PremiumGroupFakeEmoticon()) { toggleSearchLoading(false); - _searchRequestId = 0; - _searchCache.emplace(_searchQuery, std::vector()); + _searchSetsRequestId = 0; + _searchSetsCache.emplace(_searchQuery, std::vector()); + _searchStickersCache.emplace(_searchQuery, std::vector()); showSearchResults(); return; } const auto hash = uint64(0); - _searchRequestId = _api.request(MTPmessages_SearchStickerSets( + _searchStickersRequestId = _api.request(MTPmessages_SearchStickers( MTP_flags(0), MTP_string(_searchQuery), + MTPstring(), // emoticon + MTP_vector(), // lang_code + MTP_int(0), // offset + MTP_int(50), // limit MTP_long(hash) - )).done([=](const MTPmessages_FoundStickerSets &result) { - searchResultsDone(result); + )).done([=](const MTPmessages_FoundStickers &result) { + searchStickersResultsDone(result); }).fail([=] { - // show error? - toggleSearchLoading(false); - _searchRequestId = 0; + _searchStickersRequestId = 0; + _searchStickersCache.emplace(_searchQuery, std::vector()); + + _searchSetsRequestId = _api.request(MTPmessages_SearchStickerSets( + MTP_flags(0), + MTP_string(_searchQuery), + MTP_long(hash) + )).done([=](const MTPmessages_FoundStickerSets &result) { + searchResultsDone(result); + }).fail([=] { + toggleSearchLoading(false); + _searchSetsRequestId = 0; + }).handleAllErrors().send(); }).handleAllErrors().send(); } @@ -617,10 +661,14 @@ void StickersListWidget::searchForSets( } if (_searchQuery != cleaned) { toggleSearchLoading(false); - if (const auto requestId = base::take(_searchRequestId)) { + if (const auto requestId = base::take(_searchSetsRequestId)) { _api.request(requestId).cancel(); } - if (_searchCache.find(cleaned) != _searchCache.cend()) { + if (const auto requestId = base::take(_searchStickersRequestId)) { + _api.request(requestId).cancel(); + } + if (_searchStickersCache.find(cleaned) != _searchStickersCache.cend() + || _searchSetsCache.find(cleaned) != _searchSetsCache.cend()) { _searchRequestTimer.cancel(); _searchQuery = _searchNextQuery = cleaned; } else { @@ -633,14 +681,18 @@ void StickersListWidget::searchForSets( void StickersListWidget::cancelSetsSearch() { toggleSearchLoading(false); - if (const auto requestId = base::take(_searchRequestId)) { + if (const auto requestId = base::take(_searchSetsRequestId)) { + _api.request(requestId).cancel(); + } + if (const auto requestId = base::take(_searchStickersRequestId)) { _api.request(requestId).cancel(); } _searchRequestTimer.cancel(); _searchQuery = _searchNextQuery = QString(); _filteredStickers.clear(); _filterStickersCornerEmoji.clear(); - _searchCache.clear(); + _searchSetsCache.clear(); + _searchStickersCache.clear(); refreshSearchRows(nullptr); } @@ -650,8 +702,8 @@ void StickersListWidget::showSearchResults() { } void StickersListWidget::refreshSearchRows() { - auto it = _searchCache.find(_searchQuery); - auto sets = (it != end(_searchCache)) + auto it = _searchSetsCache.find(_searchQuery); + auto sets = (it != end(_searchSetsCache)) ? &it->second : nullptr; refreshSearchRows(sets); @@ -669,10 +721,20 @@ void StickersListWidget::refreshSearchRows( } }); + const auto foundStickersIt = _searchStickersCache.find(_searchNextQuery); + const auto hasCloudFoundStickers = true + && (foundStickersIt != _searchStickersCache.end()) + && !foundStickersIt->second.empty(); + fillFilteredStickersRow(); + if (!_isEffects) { fillLocalSearchRows(_searchNextQuery); } + + if (hasCloudFoundStickers) { + fillFoundStickersRow(foundStickersIt->second); + } if (!cloudSets && _searchNextQuery.isEmpty()) { showStickerSet(!_mySets.empty() ? _mySets[0].id @@ -741,6 +803,33 @@ void StickersListWidget::fillCloudSearchRows( } } +void StickersListWidget::fillFoundStickersRow( + const std::vector &stickerIds) { + if (stickerIds.empty()) { + return; + } + auto elements = std::vector(); + elements.reserve(stickerIds.size()); + for (const auto id : stickerIds) { + if (const auto document = session().data().document(id)) { + elements.push_back(Sticker{ document }); + } + } + if (elements.empty()) { + return; + } + + _searchSets.emplace_back( + SearchEmojiSectionSetId(), + nullptr, + Data::StickersSetFlag::Special, + QString(), + QString(), + elements.size(), + false, // externalLayout + std::move(elements)); +} + void StickersListWidget::fillFilteredStickersRow() { if (_filteredStickers.empty()) { return; @@ -786,6 +875,10 @@ void StickersListWidget::toggleSearchLoading(bool loading) { if (_search) { _search->setLoading(loading); } + if (_searchLoading != loading) { + _searchLoading = loading; + update(); + } } void StickersListWidget::takeHeavyData( @@ -834,7 +927,7 @@ void StickersListWidget::takeHeavyData(Set &to, Set &from) { } } for (const auto &sticker : fromList) { - if (sticker.lottie) { + if (sticker.lottie && to.lottiePlayer) { to.lottiePlayer->remove(sticker.lottie); } } @@ -867,34 +960,69 @@ auto StickersListWidget::shownSets() -> std::vector & { Unexpected("Section in StickersListWidget."); } +void StickersListWidget::searchStickersResultsDone( + const MTPmessages_FoundStickers &result) { + _searchStickersRequestId = 0; + + result.match([&](const MTPDmessages_foundStickersNotModified &data) { + LOG(("API: messages.foundStickersNotModified.")); + }, [&](const MTPDmessages_foundStickers &data) { + auto it = _searchStickersCache.find(_searchQuery); + if (it == _searchStickersCache.cend()) { + it = _searchStickersCache.emplace( + _searchQuery, + std::vector()).first; + } + + for (const auto &sticker : data.vstickers().v) { + if (const auto doc = session().data().processDocument(sticker)) { + it->second.push_back(doc->id); + } + } + + if (!it->second.empty()) { + toggleSearchLoading(false); + showSearchResults(); + } else { + const auto hash = uint64(0); + _searchSetsRequestId = _api.request(MTPmessages_SearchStickerSets( + MTP_flags(0), + MTP_string(_searchQuery), + MTP_long(hash) + )).done([=](const MTPmessages_FoundStickerSets &result) { + searchResultsDone(result); + }).fail([=] { + toggleSearchLoading(false); + _searchSetsRequestId = 0; + }).handleAllErrors().send(); + } + }); +} + void StickersListWidget::searchResultsDone( const MTPmessages_FoundStickerSets &result) { toggleSearchLoading(false); - _searchRequestId = 0; + _searchSetsRequestId = 0; - if (result.type() == mtpc_messages_foundStickerSetsNotModified) { + result.match([&](const MTPDmessages_foundStickerSetsNotModified &data) { LOG(("API Error: " "messages.foundStickerSetsNotModified not expected.")); - return; - } - - Assert(result.type() == mtpc_messages_foundStickerSets); - - auto it = _searchCache.find(_searchQuery); - if (it == _searchCache.cend()) { - it = _searchCache.emplace( - _searchQuery, - std::vector()).first; - } - auto &d = result.c_messages_foundStickerSets(); - for (const auto &data : d.vsets().v) { - const auto set = session().data().stickers().feedSet(data); - if (set->stickers.empty() && set->covers.empty()) { - continue; + }, [&](const MTPDmessages_foundStickerSets &data) { + auto it = _searchSetsCache.find(_searchQuery); + if (it == _searchSetsCache.cend()) { + it = _searchSetsCache.emplace( + _searchQuery, + std::vector()).first; } - it->second.push_back(set->id); - } - showSearchResults(); + for (const auto &setData : data.vsets().v) { + const auto set = session().data().stickers().feedSet(setData); + if (set->stickers.empty() && set->covers.empty()) { + continue; + } + it->second.push_back(set->id); + } + showSearchResults(); + }); } int StickersListWidget::stickersLeft() const { @@ -902,14 +1030,16 @@ int StickersListWidget::stickersLeft() const { } QRect StickersListWidget::stickerRect(int section, int sel) { - auto info = sectionInfo(section); + const auto info = sectionInfo(section); if (sel >= shownSets()[section].stickers.size()) { sel -= shownSets()[section].stickers.size(); } - auto countTillItem = (sel - (sel % _columnCount)); - auto rowsToSkip = (countTillItem / _columnCount) + ((countTillItem % _columnCount) ? 1 : 0); - auto x = stickersLeft() + ((sel % _columnCount) * _singleSize.width()); - auto y = info.rowsTop + rowsToSkip * _singleSize.height(); + const auto countTillItem = (sel - (sel % _columnCount)); + const auto rowsToSkip = (countTillItem / _columnCount) + + ((countTillItem % _columnCount) ? 1 : 0); + const auto x = stickersLeft() + + ((sel % _columnCount) * _singleSize.width()); + const auto y = info.rowsTop + rowsToSkip * _singleSize.height(); return QRect(QPoint(x, y), _singleSize); } @@ -924,8 +1054,16 @@ void StickersListWidget::paintEvent(QPaintEvent *e) { } void StickersListWidget::paintStickers(Painter &p, QRect clip) { - auto fromColumn = floorclamp(clip.x() - stickersLeft(), _singleSize.width(), 0, _columnCount); - auto toColumn = ceilclamp(clip.x() + clip.width() - stickersLeft(), _singleSize.width(), 0, _columnCount); + auto fromColumn = floorclamp( + clip.x() - stickersLeft(), + _singleSize.width(), + 0, + _columnCount); + auto toColumn = ceilclamp( + clip.x() + clip.width() - stickersLeft(), + _singleSize.width(), + 0, + _columnCount); if (rtl()) { qSwap(fromColumn, toColumn); fromColumn = _columnCount - fromColumn; @@ -936,8 +1074,8 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { _pathGradient->startFrame(0, width(), width() / 2); auto &sets = shownSets(); - auto selectedSticker = std::get_if(&_selected); - auto selectedButton = std::get_if(!v::is_null(_pressed) + const auto selectedSticker = std::get_if(&_selected); + const auto selectedButton = std::get_if(!v::is_null(_pressed) ? &_pressed : &_selected); @@ -945,7 +1083,14 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { const auto paused = On(PowerSaving::kStickersPanel) || this->paused(); if (sets.empty() && _section == Section::Search) { - paintEmptySearchResults(p); + const auto loading = _searchLoading || _searchRequestTimer.isActive(); + Inner::paintEmptySearchResults( + p, + st::stickersEmpty, + loading + ? tr::lng_contacts_loading(tr::now) + : tr::lng_stickers_nothing_found(tr::now), + loading); } const auto badgeText = tr::lng_stickers_creator_badge(tr::now); const auto &badgeFont = st::stickersHeaderBadgeFont; @@ -963,7 +1108,8 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { ? set.count : loadedCount; - auto widthForTitle = stickersRight() - (st().headerLeft - st().margin.left()); + auto widthForTitle = stickersRight() + - (st().headerLeft - st().margin.left()); { const auto installedSet = !featuredHasAddButton(info.section); const auto add = featuredAddRect(info, installedSet); @@ -1000,18 +1146,27 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { widthForTitle -= add.width() - (st.width / 2); } if (set.flags & SetFlag::Unread) { - widthForTitle -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip; + widthForTitle -= st::stickersFeaturedUnreadSize + + st::stickersFeaturedUnreadSkip; } auto titleText = set.title; - auto titleWidth = st::stickersTrendingHeaderFont->width(titleText); + auto titleWidth = st::stickersTrendingHeaderFont->width( + titleText); if (titleWidth > widthForTitle) { - titleText = st::stickersTrendingHeaderFont->elided(titleText, widthForTitle); + titleText = st::stickersTrendingHeaderFont->elided( + titleText, + widthForTitle); titleWidth = st::stickersTrendingHeaderFont->width(titleText); } p.setFont(st::stickersTrendingHeaderFont); p.setPen(st().trendingHeaderFg); - p.drawTextLeft(st().headerLeft - st().margin.left(), info.top + st::stickersTrendingHeaderTop, width(), titleText, titleWidth); + p.drawTextLeft( + st().headerLeft - st().margin.left(), + info.top + st::stickersTrendingHeaderTop, + width(), + titleText, + titleWidth); if (set.flags & SetFlag::Unread) { p.setPen(Qt::NoPen); @@ -1019,26 +1174,56 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { { PainterHighQualityEnabler hq(p); - p.drawEllipse(style::rtlrect(st().headerLeft - st().margin.left() + titleWidth + st::stickersFeaturedUnreadSkip, info.top + st::stickersTrendingHeaderTop + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width())); + p.drawEllipse( + style::rtlrect( + st().headerLeft + - st().margin.left() + + titleWidth + + st::stickersFeaturedUnreadSkip, + info.top + + st::stickersTrendingHeaderTop + + st::stickersFeaturedUnreadTop, + st::stickersFeaturedUnreadSize, + st::stickersFeaturedUnreadSize, width())); } } - auto statusText = (count > 0) ? tr::lng_stickers_count(tr::now, lt_count, count) : tr::lng_contacts_loading(tr::now); + const auto statusText = (count > 0) + ? tr::lng_stickers_count(tr::now, lt_count, count) + : tr::lng_contacts_loading(tr::now); p.setFont(st::stickersTrendingSubheaderFont); p.setPen(st().trendingSubheaderFg); - p.drawTextLeft(st().headerLeft - st().margin.left(), info.top + st::stickersTrendingSubheaderTop, width(), statusText); + p.drawTextLeft( + st().headerLeft - st().margin.left(), + info.top + st::stickersTrendingSubheaderTop, + width(), + statusText); if (info.rowsTop >= clip.y() + clip.height()) { return true; } - for (int j = fromColumn; j < toColumn; ++j) { - int index = j; - if (index >= loadedCount) break; + for (auto j = fromColumn; j < toColumn; ++j) { + const auto index = j; + if (index >= loadedCount) { + break; + } - auto selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false; - auto deleteSelected = false; - paintSticker(p, set, info.rowsTop, info.section, index, now, paused, selected, deleteSelected); + const auto selected = selectedSticker + ? (selectedSticker->section == info.section + && selectedSticker->index == index) + : false; + const auto deleteSelected = false; + paintSticker( + p, + set, + info.rowsTop, + info.section, + index, + now, + paused, + selected, + deleteSelected); } if (!paused) { markLottieFrameShown(set); @@ -1047,19 +1232,29 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { } if (setHasTitle(set) && clip.top() < info.rowsTop) { auto titleText = set.title; - auto titleWidth = st::stickersTrendingHeaderFont->width(titleText); - auto widthForTitle = stickersRight() - (st().headerLeft - st().margin.left()); + auto titleWidth = st::stickersTrendingHeaderFont->width( + titleText); + auto widthForTitle = stickersRight() + - (st().headerLeft - st().margin.left()); if (hasRemoveButton(info.section)) { - auto remove = removeButtonRect(info); - auto selected = selectedButton ? (selectedButton->section == info.section) : false; + const auto remove = removeButtonRect(info); + const auto selected = selectedButton + ? (selectedButton->section == info.section) + : false; const auto &removeSt = st().removeSet; if (set.ripple) { - set.ripple->paint(p, remove.x() + removeSt.rippleAreaPosition.x(), remove.y() + removeSt.rippleAreaPosition.y(), width()); + set.ripple->paint( + p, + remove.x() + removeSt.rippleAreaPosition.x(), + remove.y() + removeSt.rippleAreaPosition.y(), + width()); if (set.ripple->empty()) { set.ripple.reset(); } } - const auto &icon = selected ? removeSt.iconOver : removeSt.icon; + const auto &icon = selected + ? removeSt.iconOver + : removeSt.icon; icon.paint( p, remove.x() + (remove.width() - icon.width()) / 2, @@ -1068,19 +1263,27 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { widthForTitle -= remove.width(); } - const auto amCreator = (set.flags & Data::StickersSetFlag::AmCreator); + const auto amCreator + = (set.flags & Data::StickersSetFlag::AmCreator); if (amCreator) { widthForTitle -= badgeWidth + st::stickersFeaturedUnreadSkip + st::stickersHeaderBadgeFontSkip; } if (titleWidth > widthForTitle) { - titleText = st::stickersTrendingHeaderFont->elided(titleText, widthForTitle); + titleText = st::stickersTrendingHeaderFont->elided( + titleText, + widthForTitle); titleWidth = st::stickersTrendingHeaderFont->width(titleText); } p.setFont(st::emojiPanHeaderFont); p.setPen(st().headerFg); - p.drawTextLeft(st().headerLeft - st().margin.left(), info.top + st().headerTop, width(), titleText, titleWidth); + p.drawTextLeft( + st().headerLeft - st().margin.left(), + info.top + st().headerTop, + width(), + titleText, + titleWidth); if (amCreator) { const auto badgeLeft = st().headerLeft - st().margin.left() @@ -1117,21 +1320,46 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { } if (clip.top() + clip.height() <= info.rowsTop) { return true; - } else if (set.id == Data::Stickers::MegagroupSetId && set.stickers.empty()) { - auto buttonSelected = (std::get_if(&_selected) != nullptr); + } else if (set.id == Data::Stickers::MegagroupSetId + && set.stickers.empty()) { + const auto buttonSelected = (std::get_if(&_selected) + != nullptr); paintMegagroupEmptySet(p, info.rowsTop, buttonSelected); return true; } - auto fromRow = floorclamp(clip.y() - info.rowsTop, _singleSize.height(), 0, info.rowsCount); - auto toRow = ceilclamp(clip.y() + clip.height() - info.rowsTop, _singleSize.height(), 0, info.rowsCount); - for (int i = fromRow; i < toRow; ++i) { - for (int j = fromColumn; j < toColumn; ++j) { - int index = i * _columnCount + j; - if (index >= info.count) break; + const auto fromRow = floorclamp( + clip.y() - info.rowsTop, + _singleSize.height(), + 0, + info.rowsCount); + const auto toRow = ceilclamp( + clip.y() + clip.height() - info.rowsTop, + _singleSize.height(), + 0, + info.rowsCount); + for (auto i = fromRow; i < toRow; ++i) { + for (auto j = fromColumn; j < toColumn; ++j) { + const auto index = int(i * _columnCount + j); + if (index >= info.count) { + break; + } - auto selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false; - auto deleteSelected = selected && selectedSticker->overDelete; - paintSticker(p, set, info.rowsTop, info.section, index, now, paused, selected, deleteSelected); + const auto selected = selectedSticker + ? (selectedSticker->section == info.section + && selectedSticker->index == index) + : false; + const auto deleteSelected = selected + && selectedSticker->overDelete; + paintSticker( + p, + set, + info.rowsTop, + info.section, + index, + now, + paused, + selected, + deleteSelected); } } if (!paused) { @@ -1225,18 +1453,14 @@ void StickersListWidget::pauseInvisibleLottieIn(const SectionInfo &info) { } } -void StickersListWidget::paintEmptySearchResults(Painter &p) { - Inner::paintEmptySearchResults( - p, - st::stickersEmpty, - tr::lng_stickers_nothing_found(tr::now)); -} - int StickersListWidget::megagroupSetInfoLeft() const { return st().headerLeft - st().margin.left(); } -void StickersListWidget::paintMegagroupEmptySet(Painter &p, int y, bool buttonSelected) { +void StickersListWidget::paintMegagroupEmptySet( + Painter &p, + int y, + bool buttonSelected) { p.setPen(st().headerFg); auto infoLeft = megagroupSetInfoLeft(); @@ -1253,8 +1477,15 @@ void StickersListWidget::paintMegagroupEmptySet(Painter &p, int y, bool buttonSe } } p.setFont(st::stickerGroupCategoryAdd.style.font); - p.setPen(buttonSelected ? st::stickerGroupCategoryAdd.textFgOver : st::stickerGroupCategoryAdd.textFg); - p.drawTextLeft(button.x() - (st::stickerGroupCategoryAdd.width / 2), button.y() + st::stickerGroupCategoryAdd.textTop, width(), _megagroupSetButtonText, _megagroupSetButtonTextWidth); + p.setPen(buttonSelected + ? st::stickerGroupCategoryAdd.textFgOver + : st::stickerGroupCategoryAdd.textFg); + p.drawTextLeft( + button.x() - (st::stickerGroupCategoryAdd.width / 2), + button.y() + st::stickerGroupCategoryAdd.textTop, + width(), + _megagroupSetButtonText, + _megagroupSetButtonTextWidth); } void StickersListWidget::ensureLottiePlayer(Set &set) { @@ -1468,12 +1699,17 @@ void StickersListWidget::paintSticker( setupWebm(set, section, index); } - int row = (index / _columnCount), col = (index % _columnCount); + const auto row = int((index / _columnCount)); + const auto col = int(index % _columnCount); - auto pos = QPoint(stickersLeft() + col * _singleSize.width(), y + row * _singleSize.height()); + const auto pos = QPoint( + stickersLeft() + col * _singleSize.width(), + y + row * _singleSize.height()); if (selected) { auto tl = pos; - if (rtl()) tl.setX(width() - tl.x() - _singleSize.width()); + if (rtl()) { + tl.setX(width() - tl.x() - _singleSize.width()); + } _overBg.paint(p, QRect(tl, _singleSize)); } @@ -1552,10 +1788,17 @@ void StickersListWidget::paintSticker( p.restore(); if (selected && stickerHasDeleteButton(set, index)) { - auto xPos = pos + QPoint(_singleSize.width() - st::stickerPanDeleteIconBg.width(), 0); - p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityBgOver : st::stickerPanDeleteOpacityBg); + const auto xPos = pos + + QPoint( + _singleSize.width() - st::stickerPanDeleteIconBg.width(), + 0); + p.setOpacity(deleteSelected + ? st::stickerPanDeleteOpacityBgOver + : st::stickerPanDeleteOpacityBg); st::stickerPanDeleteIconBg.paint(p, xPos, width()); - p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityFgOver : st::stickerPanDeleteOpacityFg); + p.setOpacity(deleteSelected + ? st::stickerPanDeleteOpacityFgOver + : st::stickerPanDeleteOpacityFg); st::stickerPanDeleteIconFg.paint(p, xPos, width()); p.setOpacity(1.); } @@ -1687,24 +1930,29 @@ void StickersListWidget::setPressed(OverState newPressed) { if (!set.ripple) { set.ripple = createButtonRipple(button->section); } - set.ripple->add(mapFromGlobal(QCursor::pos()) - buttonRippleTopLeft(button->section)); + set.ripple->add(mapFromGlobal(QCursor::pos()) + - buttonRippleTopLeft(button->section)); } else if (std::get_if(&_pressed)) { if (!_megagroupSetButtonRipple) { - auto maskSize = _megagroupSetButtonRect.size(); - auto mask = Ui::RippleAnimation::RoundRectMask(maskSize, st::roundRadiusLarge); - _megagroupSetButtonRipple = std::make_unique(st::stickerGroupCategoryAdd.ripple, std::move(mask), [this] { - rtlupdate(megagroupSetButtonRectFinal()); - }); + auto mask = Ui::RippleAnimation::RoundRectMask( + _megagroupSetButtonRect.size(), + st::roundRadiusLarge); + _megagroupSetButtonRipple = std::make_unique( + st::stickerGroupCategoryAdd.ripple, + std::move(mask), + [this] { rtlupdate(megagroupSetButtonRectFinal()); }); } - _megagroupSetButtonRipple->add(mapFromGlobal(QCursor::pos()) - myrtlrect(megagroupSetButtonRectFinal()).topLeft()); + _megagroupSetButtonRipple->add(mapFromGlobal(QCursor::pos()) + - myrtlrect(megagroupSetButtonRectFinal()).topLeft()); } } QRect StickersListWidget::megagroupSetButtonRectFinal() const { auto result = QRect(); if (_section == Section::Stickers) { + using Stickers = Data::Stickers; enumerateSections([this, &result](const SectionInfo &info) { - if (shownSets()[info.section].id == Data::Stickers::MegagroupSetId) { + if (shownSets()[info.section].id == Stickers::MegagroupSetId) { result = _megagroupSetButtonRect.translated(0, info.rowsTop); return false; } @@ -1714,12 +1962,17 @@ QRect StickersListWidget::megagroupSetButtonRectFinal() const { return result; } -std::unique_ptr StickersListWidget::createButtonRipple(int section) { +std::unique_ptr StickersListWidget::createButtonRipple( + int section) { Expects(section >= 0 && section < shownSets().size()); if (shownSets()[section].externalLayout) { - auto maskSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height); - auto mask = Ui::RippleAnimation::RoundRectMask(maskSize, st::roundRadiusLarge); + const auto maskSize = QSize( + _addWidth - st::stickersTrendingAdd.width, + st::stickersTrendingAdd.height); + auto mask = Ui::RippleAnimation::RoundRectMask( + maskSize, + st::roundRadiusLarge); return std::make_unique( st::stickersTrendingAdd.ripple, std::move(mask), @@ -2163,7 +2416,11 @@ void StickersListWidget::refreshFeaturedSets() { - wasFeaturedSetsCount); for (const auto setId : session().data().stickers().featuredSetsOrder()) { const auto externalLayout = true; - appendSet(_officialSets, setId, externalLayout, AppendSkip::Installed); + appendSet( + _officialSets, + setId, + externalLayout, + AppendSkip::Installed); } _featuredSetsCount = _officialSets.size(); if (wereOfficial.size() > wasFeaturedSetsCount) { @@ -2361,7 +2618,7 @@ auto StickersListWidget::collectRecentStickers() -> std::vector { }; if (cloudCount > 0) { - for (const auto document : std::as_const(cloudIt->second->stickers)) { + for (const auto document : cloudIt->second->stickers) { add(document, false); } } @@ -2369,7 +2626,7 @@ auto StickersListWidget::collectRecentStickers() -> std::vector { add(recentSticker.first, false); } if (customCount > 0) { - for (const auto document : std::as_const(customIt->second->stickers)) { + for (const auto document : customIt->second->stickers) { add(document, true); } } @@ -2383,7 +2640,7 @@ void StickersListWidget::refreshRecentStickers(bool performResize) { if (_section == Section::Stickers) { _recentShownCount = recentPack.size(); } - auto recentIt = std::find_if(_mySets.begin(), _mySets.end(), [](auto &set) { + const auto recentIt = ranges::find_if(_mySets, [](auto &set) { return set.id == Data::Stickers::RecentSetId; }); if (!recentPack.empty()) { @@ -2414,7 +2671,8 @@ void StickersListWidget::refreshRecentStickers(bool performResize) { _mySets.erase(recentIt); } - if (performResize && (_section == Section::Stickers || _section == Section::Featured)) { + if ((_section == Section::Stickers || _section == Section::Featured) + && performResize) { resizeToWidth(width()); updateSelected(); } @@ -2482,10 +2740,12 @@ void StickersListWidget::refreshMegagroupStickers(GroupStickersPlace place) { } return; } - auto hidden = session().settings().isGroupStickersSectionHidden(_megagroupSet->id); + auto hidden = session().settings().isGroupStickersSectionHidden( + _megagroupSet->id); auto removeHiddenForGroup = [this, &hidden] { if (hidden) { - session().settings().removeGroupStickersSectionHidden(_megagroupSet->id); + session().settings().removeGroupStickersSectionHidden( + _megagroupSet->id); session().saveSettings(); hidden = false; } @@ -2532,7 +2792,8 @@ void StickersListWidget::refreshMegagroupStickers(GroupStickersPlace place) { MTP_int(0) // hash )).done([=](const MTPmessages_StickerSet &result) { result.match([&](const MTPDmessages_stickerSet &data) { - if (const auto set = session().data().stickers().feedSetFull(data)) { + if (const auto set = session().data().stickers().feedSetFull( + data)) { refreshStickers(); if (set->id == _megagroupSetIdRequested) { _megagroupSetIdRequested = 0; @@ -2604,9 +2865,11 @@ void StickersListWidget::updateSelected() { auto info = sectionInfoByOffset(p.y()); auto section = info.section; if (p.y() >= info.top && p.y() < info.rowsTop) { - if (hasRemoveButton(section) && myrtlrect(removeButtonRect(info)).contains(p.x(), p.y())) { + if (hasRemoveButton(section) + && myrtlrect(removeButtonRect(info)).contains(p)) { newSelected = OverButton{ section }; - } else if (featuredHasAddButton(section) && myrtlrect(featuredAddRect(info, false)).contains(p.x(), p.y())) { + } else if (featuredHasAddButton(section) + && myrtlrect(featuredAddRect(info, false)).contains(p)) { newSelected = OverButton{ section }; } else if (_features.openStickerSets && !(sets[section].flags & SetFlag::Special)) { @@ -2616,23 +2879,32 @@ void StickersListWidget::updateSelected() { || !sets[section].stickers.empty())) { newSelected = OverSet{ section }; } - } else if (p.y() >= info.rowsTop && p.y() < info.rowsBottom && sx >= 0) { - auto yOffset = p.y() - info.rowsTop; - auto &set = sets[section]; - if (set.id == Data::Stickers::MegagroupSetId && set.stickers.empty()) { - if (_megagroupSetButtonRect.contains(stickersLeft() + sx, yOffset)) { + } else if (p.y() >= info.rowsTop + && p.y() < info.rowsBottom + && sx >= 0) { + const auto yOffset = p.y() - info.rowsTop; + const auto &set = sets[section]; + if (set.id == Data::Stickers::MegagroupSetId + && set.stickers.empty()) { + if (_megagroupSetButtonRect.contains( + stickersLeft() + sx, + yOffset)) { newSelected = OverGroupAdd{}; } } else { - auto rowIndex = qFloor(yOffset / _singleSize.height()); - auto columnIndex = qFloor(sx / _singleSize.width()); - auto index = rowIndex * _columnCount + columnIndex; + const auto rowIndex = qFloor(yOffset / _singleSize.height()); + const auto columnIndex = qFloor(sx / _singleSize.width()); + const auto index = rowIndex * _columnCount + columnIndex; if (index >= 0 && index < set.stickers.size()) { auto overDelete = false; if (stickerHasDeleteButton(set, index)) { - auto inx = sx - (columnIndex * _singleSize.width()); - auto iny = yOffset - (rowIndex * _singleSize.height()); - if (inx >= _singleSize.width() - st::stickerPanDeleteIconBg.width() && iny < st::stickerPanDeleteIconBg.height()) { + const auto inx = sx + - (columnIndex * _singleSize.width()); + const auto iny = yOffset + - (rowIndex * _singleSize.height()); + const auto &icon = st::stickerPanDeleteIconBg; + if (inx >= _singleSize.width() - icon.width() + && iny < icon.height()) { overDelete = true; } } @@ -2695,11 +2967,15 @@ void StickersListWidget::setSelected(OverState newSelected) { if (_previewShown && _pressed != _selected) { if (const auto sticker = std::get_if(&_selected)) { _pressed = _selected; - Assert(sticker->section >= 0 && sticker->section < sets.size()); + Assert(sticker->section >= 0 + && sticker->section < sets.size()); const auto &set = sets[sticker->section]; - Assert(sticker->index >= 0 && sticker->index < set.stickers.size()); + Assert(sticker->index >= 0 + && sticker->index < set.stickers.size()); const auto document = set.stickers[sticker->index].document; - _show->showMediaPreview(document->stickerSetOrigin(), document); + _show->showMediaPreview( + document->stickerSetOrigin(), + document); } } } @@ -2794,12 +3070,15 @@ void StickersListWidget::refreshIcons(ValidateIconAnimations animations) { } void StickersListWidget::refreshMegagroupSetGeometry() { - auto left = megagroupSetInfoLeft(); - auto availableWidth = (width() - left); - auto top = _megagroupSetAbout.countHeight(availableWidth) + st::stickerGroupCategoryAddMargin.top(); - _megagroupSetButtonTextWidth = st::stickerGroupCategoryAdd.style.font->width(_megagroupSetButtonText); - auto buttonWidth = _megagroupSetButtonTextWidth - st::stickerGroupCategoryAdd.width; - _megagroupSetButtonRect = QRect(left, top, buttonWidth, st::stickerGroupCategoryAdd.height); + const auto left = megagroupSetInfoLeft(); + const auto availableWidth = (width() - left); + const auto top = _megagroupSetAbout.countHeight(availableWidth) + + st::stickerGroupCategoryAddMargin.top(); + const auto &st = st::stickerGroupCategoryAdd; + _megagroupSetButtonTextWidth = st.style.font->width( + _megagroupSetButtonText); + const auto buttonWidth = _megagroupSetButtonTextWidth - st.width; + _megagroupSetButtonRect = QRect(left, top, buttonWidth, st.height); } void StickersListWidget::showMegagroupSet(ChannelData *megagroup) { diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h index cb7a7e6ba7..ed67bf32df 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h @@ -287,7 +287,6 @@ private: bool paused, bool selected, bool deleteSelected); - void paintEmptySearchResults(Painter &p); void ensureLottiePlayer(Set &set); void setupLottie(Set &set, int section, int index); @@ -358,11 +357,13 @@ private: void cancelSetsSearch(); void showSearchResults(); void searchResultsDone(const MTPmessages_FoundStickerSets &result); + void searchStickersResultsDone(const MTPmessages_FoundStickers &result); void refreshSearchRows(); void refreshSearchRows(const std::vector *cloudSets); void fillFilteredStickersRow(); void fillLocalSearchRows(const QString &query); void fillCloudSearchRows(const std::vector &cloudSets); + void fillFoundStickersRow(const std::vector &stickerIds); void addSearchRow(not_null set); void toggleSearchLoading(bool loading); @@ -444,11 +445,14 @@ private: std::vector> _filteredStickers; std::vector _filterStickersCornerEmoji; rpl::variable _recentShownCount; - std::map> _searchCache; + std::map> _searchSetsCache; + std::map> _searchStickersCache; std::vector> _searchIndex; base::Timer _searchRequestTimer; QString _searchQuery, _searchNextQuery; - mtpRequestId _searchRequestId = 0; + mtpRequestId _searchSetsRequestId = 0; + mtpRequestId _searchStickersRequestId = 0; + bool _searchLoading = false; rpl::event_stream _chosen; rpl::event_stream<> _scrollUpdated; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp b/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp index 04ceda97cb..0c62f7680a 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp @@ -354,10 +354,37 @@ not_null GenerateLocalSticker( not_null GenerateLocalTgsSticker( not_null session, const QString &name) { + const auto cache = [&] { + struct Session { + base::weak_ptr session; + base::flat_map> cache; + }; + static auto Map = std::vector(); + for (auto i = begin(Map); i != end(Map);) { + if (const auto strong = i->session.get()) { + if (strong == session) { + return &i->cache; + } + ++i; + } else { + i = Map.erase(i); + } + } + Map.push_back({ .session = session }); + return &Map.back().cache; + }(); + + const auto i = cache->find(name); + if (i != end(*cache)) { + return i->second; + } + const auto result = GenerateLocalSticker( session, u":/animations/"_q + name + u".tgs"_q); + cache->emplace(name, result); + Ensures(result->sticker()->isLottie()); return result; } diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp index 3676866a30..518cb24067 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp @@ -12,7 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/ui_utility.h" #include "chat_helpers/tabbed_selector.h" #include "window/window_session_controller.h" -#include "mainwindow.h" +#include "main/main_session.h" +#include "data/data_session.h" +#include "data/stickers/data_stickers.h" #include "ayu/ayu_settings.h" #include "core/application.h" #include "base/options.h" @@ -116,6 +118,13 @@ TabbedPanel::TabbedPanel( hideAnimated(); }, lifetime()); + if (_regularWindow) { + _regularWindow->session().data().stickers().gifWithCaptionSent( + ) | rpl::on_next([=] { + hideAnimated(); + }, lifetime()); + } + _selector->slideFinished( ) | rpl::on_next([=] { InvokeQueued(this, [=] { diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 317129cb45..65c0aaf7f3 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -1494,12 +1494,15 @@ void TabbedSelector::Inner::checkHideWithBox( void TabbedSelector::Inner::paintEmptySearchResults( Painter &p, const style::icon &icon, - const QString &text) const { + const QString &text, + bool skipIcon) const { const auto iconLeft = (width() - icon.width()) / 2; const auto iconTop = std::max( (height() / 3) - (icon.height() / 2), st::normalFont->height); - icon.paint(p, iconLeft, iconTop, width()); + if (!skipIcon) { + icon.paint(p, iconLeft, iconTop, width()); + } const auto textWidth = st::normalFont->width(text); const auto textTop = std::min( diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index 47e8c4b649..903da475b1 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -422,7 +422,8 @@ protected: void paintEmptySearchResults( Painter &p, const style::icon &icon, - const QString &text) const; + const QString &text, + bool skipIcon = false) const; private: const style::EmojiPan &_st; diff --git a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp index da48ee36b0..d31bcbf829 100644 --- a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp @@ -166,8 +166,8 @@ PreviewWrap::PreviewWrap( const auto session = &_item->history()->session(); session->data().viewRepaintRequest( - ) | rpl::on_next([=](not_null view) { - if (view == _element.get()) { + ) | rpl::on_next([=](Data::RequestViewRepaint data) { + if (data.view == _element.get()) { update(_elementGeometry); } }, lifetime()); @@ -330,6 +330,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { _style.get(), Rect(_element->currentSize()), Rect(_element->currentSize()), + Rect(_element->currentSize()), !window()->isActiveWindow()); context.outbg = _element->hasOutLayout(); _element->draw(p, context); diff --git a/Telegram/SourceFiles/core/bank_card_click_handler.cpp b/Telegram/SourceFiles/core/bank_card_click_handler.cpp index 1bcca7f0ec..3191c49122 100644 --- a/Telegram/SourceFiles/core/bank_card_click_handler.cpp +++ b/Telegram/SourceFiles/core/bank_card_click_handler.cpp @@ -72,7 +72,7 @@ void RequestResolveBankCard( class ResolveBankCardAction final : public Ui::Menu::ItemBase { public: ResolveBankCardAction( - not_null parent, + not_null parent, const style::Menu &st); void setStatus(Status status); @@ -98,14 +98,14 @@ private: }; ResolveBankCardAction::ResolveBankCardAction( - not_null parent, + not_null parent, const style::Menu &st) : ItemBase(parent, st) , _dummyAction(Ui::CreateChild(parent)) , _st(st) , _height(st::groupCallJoinAsPhotoSize) { setAcceptBoth(true); - initResizeHook(parent->sizeValue()); + fitToMenuWidth(); setStatus(Status::Loading); } @@ -199,7 +199,7 @@ void BankCardClickHandler::onClick(ClickContext context) const { &st::menuIconCopy); auto resolveBankCardAction = base::make_unique_q( - menu, + menu->menu(), menu->st().menu); const auto resolveBankCardRaw = resolveBankCardAction.get(); @@ -209,12 +209,12 @@ void BankCardClickHandler::onClick(ClickContext context) const { const auto addTitle = [=](const QString &name) { auto button = base::make_unique_q( - menu, + menu->menu(), menu->st().menu, st::historyHasCustomEmoji, st::historyBankCardMenuMultilinePosition, TextWithEntities{ name }); - button->setClickedCallback(copy); + button->setActionTriggered(copy); menu->addAction(std::move(button)); }; diff --git a/Telegram/SourceFiles/core/core_cloud_password.cpp b/Telegram/SourceFiles/core/core_cloud_password.cpp index 4a9cd52196..95e1782a8f 100644 --- a/Telegram/SourceFiles/core/core_cloud_password.cpp +++ b/Telegram/SourceFiles/core/core_cloud_password.cpp @@ -315,8 +315,8 @@ CloudPasswordState ParseCloudPasswordState( ParseSecureSecretAlgo(data.vnew_secure_algo())); result.unconfirmedPattern = qs( data.vemail_unconfirmed_pattern().value_or_empty()); - const auto pattern = qs(data.vlogin_email_pattern().value_or_empty()); - result.loginEmailPattern = pattern.contains(' ') ? QString() : pattern; + result.loginEmailPattern = qs( + data.vlogin_email_pattern().value_or_empty()); result.pendingResetDate = data.vpending_reset_date().value_or_empty(); result.outdatedClient = [&] { diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index aeaf30ec15..8271ed6a58 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -510,7 +510,14 @@ void LoginEmailBox( { box->getDelegate()->setTitle(rpl::duplicate( email - ) | rpl::map(Ui::Text::WrapEmailPattern)); + ) | rpl::map([](QString email) { + if (email.contains(' ')) { + return tr::lng_settings_cloud_login_email_section_title( + tr::now, + tr::rich); + } + return Ui::Text::WrapEmailPattern(std::move(email)); + })); for (const auto &child : ranges::views::reverse( box->parentWidget()->children())) { if (child && child->isWidgetType()) { diff --git a/Telegram/SourceFiles/core/phone_click_handler.cpp b/Telegram/SourceFiles/core/phone_click_handler.cpp index 3656708b8a..c752a63701 100644 --- a/Telegram/SourceFiles/core/phone_click_handler.cpp +++ b/Telegram/SourceFiles/core/phone_click_handler.cpp @@ -40,7 +40,7 @@ namespace { class ResolvePhoneAction final : public Ui::Menu::ItemBase { public: ResolvePhoneAction( - not_null parent, + not_null parent, const style::PopupMenu &st, const QString &phone, not_null controller); @@ -65,7 +65,6 @@ private: const not_null _dummyAction; const style::Menu &_st; - const int _shadowPadding; rpl::variable _peer; rpl::variable _loaded; Ui::PeerUserpicView _userpicView; @@ -81,20 +80,19 @@ private: }; ResolvePhoneAction::ResolvePhoneAction( - not_null parent, + not_null parent, const style::PopupMenu &st, const QString &phone, not_null controller) : ItemBase(parent, st.menu) -, _dummyAction(new QAction(parent)) +, _dummyAction(Ui::CreateChild(parent)) , _st(st.menu) -, _shadowPadding(rect::m::sum::h(st.shadow.extend)) , _api(&controller->session().mtp()) , _height(rect::m::sum::v(st::groupCallJoinAsPadding) + st::groupCallJoinAsPhotoSize) { setAcceptBoth(true); - initResizeHook(parent->sizeValue()); - setClickedCallback([=] { + fitToMenuWidth(); + setActionTriggered([=] { if (const auto peer = _peer.current()) { controller->showPeerInfo(peer); } @@ -217,9 +215,7 @@ void ResolvePhoneAction::paint(Painter &p) { width()); } else { p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut); - const auto w = width() - - rect::m::sum::h(padding) - - _shadowPadding; + const auto w = width() - rect::m::sum::h(padding); _below.draw(p, Ui::Text::PaintContext{ .position = QPoint( padding.left(), @@ -367,7 +363,7 @@ void PhoneClickHandler::onClick(ClickContext context) const { }, &st::menuIconCopy); auto resolvePhoneAction = base::make_unique_q( - menu, + menu->menu(), menu->st(), phone, controller); diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp index 4f8c65e98f..81ea4e4906 100644 --- a/Telegram/SourceFiles/core/sandbox.cpp +++ b/Telegram/SourceFiles/core/sandbox.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer.h" #include "base/concurrent_timer.h" #include "base/invoke_queued.h" +#include "base/options.h" #include "base/qthelp_url.h" #include "base/qthelp_regex.h" #include "ui/ui_utility.h" @@ -35,6 +36,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace Core { +namespace { + +base::options::toggle OptionDeadlockDetector({ + .id = kOptionDeadlockDetector, + .name = "Deadlock Detector", + .description = "Check once every 30 seconds that main thread is still responsive.", + .restartRequired = true, +}); + +} // namespace + +const char kOptionDeadlockDetector[] = "deadlock-detector"; bool Sandbox::QuitOnStartRequested = false; @@ -149,12 +162,10 @@ void Sandbox::launchApplication() { } setupScreenScale(); -#ifndef _DEBUG - if (Logs::DebugEnabled()) { + if (OptionDeadlockDetector.value()) { using DeadlockDetector::PingThread; _deadlockDetector = std::make_unique(this); } -#endif // !_DEBUG _application = std::make_unique(); diff --git a/Telegram/SourceFiles/core/sandbox.h b/Telegram/SourceFiles/core/sandbox.h index 5dd5cea2bc..d881a9aa95 100644 --- a/Telegram/SourceFiles/core/sandbox.h +++ b/Telegram/SourceFiles/core/sandbox.h @@ -18,6 +18,8 @@ class QLockFile; namespace Core { +extern const char kOptionDeadlockDetector[]; + class UpdateChecker; class Application; diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp index 476c53a828..669c749959 100644 --- a/Telegram/SourceFiles/core/shortcuts.cpp +++ b/Telegram/SourceFiles/core/shortcuts.cpp @@ -136,6 +136,7 @@ const auto CommandByName = base::flat_map{ { u"show_scheduled"_q , Command::ShowScheduled }, { u"archive_chat"_q , Command::ArchiveChat }, { u"record_round"_q , Command::RecordRound }, + { u"show_admin_log"_q , Command::ShowAdminLog }, // }; diff --git a/Telegram/SourceFiles/core/shortcuts.h b/Telegram/SourceFiles/core/shortcuts.h index dffb31ec63..ff59b47658 100644 --- a/Telegram/SourceFiles/core/shortcuts.h +++ b/Telegram/SourceFiles/core/shortcuts.h @@ -80,6 +80,8 @@ enum class Command { ShowChatMenu, ShowChatPreview, + ShowAdminLog, + SupportReloadTemplates, SupportToggleMuted, SupportScrollToCurrent, diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 504f2aed0a..eb832a9f20 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 = 6003010; -constexpr auto AppVersionStr = "6.3.10"; +constexpr auto AppVersion = 6004002; +constexpr auto AppVersionStr = "6.4.2"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp b/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp index b9ac9c665f..259f38cb96 100644 --- a/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp +++ b/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp @@ -97,7 +97,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000); (data.vsuggested_post() ? *data.vsuggested_post() : MTPSuggestedPost()), - MTP_int(data.vschedule_repeat_period().value_or_empty())); + MTP_int(data.vschedule_repeat_period().value_or_empty()), + MTP_string(qs(data.vsummary_from_language().value_or_empty()))); }); } diff --git a/Telegram/SourceFiles/data/components/gift_auctions.cpp b/Telegram/SourceFiles/data/components/gift_auctions.cpp index 8ad720e550..33faaaad65 100644 --- a/Telegram/SourceFiles/data/components/gift_auctions.cpp +++ b/Telegram/SourceFiles/data/components/gift_auctions.cpp @@ -33,11 +33,85 @@ GiftAuctions::GiftAuctions(not_null session) GiftAuctions::~GiftAuctions() = default; -rpl::producer GiftAuctions::state(const QString &slug) { +void GiftAuctions::resolveSlug(const QString &slug, Fn done) { + const auto it = _slugToGiftId.find(slug); + if (it != _slugToGiftId.end()) { + done(it->second); + return; + } + + auto &request = _slugRequests[slug]; + request.callbacks.push_back(std::move(done)); + if (request.callbacks.size() > 1) { + return; + } + + _session->api().request(MTPpayments_GetStarGiftAuctionState( + MTP_inputStarGiftAuctionSlug(MTP_string(slug)), + MTP_int(0) + )).done([=](const MTPpayments_StarGiftAuctionState &result) { + const auto &data = result.data(); + + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + + auto gift = Api::FromTL(_session, data.vgift()); + if (!gift || !gift->id) { + _slugRequests.erase(slug); + return; + } + const auto giftId = gift->id; + + _slugToGiftId.emplace(slug, giftId); + + auto &entry = _map[giftId]; + if (!entry) { + entry = std::make_unique(); + } + const auto raw = entry.get(); + raw->state.gift = std::move(gift); + + applyStateResponse(raw, result); + + auto callbacks = std::move(_slugRequests[slug].callbacks); + _slugRequests.erase(slug); + for (const auto &callback : callbacks) { + callback(giftId); + } + }).fail([=] { + _slugRequests.erase(slug); + }).send(); +} + +void GiftAuctions::applyStateResponse( + not_null entry, + const MTPpayments_StarGiftAuctionState &result) { + const auto &data = result.data(); + + const auto timeout = data.vtimeout().v; + const auto ms = timeout * crl::time(1000); + entry->state.subscribedTill = ms ? (crl::now() + ms) : -1; + + const auto was = myStateKey(entry->state); + apply(entry, data.vstate()); + apply(entry, data.vuser_state()); + + if (entry->changes.has_consumers()) { + entry->changes.fire({}); + if (ms && (!_timer.isActive() || _timer.remainingTime() > ms)) { + _timer.callOnce(ms); + } + } + if (was != myStateKey(entry->state)) { + _activeChanged.fire({}); + } +} + +rpl::producer GiftAuctions::state(uint64 giftId) { return [=](auto consumer) { auto lifetime = rpl::lifetime(); - auto &entry = _map[slug]; + auto &entry = _map[giftId]; if (!entry) { entry = std::make_unique(); } @@ -52,7 +126,7 @@ rpl::producer GiftAuctions::state(const QString &slug) { || raw->state.subscribedTill >= now) { consumer.put_next_copy(raw->state); } else if (raw->state.subscribedTill >= 0) { - request(slug); + request(giftId); } return lifetime; @@ -196,7 +270,7 @@ rpl::producer GiftAuctions::hasActiveChanges() const { } bool GiftAuctions::hasActive() const { - for (const auto &[slug, entry] : _map) { + for (const auto &[giftId, entry] : _map) { if (myStateKey(entry->state)) { return true; } @@ -207,13 +281,13 @@ bool GiftAuctions::hasActive() const { void GiftAuctions::checkSubscriptions() { const auto now = crl::now(); auto next = crl::time(); - for (const auto &[slug, entry] : _map) { + for (const auto &[giftId, entry] : _map) { const auto raw = entry.get(); const auto till = raw->state.subscribedTill; if (till <= 0 || !raw->changes.has_consumers()) { continue; } else if (till <= now) { - request(slug); + request(giftId); } else { const auto timeout = till - now; if (!next || timeout < next) { @@ -249,7 +323,7 @@ auto GiftAuctions::myStateKey(const GiftAuctionState &state) const ActiveAuctions GiftAuctions::collectActive() const { auto result = ActiveAuctions(); result.list.reserve(_map.size()); - for (const auto &[slug, entry] : _map) { + for (const auto &[giftId, entry] : _map) { const auto raw = &entry->state; if (raw->gift && raw->my.date) { result.list.push_back(raw); @@ -274,23 +348,24 @@ void GiftAuctions::requestActive() { _activeRequestId = _session->api().request( MTPpayments_GetStarGiftActiveAuctions(MTP_long(countActiveHash())) ).done([=](const MTPpayments_StarGiftActiveAuctions &result) { + _activeRequestId = 0; result.match([=](const MTPDpayments_starGiftActiveAuctions &data) { const auto owner = &_session->data(); owner->processUsers(data.vusers()); owner->processChats(data.vchats()); - auto giftsFound = base::flat_set(); + auto giftsFound = base::flat_set(); const auto &list = data.vauctions().v; giftsFound.reserve(list.size()); for (const auto &auction : list) { const auto &data = auction.data(); auto gift = Api::FromTL(_session, data.vgift()); - const auto slug = gift ? gift->auctionSlug : QString(); - if (slug.isEmpty()) { + if (!gift || !gift->id) { LOG(("Api Error: Bad auction gift.")); continue; } - auto &entry = _map[slug]; + const auto giftId = gift->id; + auto &entry = _map[giftId]; if (!entry) { entry = std::make_unique(); } @@ -300,21 +375,21 @@ void GiftAuctions::requestActive() { } apply(raw, data.vstate()); apply(raw, data.vuser_state()); - giftsFound.emplace(slug); + giftsFound.emplace(giftId); } - for (const auto &[slug, entry] : _map) { + for (const auto &[giftId, entry] : _map) { const auto my = &entry->state.my; - if (my->date && !giftsFound.contains(slug)) { + if (my->date && !giftsFound.contains(giftId)) { my->to = nullptr; my->minBidAmount = 0; my->bid = 0; my->date = 0; my->returned = false; - giftsFound.emplace(slug); + giftsFound.emplace(giftId); } } - for (const auto &slug : giftsFound) { - _map[slug]->changes.fire({}); + for (const auto &giftId : giftsFound) { + _map[giftId]->changes.fire({}); } _activeChanged.fire({}); }, [](const MTPDpayments_starGiftActiveAuctionsNotModified &) { @@ -322,20 +397,27 @@ void GiftAuctions::requestActive() { }).send(); } -void GiftAuctions::request(const QString &slug) { - auto &entry = _map[slug]; - Assert(entry != nullptr); - - const auto raw = entry.get(); +void GiftAuctions::request(uint64 giftId) { + auto it = _map.find(giftId); + if (it == _map.end()) { + return; + } + const auto raw = it->second.get(); if (raw->requested) { return; } raw->requested = true; _session->api().request(MTPpayments_GetStarGiftAuctionState( - MTP_inputStarGiftAuctionSlug(MTP_string(slug)), + MTP_inputStarGiftAuction(MTP_long(giftId)), MTP_int(raw->state.version) )).done([=](const MTPpayments_StarGiftAuctionState &result) { + auto it = _map.find(giftId); + if (it == _map.end()) { + return; + } + const auto raw = it->second.get(); raw->requested = false; + const auto &data = result.data(); _session->data().processUsers(data.vusers()); @@ -345,32 +427,19 @@ void GiftAuctions::request(const QString &slug) { if (!raw->state.gift) { return; } - const auto timeout = data.vtimeout().v; - const auto ms = timeout * crl::time(1000); - raw->state.subscribedTill = ms ? (crl::now() + ms) : -1; - const auto was = myStateKey(raw->state); - apply(raw, data.vstate()); - apply(raw, data.vuser_state()); - if (raw->changes.has_consumers()) { - raw->changes.fire({}); - if (ms && (!_timer.isActive() || _timer.remainingTime() > ms)) { - _timer.callOnce(ms); - } - } - if (was != myStateKey(raw->state)) { - _activeChanged.fire({}); + applyStateResponse(raw, result); + }).fail([=] { + auto it = _map.find(giftId); + if (it != _map.end()) { + it->second->requested = false; } }).send(); } GiftAuctions::Entry *GiftAuctions::find(uint64 giftId) const { - for (const auto &[slug, entry] : _map) { - if (entry->state.gift && entry->state.gift->id == giftId) { - return entry.get(); - } - } - return nullptr; + const auto it = _map.find(giftId); + return (it != _map.end()) ? it->second.get() : nullptr; } void GiftAuctions::apply( diff --git a/Telegram/SourceFiles/data/components/gift_auctions.h b/Telegram/SourceFiles/data/components/gift_auctions.h index 48f186e54c..a569b0fd01 100644 --- a/Telegram/SourceFiles/data/components/gift_auctions.h +++ b/Telegram/SourceFiles/data/components/gift_auctions.h @@ -80,7 +80,8 @@ public: explicit GiftAuctions(not_null session); ~GiftAuctions(); - [[nodiscard]] rpl::producer state(const QString &slug); + [[nodiscard]] rpl::producer state(uint64 giftId); + void resolveSlug(const QString &slug, Fn done); void apply(const MTPDupdateStarGiftAuctionState &data); void apply(const MTPDupdateStarGiftAuctionUserState &data); @@ -117,9 +118,15 @@ private: Data::UniqueGiftAttributes lists; std::vector> waiters; }; + struct SlugRequest { + std::vector> callbacks; + }; - void request(const QString &slug); + void request(uint64 giftId); Entry *find(uint64 giftId) const; + void applyStateResponse( + not_null entry, + const MTPpayments_StarGiftAuctionState &result); void apply( not_null entry, const MTPStarGiftAuctionState &state); @@ -142,7 +149,9 @@ private: const not_null _session; base::Timer _timer; - base::flat_map> _map; + base::flat_map> _map; + base::flat_map _slugRequests; + base::flat_map _slugToGiftId; base::flat_map _attributes; rpl::event_stream<> _activeChanged; diff --git a/Telegram/SourceFiles/data/components/scheduled_messages.cpp b/Telegram/SourceFiles/data/components/scheduled_messages.cpp index ca455a0645..ba5059df46 100644 --- a/Telegram/SourceFiles/data/components/scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/components/scheduled_messages.cpp @@ -101,7 +101,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000); (data.vsuggested_post() ? *data.vsuggested_post() : MTPSuggestedPost()), - MTP_int(data.vschedule_repeat_period().value_or_empty())); + MTP_int(data.vschedule_repeat_period().value_or_empty()), + MTP_string(qs(data.vsummary_from_language().value_or_empty()))); }); } @@ -278,7 +279,8 @@ void ScheduledMessages::sendNowSimpleMessage( MTPint(), // report_delivery_until_date MTPlong(), // paid_message_stars MTPSuggestedPost(), - MTPint()), // schedule_repeat_period + MTPint(), // schedule_repeat_period + MTPstring()), // summary_from_language localFlags, NewMessageType::Unread); diff --git a/Telegram/SourceFiles/data/data_forum.cpp b/Telegram/SourceFiles/data/data_forum.cpp index a82afd9b4b..2ce2861839 100644 --- a/Telegram/SourceFiles/data/data_forum.cpp +++ b/Telegram/SourceFiles/data/data_forum.cpp @@ -366,10 +366,7 @@ void Forum::applyReceivedTopics( const auto rootId = topic.match([&](const auto &data) { return data.vid().v; }); - _staleRootIds.remove(rootId); - topic.match([&](const MTPDforumTopicDeleted &data) { - applyTopicDeleted(rootId); - }, [&](const MTPDforumTopic &data) { + const auto apply = [&](const MTPDforumTopic *fields = nullptr) { _topicsDeleted.remove(rootId); const auto i = _topics.find(rootId); const auto creating = (i == end(_topics)); @@ -379,7 +376,9 @@ void Forum::applyReceivedTopics( std::make_unique(this, rootId) ).first->second.get() : i->second.get(); - raw->applyTopic(data); + if (fields) { + raw->applyTopic(*fields); + } if (creating) { if (const auto last = _history->chatListMessage() ; last && last->topicRootId() == rootId) { @@ -390,6 +389,19 @@ void Forum::applyReceivedTopics( if (callback) { callback(raw); } + }; + + _staleRootIds.remove(rootId); + topic.match([&](const MTPDforumTopicDeleted &data) { + if (rootId != ForumTopic::kGeneralId) { + applyTopicDeleted(rootId); + } else { + // We shouldn't delete general topic in any case. + // Here this happens in bot forums, for example. + apply(); + } + }, [&](const MTPDforumTopic &data) { + apply(&data); }); } } diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 4b5ae3ec78..8e613a2851 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_unique_gift.h" #include "history/view/media/history_view_userpic_suggestion.h" #include "dialogs/ui/dialogs_message_view.h" +#include "ui/boxes/emoji_stake_box.h" #include "ui/image/image.h" #include "ui/effects/spoiler_mess.h" #include "ui/text/format_song_document_name.h" @@ -686,6 +687,10 @@ const GiveawayResults *Media::giveawayResults() const { return nullptr; } +DiceGameOutcome Media::diceGameOutcome() const { + return {}; +} + bool Media::uploading() const { return false; } @@ -2420,14 +2425,19 @@ std::unique_ptr MediaTodoList::createView( replacing); } -MediaDice::MediaDice(not_null parent, QString emoji, int value) +MediaDice::MediaDice( + not_null parent, + DiceGameOutcome outcome, + QString emoji, + int value) : Media(parent) +, _outcome(outcome) , _emoji(emoji) , _value(value) { } std::unique_ptr MediaDice::clone(not_null parent) { - return std::make_unique(parent, _emoji, _value); + return std::make_unique(parent, _outcome, _emoji, _value); } QString MediaDice::emoji() const { @@ -2438,6 +2448,10 @@ int MediaDice::value() const { return _value; } +DiceGameOutcome MediaDice::diceGameOutcome() const { + return _outcome; +} + bool MediaDice::allowsRevoke(TimeId now) const { const auto peer = parent()->history()->peer; if (peer->isSelf() || !peer->isUser()) { @@ -2470,8 +2484,19 @@ bool MediaDice::updateSentMedia(const MTPMessageMedia &media) { if (media.type() != mtpc_messageMediaDice) { return false; } - _value = media.c_messageMediaDice().vvalue().v; - parent()->history()->owner().requestItemRepaint(parent()); + const auto &data = media.c_messageMediaDice(); + _value = data.vvalue().v; + if (const auto outcome = data.vgame_outcome()) { + const auto &data = outcome->data(); + _outcome = Data::DiceGameOutcome{ + .nanoTon = int64(data.vton_amount().v), + .stakeNanoTon = int64(data.vstake_ton_amount().v), + .seed = data.vseed().v, + }; + } else { + _outcome = {}; + } + parent()->history()->owner().notifyItemDataChange(parent()); return true; } @@ -2504,39 +2529,83 @@ ClickHandlerPtr MediaDice::MakeHandler( } }; return std::make_shared([=](ClickContext context) { - auto config = Ui::Toast::Config{ - .text = { tr::lng_about_random(tr::now, lt_emoji, emoji) }, - .st = &st::historyDiceToast, - .duration = Ui::Toast::kDefaultDuration * 2, - }; - if (CanSend(history->peer, ChatRestriction::SendOther)) { - auto link = tr::link(tr::lng_about_random_send(tr::now)); - link.entities.push_back( - EntityInText(EntityType::Semibold, 0, link.text.size())); - config.text.append(' ').append(std::move(link)); - config.filter = crl::guard(&history->session(), [=]( - const ClickHandlerPtr &handler, - Qt::MouseButton button) { - if (button == Qt::LeftButton && !ShownToast.empty()) { - auto message = Api::MessageToSend( - Api::SendAction(history)); - message.action.clearDraft = false; - message.textWithTags.text = emoji; - - Api::SendDice(message); - HideExisting(); - } - return false; - }); - } - - HideExisting(); + const auto found = Ui::Emoji::Find(emoji); + const auto id = found ? found->id() : QString(); + const auto game = (id == QString::fromUtf8("\xf0\x9f\x8e\xb2")); const auto my = context.other.value(); const auto weak = my.sessionWindow; - if (const auto strong = weak.get()) { - ShownToast = strong->showToast(std::move(config)); + const auto sendWith = [=](const QByteArray &hash, int64 nanoTon) { + auto message = Api::MessageToSend( + Api::SendAction(history)); + message.textWithTags.text = emoji; + + auto &action = message.action; + action.clearDraft = false; + + auto &options = action.options; + options.stakeNanoTon = nanoTon; + options.stakeSeedHash = hash; + + Api::SendDice(message); + + HideExisting(); + }; + const auto sendAllowed = CanSend( + history->peer, + ChatRestriction::SendOther); + const auto showToast = [=](Ui::Toast::Config &&config) { + HideExisting(); + if (const auto strong = weak.get()) { + ShownToast = strong->showToast(std::move(config)); + } else { + ShownToast = Ui::Toast::Show(std::move(config)); + } + }; + const auto showSimple = [=] { + auto config = Ui::Toast::Config{ + .text = { tr::lng_about_random(tr::now, lt_emoji, emoji) }, + .st = &st::historyDiceToast, + .duration = Ui::Toast::kDefaultDuration * 2, + }; + if (sendAllowed) { + auto link = tr::link(tr::lng_about_random_send(tr::now)); + link.entities.push_back( + EntityInText(EntityType::Semibold, 0, link.text.size())); + config.text.append(' ').append(std::move(link)); + config.filter = crl::guard(&history->session(), [=]( + const ClickHandlerPtr &handler, + Qt::MouseButton button) { + if (button == Qt::LeftButton && !ShownToast.empty()) { + sendWith(QByteArray(), 0); + } + return false; + }); + } + showToast(std::move(config)); + }; + if (!game || !sendAllowed) { + showSimple(); } else { - ShownToast = Ui::Toast::Show(std::move(config)); + const auto pack = &history->session().diceStickersPacks(); + pack->resolveGameOptions([=]( + const Data::DiceGameOptions &options) { + const auto window = weak.get(); + const auto seedHash = options.seedHash; + const auto sendWithStake = [=](int64 stakeNanoTon) { + sendWith(seedHash, stakeNanoTon); + }; + if (!options || !window) { + showSimple(); + } else { + showToast(Ui::MakeEmojiGameStakeToast(window->uiShow(), { + .session = &window->session(), + .currentStake = options.previousSteakNanoTon, + .milliRewards = options.milliRewards, + .jackpotMilliReward = options.jackpotMilliReward, + .submit = sendWithStake, + })); + } + }); } }); } diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index d0d60fc200..93a991bfc3 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -133,6 +133,29 @@ struct GiveawayResults { bool all = false; }; +struct DiceGameOptions { + QByteArray seedHash; + int64 previousSteakNanoTon = 0; + std::array milliRewards; + int jackpotMilliReward = 0; + int currentStreak = 0; + int playsLeft = 0; + + explicit operator bool() const { + return !seedHash.isEmpty(); + } +}; + +struct DiceGameOutcome { + int64 nanoTon = 0; + int64 stakeNanoTon = 0; + QByteArray seed; + + explicit operator bool() const { + return stakeNanoTon != 0; + } +}; + enum class GiftType : uchar { Premium, // count - days Credits, // count - credits @@ -218,6 +241,7 @@ public: virtual bool storyMention() const; virtual const GiveawayStart *giveawayStart() const; virtual const GiveawayResults *giveawayResults() const; + virtual DiceGameOutcome diceGameOutcome() const; virtual bool uploading() const; virtual Storage::SharedMediaTypesMask sharedMediaTypes() const; @@ -655,7 +679,11 @@ private: class MediaDice final : public Media { public: - MediaDice(not_null parent, QString emoji, int value); + MediaDice( + not_null parent, + DiceGameOutcome outcome, + QString emoji, + int value); std::unique_ptr clone(not_null parent) override; @@ -667,6 +695,7 @@ public: QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; bool forceForwardedInfo() const override; + DiceGameOutcome diceGameOutcome() const override; bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; @@ -681,6 +710,7 @@ public: const QString &emoji); private: + DiceGameOutcome _outcome; QString _emoji; int _value = 0; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 7a059ee678..f24077e7eb 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -654,6 +654,12 @@ not_null Session::processUser(const MTPUser &data) { result->setUnavailableReasons(Data::UnavailableReason::Extract( data.vrestriction_reason())); } + + if (const auto accessHash = data.vaccess_hash()) { + if (!minimal || !result->accessHash()) { + result->setAccessHash(accessHash->v); + } + } if (data.is_deleted()) { if (!result->phone().isEmpty()) { result->setPhone(QString()); @@ -663,12 +669,6 @@ not_null Session::processUser(const MTPUser &data) { result->setPhoto(MTP_userProfilePhotoEmpty()); status = &emptyStatus; } else { - if (const auto accessHash = data.vaccess_hash()) { - if (!minimal || !result->accessHash()) { - result->setAccessHash(accessHash->v); - } - } - // apply first_name and last_name from minimal user only if we don't have // local values for first name and last name already, otherwise skip const auto noLocalName = result->firstName.isEmpty() @@ -1959,14 +1959,14 @@ rpl::producer Session::itemIdChanged() const { return _itemIdChanges.events(); } -void Session::requestItemRepaint(not_null item) { +void Session::requestItemRepaint(not_null item, QRect r) { _itemRepaintRequest.fire_copy(item); auto repaintGroupLeader = false; auto repaintView = [&](not_null view) { if (view->isHiddenByGroup()) { repaintGroupLeader = true; } else { - requestViewRepaint(view); + requestViewRepaint(view, r); } }; enumerateItemViews(item, repaintView); @@ -1998,11 +1998,11 @@ rpl::producer> Session::itemRepaintRequest() const return _itemRepaintRequest.events(); } -void Session::requestViewRepaint(not_null view) { - _viewRepaintRequest.fire_copy(view); +void Session::requestViewRepaint(not_null view, QRect r) { + _viewRepaintRequest.fire_copy({ view, r }); } -rpl::producer> Session::viewRepaintRequest() const { +rpl::producer Session::viewRepaintRequest() const { return _viewRepaintRequest.events(); } @@ -2027,6 +2027,14 @@ rpl::producer> Session::viewResizeRequest() const { return _viewResizeRequest.events(); } +void Session::requestItemShowHighlight(not_null item) { + _itemShowHighlightRequest.fire_copy(item); +} + +rpl::producer> Session::itemShowHighlightRequest() const { + return _itemShowHighlightRequest.events(); +} + void Session::requestItemViewRefresh(not_null item) { if (const auto view = item->mainView()) { notifyHistoryChangeDelayed(item->history()); @@ -5192,7 +5200,8 @@ void Session::insertCheckedServiceNotification( MTPint(), // report_delivery_until_date MTPlong(), // paid_message_stars MTPSuggestedPost(), - MTPint()), // schedule_repeat_period + MTPint(), // schedule_repeat_period + MTPstring()), // summary_from_language localFlags, NewMessageType::Unread); } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 41777192a4..eb460302b6 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -140,6 +140,11 @@ struct RecentJoinChat { PeerId joinedPeerId = 0; }; +struct RequestViewRepaint { + not_null view; + QRect rect; +}; + class Session final { public: using ViewElement = HistoryView::Element; @@ -368,14 +373,16 @@ public: [[nodiscard]] rpl::producer giftsUpdates() const; void notifyGiftAuctionGot(GiftAuctionGot &&update); [[nodiscard]] rpl::producer giftAuctionGots() const; - void requestItemRepaint(not_null item); + void requestItemRepaint(not_null item, QRect r = QRect()); [[nodiscard]] rpl::producer> itemRepaintRequest() const; - void requestViewRepaint(not_null view); - [[nodiscard]] rpl::producer> viewRepaintRequest() const; + void requestViewRepaint(not_null view, QRect r = QRect()); + [[nodiscard]] rpl::producer viewRepaintRequest() const; void requestItemResize(not_null item); [[nodiscard]] rpl::producer> itemResizeRequest() const; void requestViewResize(not_null view); [[nodiscard]] rpl::producer> viewResizeRequest() const; + void requestItemShowHighlight(not_null item); + [[nodiscard]] rpl::producer> itemShowHighlightRequest() const; void requestItemViewRefresh(not_null item); [[nodiscard]] rpl::producer> itemViewRefreshRequest() const; void requestItemTextRefresh(not_null item); @@ -1101,9 +1108,10 @@ private: rpl::event_stream _giftsUpdates; rpl::event_stream _giftAuctionGots; rpl::event_stream> _itemRepaintRequest; - rpl::event_stream> _viewRepaintRequest; + rpl::event_stream _viewRepaintRequest; rpl::event_stream> _itemResizeRequest; rpl::event_stream> _viewResizeRequest; + rpl::event_stream> _itemShowHighlightRequest; rpl::event_stream> _itemViewRefreshRequest; rpl::event_stream> _itemTextRefreshRequest; rpl::event_stream> _itemDataChanges; diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 35b790f177..543d77aa94 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -361,6 +361,8 @@ enum class MessageFlag : uint64 { SavedMusicItem = (1ULL << 55), HasHiddenLinks = (1ULL << 56), + HasSummaryEntry = (1ULL << 57), + CanBeSummarized = (1ULL << 58), AyuNoForwards = (1ULL << 63), }; diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp index 2678388670..02cb41bc34 100644 --- a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp +++ b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp @@ -132,9 +132,9 @@ void NotifySettings::apply( }, [](const MTPDinputPeerChannel &data) { return peerFromChannel(data.vchannel_id()); }, [](const MTPDinputPeerUserFromMessage &data) -> PeerId { - Unexpected("From message peer in NotifySettings::apply."); + return peerFromUser(data.vuser_id()); }, [](const MTPDinputPeerChannelFromMessage &data) -> PeerId { - Unexpected("From message peer in NotifySettings::apply."); + return peerFromChannel(data.vchannel_id()); }, [](const MTPDinputPeerEmpty &) -> PeerId { Unexpected("Empty peer in NotifySettings::apply."); }); diff --git a/Telegram/SourceFiles/data/stickers/data_stickers.cpp b/Telegram/SourceFiles/data/stickers/data_stickers.cpp index 86216b878a..af551ef4c4 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers.cpp +++ b/Telegram/SourceFiles/data/stickers/data_stickers.cpp @@ -188,6 +188,14 @@ rpl::producer Stickers::stickerSetInstalled() const { return _stickerSetInstalled.events(); } +void Stickers::notifyGifWithCaptionSent() { + _gifWithCaptionSent.fire({}); +} + +rpl::producer<> Stickers::gifWithCaptionSent() const { + return _gifWithCaptionSent.events(); +} + void Stickers::notifyEmojiSetInstalled(uint64 setId) { _emojiSetInstalled.fire(std::move(setId)); } diff --git a/Telegram/SourceFiles/data/stickers/data_stickers.h b/Telegram/SourceFiles/data/stickers/data_stickers.h index cc2c7fbe4f..602b2b4c53 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers.h +++ b/Telegram/SourceFiles/data/stickers/data_stickers.h @@ -78,6 +78,8 @@ public: [[nodiscard]] rpl::producer<> savedGifsUpdated() const; void notifyStickerSetInstalled(uint64 setId); [[nodiscard]] rpl::producer stickerSetInstalled() const; + void notifyGifWithCaptionSent(); + [[nodiscard]] rpl::producer<> gifWithCaptionSent() const; void notifyEmojiSetInstalled(uint64 setId); [[nodiscard]] rpl::producer emojiSetInstalled() const; @@ -306,6 +308,7 @@ private: rpl::event_stream _recentUpdated; rpl::event_stream<> _savedGifsUpdated; rpl::event_stream _stickerSetInstalled; + rpl::event_stream<> _gifWithCaptionSent; rpl::event_stream _emojiSetInstalled; crl::time _lastUpdate = 0; crl::time _lastRecentUpdate = 0; diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp index b8d344bb47..d62aef34a0 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -505,6 +505,9 @@ rpl::producer*> TopBarSuggestionValue( widget->raise(); }, widget->lifetime()); for (const auto &id : users) { + if (s->inRow.size() >= 3) { + break; + } if (const auto user = session->data().user(id)) { s->inRow.push_back({ .peer = user }); } diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp index 834e3f2f71..e426f453b9 100644 --- a/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp @@ -95,12 +95,10 @@ Action::Action( , _height(st::dialogsSearchInHeight) , _icon(std::move(icon)) , _checked(chosen) { - const auto parent = parentMenu->menu(); - _text.setText(st::semiboldTextStyle, label); _icon->subscribeToUpdates([=] { update(); }); - initResizeHook(parent->sizeValue()); + fitToMenuWidth(); resolveMinWidth(); paintRequest( @@ -332,7 +330,7 @@ void ChatSearchIn::showMenu() { tab.icon, TabLabel(value, _peerTabType), (value == active)); - action->setClickedCallback([=] { + action->setActionTriggered([=] { _active = value; }); _menu->addAction(std::move(action)); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp index 9286e40cdb..67e3d73e58 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp @@ -209,7 +209,7 @@ void TopicsView::paint( rect.setWidth(rect.width() - _lastTopicJumpGeometry.rightCut); auto skipBig = _jumpToTopic && !context.active; if (_titles.empty()) { - const auto text = (_monoforum && _allLoaded) + const auto text = _allLoaded ? tr::lng_filters_no_chats(tr::now) : tr::lng_contacts_loading(tr::now); p.drawText( diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index 7e72df0c95..e0b7108cd6 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "api/api_chat_participants.h" #include "api/api_attached_stickers.h" +#include "api/api_report.h" #include "window/window_session_controller.h" #include "main/main_session.h" #include "main/main_session_settings.h" @@ -273,9 +274,9 @@ InnerWidget::InnerWidget( setMouseTracking(true); _scrollDateHideTimer.setCallback([=] { scrollDateHideByTimer(); }); session().data().viewRepaintRequest( - ) | rpl::on_next([=](auto view) { - if (myView(view)) { - repaintItem(view); + ) | rpl::on_next([=](Data::RequestViewRepaint data) { + if (myView(data.view)) { + repaintItem(data.view, data.rect); } }, lifetime()); session().data().viewResizeRequest( @@ -621,8 +622,9 @@ void InnerWidget::updateEmptyText() { } QString InnerWidget::tooltipText() const { - if (_mouseCursorState == CursorState::Date - && _mouseAction == MouseAction::None) { + if (_mouseAction == MouseAction::None + && (_mouseCursorState == CursorState::Date + || _mouseCursorState == CursorState::LogAdminService)) { if (const auto view = Element::Hovered()) { auto dateText = HistoryView::DateTooltipText(view); @@ -720,7 +722,7 @@ bool InnerWidget::elementAnimationsPaused() { } bool InnerWidget::elementHideReply(not_null view) { - return true; + return false; } bool InnerWidget::elementShownUnread(not_null view) { @@ -916,6 +918,7 @@ void InnerWidget::addEvents(Direction direction, const QVectordata(), item.get()); - if (rememberRealMsgId && realId) { - _antiSpamValidator.addEventMsgId( - item->data()->fullId(), - realId); + if (realId) { + if (rememberRealMsgId) { + _antiSpamValidator.addEventMsgId( + item->data()->fullId(), + realId); + } + if (canRestrict) { + _realIdsForReport[item->data()->fullId()] = realId; + } } addToItems.push_back(std::move(item)); ++count; @@ -1048,6 +1056,7 @@ Ui::ChatPaintContext InnerWidget::preparePaintContext(QRect clip) const { .visibleAreaPositionGlobal = mapToGlobal(QPoint(0, _visibleTop)), .visibleAreaTop = _visibleTop, .visibleAreaWidth = width(), + .visibleAreaHeight = _visibleBottom - _visibleTop, }); } @@ -1382,7 +1391,15 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } } else if (fromId) { // suggest to block if (const auto participant = session().data().peer(fromId)) { - suggestRestrictParticipant(participant); + const auto item = view ? view->data().get() : nullptr; + auto realId = FullMsgId(); + if (const auto itemId = item ? item->fullId() : FullMsgId()) { + const auto it = _realIdsForReport.find(itemId); + if (it != _realIdsForReport.end()) { + realId = FullMsgId(_channel->id, it->second); + } + } + suggestRestrictParticipant(participant, realId); } } else { // maybe cursor on some text history item? const auto item = view ? view->data().get() : nullptr; @@ -1532,12 +1549,11 @@ void InnerWidget::copyContextText(FullMsgId itemId) { } void InnerWidget::suggestRestrictParticipant( - not_null participant) { + not_null participant, + FullMsgId realId) { Expects(_menu != nullptr); - if (!_channel->isMegagroup() - || !_channel->canBanMembers() - || _admins.empty()) { + if (!canRestrict()) { return; } if (ranges::contains(_admins, participant)) { @@ -1640,12 +1656,26 @@ void InnerWidget::suggestRestrictParticipant( participant, { _channel->restrictions(), 0 }); }; - Ui::Menu::CreateAddActionCallback(_menu)({ + const auto addAction = Ui::Menu::CreateAddActionCallback(_menu); + addAction({ .text = tr::lng_context_ban_user(tr::now), - .handler = std::move(handler), + .handler = handler, .icon = &st::menuIconBlockAttention, .isAttention = true, }); + + if (realId) { + addAction({ + .text = tr::lng_report_and_ban(tr::now), + .handler = [=, show = _controller->uiShow()] { + Api::ReportSpam(participant, { realId }); + handler(); + show->showToast(tr::lng_report_spam_done(tr::now)); + }, + .icon = &st::menuIconReportAttention, + .isAttention = true, + }); + } } } @@ -1682,6 +1712,12 @@ void InnerWidget::restrictParticipantDone( checkPreloadMore(); } +bool InnerWidget::canRestrict() const { + return _channel->isMegagroup() + && _channel->canBanMembers() + && !_admins.empty(); +} + void InnerWidget::mousePressEvent(QMouseEvent *e) { if (_menu) { e->accept(); @@ -1918,6 +1954,9 @@ void InnerWidget::updateSelected() { } dragState = view->textState(itemPoint, request); lnkhost = view; + if (item->isService()) { + dragState.cursor = CursorState::LogAdminService; + } if (!dragState.link && itemPoint.x() >= st::historyPhotoLeft && itemPoint.x() < st::historyPhotoLeft + st::msgPhotoSize) { if (!item->isService() && view->hasFromPhoto()) { enumerateUserpics([&](not_null view, int userpicTop) { @@ -1943,7 +1982,8 @@ void InnerWidget::updateSelected() { } if (dragState.link || dragState.cursor == CursorState::Date - || dragState.cursor == CursorState::Forwarded) { + || dragState.cursor == CursorState::Forwarded + || dragState.cursor == CursorState::LogAdminService) { Ui::Tooltip::Show(350, this); } @@ -2116,6 +2156,17 @@ void InnerWidget::repaintItem(const Element *view) { update(0, top + range.top, width(), range.height); } +void InnerWidget::repaintItem(const Element *view, QRect rect) { + if (rect.isNull()) { + return repaintItem(view); + } + if (!view) { + return; + } + const auto top = itemTop(view); + update(rect.translated(0, top)); +} + void InnerWidget::resizeItem(not_null view) { updateSize(); } diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h index bd537fe141..15d5b5f02d 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -198,6 +198,7 @@ private: void performDrag(); int itemTop(not_null view) const; void repaintItem(const Element *view); + void repaintItem(const Element *view, QRect rect); void refreshItem(not_null view); void resizeItem(not_null view); QPoint mapPointToItem(QPoint point, const Element *view) const; @@ -213,7 +214,9 @@ private: void copyContextText(FullMsgId itemId); void copySelectedText(); TextForMimeData getSelectedText() const; - void suggestRestrictParticipant(not_null participant); + void suggestRestrictParticipant( + not_null participant, + FullMsgId realId); void restrictParticipant( not_null participant, ChatRestrictionsInfo oldRights, @@ -221,6 +224,7 @@ private: void restrictParticipantDone( not_null participant, ChatRestrictionsInfo rights); + [[nodiscard]] bool canRestrict() const; void requestAdmins(); void checkPreloadMore(); @@ -286,6 +290,7 @@ private: base::flat_set _animatedStickersPlayed; base::flat_map, Ui::PeerUserpicView> _userpics; base::flat_map, Ui::PeerUserpicView> _userpicsCache; + base::flat_map _realIdsForReport; int _itemsTop = 0; int _itemsWidth = 0; int _itemsHeight = 0; diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index cc054c1693..e8b30c43ae 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -87,7 +87,7 @@ std::optional PrepareLogReply( } return header->match([&](const MTPDmessageReplyHeader &data) -> std::optional { - if (data.vreply_to_peer_id()) { + if (data.vreply_to_peer_id() || data.vreply_to_msg_id()) { return *header; } else if (data.is_forum_topic()) { const auto topId = data.vreply_to_top_id().value_or( @@ -165,7 +165,8 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) { | Flag::f_ttl_period | Flag::f_factcheck | Flag::f_report_delivery_until_date - | Flag::f_suggested_post; + | Flag::f_suggested_post + | Flag::f_summary_from_language; return MTP_message( MTP_flags(data.vflags().v & ~removeFlags), data.vid(), @@ -199,7 +200,8 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) { MTPint(), // report_delivery_until_date MTP_long(data.vpaid_message_stars().value_or_empty()), MTPSuggestedPost(), - MTPint()); // schedule_repeat_period + MTPint(), // schedule_repeat_period + MTPstring()); // summary_from_language }); } @@ -352,10 +354,23 @@ TextWithEntities GeneratePermissionsChangeText( const auto prevFlags = prevRights.flags; const auto indefinitely = ChannelData::IsRestrictedForever(newUntil); if (newFlags & Flag::ViewMessages) { - return tr::lng_admin_log_banned( + if (indefinitely) { + return tr::lng_admin_log_banned( + tr::now, + lt_user, + user, + tr::marked); + } + return tr::lng_admin_log_banned_until( tr::now, lt_user, user, + lt_until, + tr::lng_admin_log_restricted_until( + tr::now, + lt_date, + { langDateTime(base::unixtime::parse(newUntil)) }, + tr::marked), tr::marked); } else if (newFlags == 0 && (prevFlags & Flag::ViewMessages) diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp index 4def6c19ce..30a79c4984 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp @@ -11,19 +11,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/admin_log/history_admin_log_filter.h" #include "profile/profile_back_button.h" #include "core/shortcuts.h" +#include "info/profile/info_profile_values.h" #include "ui/chat/chat_style.h" #include "ui/controls/swipe_handler.h" #include "ui/effects/animations.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" #include "ui/widgets/buttons.h" +#include "ui/controls/userpic_button.h" #include "ui/widgets/fields/input_field.h" #include "ui/ui_utility.h" -#include "mainwidget.h" -#include "mainwindow.h" #include "apiwrap.h" #include "window/themes/window_theme.h" -#include "window/window_adaptive.h" #include "window/window_session_controller.h" #include "ui/boxes/confirm_box.h" #include "base/timer.h" @@ -44,7 +43,6 @@ public: not_null controller, not_null channel); - [[nodiscard]] rpl::producer<> showFilterRequests() const; [[nodiscard]] rpl::producer<> searchCancelRequests() const; [[nodiscard]] rpl::producer searchRequests() const; @@ -81,7 +79,6 @@ private: object_ptr _backButton; object_ptr _search; object_ptr _cancel; - object_ptr _filter; Ui::Animations::Simple _searchShownAnimation; bool _searchShown = false; @@ -114,26 +111,18 @@ FixedBar::FixedBar( , _controller(controller) , _channel(channel) , _field(this, st::defaultMultiSelectSearchField, tr::lng_dlg_filter()) -, _backButton( - this, - &controller->session(), - tr::lng_admin_log_title_all(tr::now), - controller->adaptive().oneColumnValue()) +, _backButton(this) , _search(this, st::topBarSearch) -, _cancel(this, st::historyAdminLogCancelSearch) -, _filter(this, tr::lng_admin_log_filter(), st::topBarButton) { +, _cancel(this, st::historyAdminLogCancelSearch) { _backButton->moveToLeft(0, 0); _backButton->setClickedCallback([=] { goBack(); }); _search->setClickedCallback([=] { showSearch(); }); _cancel->setClickedCallback([=] { cancelSearch(); }); _field->hide(); - _filter->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); - _field->cancelled( - ) | rpl::on_next([=] { + _field->cancelled() | rpl::on_next([=] { cancelSearch(); }, _field->lifetime()); - _field->changes( - ) | rpl::on_next([=] { + _field->changes() | rpl::on_next([=] { searchUpdated(); }, _field->lifetime()); _field->submits( @@ -141,6 +130,15 @@ FixedBar::FixedBar( _searchTimer.setCallback([=] { applySearch(); }); _cancel->hide(anim::type::instant); + + _backButton->setSubtext(tr::lng_admin_log_title_all(tr::now)); + Info::Profile::NameValue(channel) | rpl::on_next([=](QString name) { + _backButton->setText(name); + }, _backButton->lifetime()); + _backButton->setWidget(Ui::CreateChild( + _backButton.get(), + channel, + st::topBarInfoButton)); } void FixedBar::applyFilter(const FilterValue &value) { @@ -187,6 +185,7 @@ void FixedBar::searchAnimationCallback() { _searchShown ? &st::topBarBg : nullptr); _search->setCursor( _searchShown ? style::cur_default : style::cur_pointer); + _backButton->setOpacity(1.); } resizeToWidth(width()); } @@ -216,31 +215,35 @@ void FixedBar::applySearch() { } int FixedBar::resizeGetHeight(int newWidth) { - auto filterLeft = newWidth - _filter->width(); - _filter->moveToLeft(filterLeft, 0); - - auto cancelLeft = filterLeft - _cancel->width(); - _cancel->moveToLeft(cancelLeft, 0); - - auto searchShownLeft = st::topBarArrowPadding.left(); - auto searchHiddenLeft = filterLeft - _search->width(); - auto searchShown = _searchShownAnimation.value(_searchShown ? 1. : 0.); - auto searchCurrentLeft = anim::interpolate(searchHiddenLeft, searchShownLeft, searchShown); + const auto offset = st::historySendRight + st::lineWidth; + const auto searchShownLeft = st::topBarArrowPadding.left(); + const auto searchHiddenLeft = newWidth - _search->width() - offset; + const auto searchShown = _searchShownAnimation.value(_searchShown + ? 1. + : 0.); + const auto searchCurrentLeft = anim::interpolate( + searchHiddenLeft, + searchShownLeft, + searchShown); _search->moveToLeft(searchCurrentLeft, 0); + _backButton->setOpacity(1. - searchShown); _backButton->resizeToWidth(searchCurrentLeft); _backButton->moveToLeft(0, 0); - auto newHeight = _backButton->height(); - auto fieldLeft = searchShownLeft + _search->width(); - _field->setGeometryToLeft(fieldLeft, st::historyAdminLogSearchTop, cancelLeft - fieldLeft, _field->height()); + const auto cancelLeft = newWidth - _cancel->width() - offset; + _cancel->moveToLeft(cancelLeft, 0); + + const auto newHeight = _backButton->height(); + const auto fieldLeft = searchShownLeft + _search->width(); + _field->setGeometryToLeft( + fieldLeft, + st::historyAdminLogSearchTop, + cancelLeft - fieldLeft, + _field->height()); return newHeight; } -rpl::producer<> FixedBar::showFilterRequests() const { - return _filter->clicks() | rpl::to_empty; -} - rpl::producer<> FixedBar::searchCancelRequests() const { return _searchCancelRequests.events(); } @@ -289,16 +292,13 @@ Widget::Widget( , _scroll(this, st::historyScroll, false) , _fixedBar(this, controller, channel) , _fixedBarShadow(this) -, _whatIsThis( - this, - tr::lng_admin_log_about(tr::now), - st::historyComposeButton) { +, _settingsFilter( + this, + tr::lng_menu_settings(tr::now), + st::historyComposeButton) +, _whatIsThis(this, st::historyAdminLogWhatIsThis) { _fixedBar->move(0, 0); _fixedBar->resizeToWidth(width()); - _fixedBar->showFilterRequests( - ) | rpl::on_next([=] { - showFilter(); - }, lifetime()); _fixedBar->searchCancelRequests( ) | rpl::on_next([=] { setInnerFocus(); @@ -309,6 +309,7 @@ Widget::Widget( }, lifetime()); _fixedBar->show(); + _whatIsThis->raise(); _fixedBarShadow->raise(); controller->adaptive().value( @@ -316,7 +317,8 @@ Widget::Widget( updateAdaptiveLayout(); }, lifetime()); - _inner = _scroll->setOwnedWidget(object_ptr(this, controller, channel)); + _inner = _scroll->setOwnedWidget( + object_ptr(this, controller, channel)); _inner->showSearchSignal( ) | rpl::on_next([=] { _fixedBar->showSearch(); @@ -332,11 +334,13 @@ Widget::Widget( _scroll->move(0, _fixedBar->height()); _scroll->show(); - _scroll->scrolls( - ) | rpl::on_next([=] { + _scroll->scrolls() | rpl::on_next([=] { onScroll(); }, lifetime()); + _settingsFilter->setClickedCallback([=] { + showFilter(); + }); _whatIsThis->setClickedCallback([=] { controller->show(Ui::MakeInformBox(channel->isMegagroup() ? tr::lng_admin_log_about_text() @@ -373,10 +377,15 @@ Dialogs::RowDescriptor Widget::activeChat() const { }; } -QPixmap Widget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { - if (params.withTopBarShadow) _fixedBarShadow->hide(); +QPixmap Widget::grabForShowAnimation( + const Window::SectionSlideParams ¶ms) { + if (params.withTopBarShadow) { + _fixedBarShadow->hide(); + } auto result = Ui::GrabWidget(this); - if (params.withTopBarShadow) _fixedBarShadow->show(); + if (params.withTopBarShadow) { + _fixedBarShadow->show(); + } return result; } @@ -398,7 +407,9 @@ bool Widget::showInternal( return false; } -void Widget::setInternalState(const QRect &geometry, not_null memento) { +void Widget::setInternalState( + const QRect &geometry, + not_null memento) { setGeometry(geometry); Ui::SendPendingMoveResizeEvents(this); restoreState(memento); @@ -481,15 +492,17 @@ void Widget::resizeEvent(QResizeEvent *e) { return; } - auto contentWidth = width(); + const auto contentWidth = width(); - auto newScrollTop = _scroll->scrollTop() + topDelta(); + const auto newScrollTop = _scroll->scrollTop() + topDelta(); _fixedBar->resizeToWidth(contentWidth); _fixedBarShadow->resize(contentWidth, st::lineWidth); - auto bottom = height(); - auto scrollHeight = bottom - _fixedBar->height() - _whatIsThis->height(); - auto scrollSize = QSize(contentWidth, scrollHeight); + const auto bottom = height(); + const auto scrollHeight = bottom + - _fixedBar->height() + - _settingsFilter->height(); + const auto scrollSize = QSize(contentWidth, scrollHeight); if (_scroll->size() != scrollSize) { _scroll->resize(scrollSize); _inner->resizeToWidth(scrollSize.width(), _scroll->height()); @@ -503,8 +516,15 @@ void Widget::resizeEvent(QResizeEvent *e) { auto scrollTop = _scroll->scrollTop(); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); } - auto fullWidthButtonRect = myrtlrect(0, bottom - _whatIsThis->height(), contentWidth, _whatIsThis->height()); - _whatIsThis->setGeometry(fullWidthButtonRect); + const auto fullWidthButtonRect = myrtlrect( + 0, + bottom - _settingsFilter->height(), + contentWidth, + _settingsFilter->height()); + _settingsFilter->setGeometry(fullWidthButtonRect); + _whatIsThis->moveToRight( + st::historySendRight, + bottom - _whatIsThis->height()); } void Widget::paintEvent(QPaintEvent *e) { @@ -533,7 +553,9 @@ void Widget::onScroll() { void Widget::showAnimatedHook( const Window::SectionSlideParams ¶ms) { _fixedBar->setAnimatingMode(true); - if (params.withTopBarShadow) _fixedBarShadow->show(); + if (params.withTopBarShadow) { + _fixedBarShadow->show(); + } } void Widget::showFinishedHook() { diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h index 9643694f82..3cff97fe47 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h @@ -18,6 +18,7 @@ namespace Ui { class ScrollArea; class PlainShadow; class FlatButton; +class IconButton; } // namespace Ui namespace Profile { @@ -81,7 +82,8 @@ private: QPointer _inner; object_ptr _fixedBar; object_ptr _fixedBarShadow; - object_ptr _whatIsThis; + object_ptr _settingsFilter; + object_ptr _whatIsThis; Ui::Controls::SwipeBackResult _swipeBackData; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 574342cb23..dbe414a4e9 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -412,8 +412,8 @@ HistoryInner::HistoryInner( mouseActionCancel(); }, lifetime()); session().data().viewRepaintRequest( - ) | rpl::on_next([this](not_null view) { - repaintItem(view); + ) | rpl::on_next([this](Data::RequestViewRepaint data) { + repaintItem(data.view, data.rect); }, lifetime()); session().data().viewLayoutChanged( ) | rpl::filter([=](not_null view) { @@ -771,6 +771,19 @@ void HistoryInner::repaintItem(const Element *view) { } } +void HistoryInner::repaintItem(const Element *view, QRect rect) { + if (rect.isNull()) { + return repaintItem(view); + } + if (_widget->skipItemRepaint()) { + return; + } + const auto top = itemTop(view); + if (top >= 0) { + update(rect.translated(0, top)); + } +} + template void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Method method) { // No displayed messages in this history. @@ -1118,6 +1131,7 @@ Ui::ChatPaintContext HistoryInner::preparePaintContext( .visibleAreaPositionGlobal = visibleAreaPositionGlobal, .visibleAreaTop = _visibleAreaTop, .visibleAreaWidth = width(), + .visibleAreaHeight = _visibleAreaBottom - _visibleAreaTop, }); } @@ -3155,7 +3169,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu->addSeparator(&st::expandedMenuSeparator); } auto item = base::make_unique_q( - _menu, + _menu->menu(), st::menuWithIcons, st::historyHasCustomEmoji, st::historySponsoredAboutMenuLabelPosition, diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index f775a8dc20..16f2744bb1 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -129,6 +129,7 @@ public: void repaintItem(const HistoryItem *item); void repaintItem(const Element *view); + void repaintItem(const Element *view, QRect rect); [[nodiscard]] bool canCopySelected() const; [[nodiscard]] bool canDeleteSelected() const; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 1cf8dd3842..71902b2872 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_premium.h" #include "api/api_sensitive_content.h" +#include "api/api_transcribes.h" #include "lang/lang_keys.h" #include "calls/calls_instance.h" // Core::App().calls().joinGroupCall. #include "history/view/history_view_item_preview.h" @@ -374,8 +375,16 @@ std::unique_ptr HistoryItem::CreateMedia( item, item->history()->owner().processTodoList(item->fullId(), media)); }, [&](const MTPDmessageMediaDice &media) -> Result { + auto outcome = Data::DiceGameOutcome(); + if (const auto game = media.vgame_outcome()) { + const auto &data = game->data(); + outcome.seed = data.vseed().v; + outcome.nanoTon = data.vton_amount().v; + outcome.stakeNanoTon = data.vstake_ton_amount().v; + } return std::make_unique( item, + outcome, qs(media.vemoticon()), media.vvalue().v); }, [&](const MTPDmessageMediaStory &media) -> Result { @@ -739,6 +748,7 @@ HistoryItem::HistoryItem( _media = std::make_unique(this, document, Args{ .hasQualitiesList = video && !video->qualities.empty(), .skipPremiumEffect = !history->session().premium(), + .spoiler = fields.mediaSpoiler, }); setText(caption); } @@ -751,7 +761,7 @@ HistoryItem::HistoryItem( : HistoryItem(history, fields) { createComponentsHelper(std::move(fields)); - const auto spoiler = false; + const auto spoiler = fields.mediaSpoiler; _media = std::make_unique(this, photo, spoiler); setText(caption); } @@ -1882,6 +1892,18 @@ TextWithEntities HistoryItem::factcheckText() const { return {}; } +const Api::SummaryEntry &HistoryItem::summaryEntry() const { + if (!(_flags & MessageFlag::HasSummaryEntry)) { + static const auto empty = Api::SummaryEntry(); + return empty; + } + return history()->session().api().transcribes().summary(this); +} + +void HistoryItem::setHasSummaryEntry() { + _flags |= MessageFlag::HasSummaryEntry; +} + PeerData *HistoryItem::specialNotificationPeer() const { return (mentionsMe() && !_history->peer->isUser()) ? from().get() diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index d79d1eead2..c760111fe5 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -32,6 +32,10 @@ class ReplyKeyboard; struct LanguageId; enum class SuggestionActions : uchar; +namespace Api { +struct SummaryEntry; +} // namespace Api + namespace base { template class enum_mask; @@ -92,6 +96,7 @@ struct HistoryItemCommonFields { HistoryMessageSuggestInfo suggest; bool ignoreForwardFrom = false; bool ignoreForwardCaptions = false; + bool mediaSpoiler = false; }; enum class HistoryReactionSource : char { @@ -214,6 +219,8 @@ public: void setFactcheck(MessageFactcheck info); [[nodiscard]] bool hasUnrequestedFactcheck() const; [[nodiscard]] TextWithEntities factcheckText() const; + [[nodiscard]] const Api::SummaryEntry &summaryEntry() const; + void setHasSummaryEntry(); [[nodiscard]] not_null notificationThread() const; [[nodiscard]] Data::Thread *maybeNotificationThread() const; @@ -332,6 +339,9 @@ public: [[nodiscard]] bool showSimilarChannels() const { return _flags & MessageFlag::ShowSimilarChannels; } + [[nodiscard]] bool canBeSummarized() const { + return _flags & MessageFlag::CanBeSummarized; + } [[nodiscard]] bool hasRealFromId() const; [[nodiscard]] bool isPostHidingAuthor() const; [[nodiscard]] bool isPostShowingAuthor() const; diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 8face82515..413dd57e4c 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -32,7 +32,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/click_handler_types.h" #include "core/ui_integration.h" #include "layout/layout_position.h" -#include "mainwindow.h" #include "media/audio/media_audio.h" #include "media/player/media_player_instance.h" #include "data/business/data_shortcut_messages.h" @@ -379,6 +378,8 @@ ReplyFields ReplyFieldsFromMTP( auto result = ReplyFields(); if (const auto peer = data.vreply_to_peer_id()) { result.externalPeerId = peerFromMTP(*peer); + } else if (item->isAdminLogEntry()) { + result.externalPeerId = item->history()->peer->id; } const auto owner = &item->history()->owner(); if (const auto id = data.vreply_to_msg_id().value_or_empty()) { @@ -661,7 +662,9 @@ void HistoryMessageReply::storyRemoved( void HistoryMessageReply::refreshReplyToMedia() { replyToDocumentId = 0; replyToWebPageId = 0; - if (const auto media = resolvedMessage ? resolvedMessage->media() : nullptr) { + if (const auto media = resolvedMessage + ? resolvedMessage->media() + : nullptr) { if (const auto document = media->document()) { replyToDocumentId = document->id; } else if (const auto webpage = media->webpage()) { @@ -863,22 +866,23 @@ void ReplyKeyboard::resize(int width, int height) { ? float64(_st->buttonHeight()) : (float64(height + _st->buttonSkip()) / _rows.size()); for (auto &row : _rows) { - int s = row.size(); + auto s = int(row.size()); - int widthForButtons = _width - ((s - 1) * _st->buttonSkip()); - int widthForText = widthForButtons; - int widthOfText = 0; - int maxMinButtonWidth = 0; + auto widthForButtons = _width - ((s - 1) * _st->buttonSkip()); + auto widthForText = widthForButtons; + auto widthOfText = 0; + auto maxMinButtonWidth = 0; for (const auto &button : row) { widthOfText += qMax(button.text.maxWidth(), 1); int minButtonWidth = _st->minButtonWidth(button.type); widthForText -= minButtonWidth; accumulate_max(maxMinButtonWidth, minButtonWidth); } - bool exact = (widthForText == widthOfText); - bool enough = (widthForButtons - s * maxMinButtonWidth) >= widthOfText; + const auto exact = (widthForText == widthOfText); + const auto enough + = (widthForButtons - s * maxMinButtonWidth) >= widthOfText; - float64 x = 0; + auto x = 0.; for (auto &button : row) { int buttonw = qMax(button.text.maxWidth(), 1); float64 textw = buttonw, minw = _st->minButtonWidth(button.type); @@ -894,10 +898,17 @@ void ReplyKeyboard::resize(int width, int height) { accumulate_max(w, 2 * float64(_st->buttonPadding())); } - int rectx = static_cast(std::floor(x)); - int rectw = static_cast(std::floor(x + w)) - rectx; - button.rect = QRect(rectx, qRound(y), rectw, qRound(buttonHeight - _st->buttonSkip())); - if (rtl()) button.rect.setX(_width - button.rect.x() - button.rect.width()); + const auto rectx = static_cast(std::floor(x)); + const auto rectw = static_cast(std::floor(x + w)) - rectx; + button.rect = QRect( + rectx, + qRound(y), + rectw, + qRound(buttonHeight - _st->buttonSkip())); + if (rtl()) { + button.rect.setX( + _width - button.rect.x() - button.rect.width()); + } x += w + _st->buttonSkip(); button.link->setFullDisplayed(textw >= buttonw); @@ -906,10 +917,12 @@ void ReplyKeyboard::resize(int width, int height) { } } -bool ReplyKeyboard::isEnoughSpace(int width, const style::BotKeyboardButton &st) const { +bool ReplyKeyboard::isEnoughSpace( + int width, + const style::BotKeyboardButton &st) const { for (const auto &row : _rows) { - int s = row.size(); - int widthLeft = width - ((s - 1) * st.margin + s * 2 * st.padding); + auto s = int(row.size()); + auto widthLeft = width - ((s - 1) * st.margin + s * 2 * st.padding); for (const auto &button : row) { widthLeft -= qMax(button.text.maxWidth(), 1); if (widthLeft < 0) { @@ -953,7 +966,8 @@ int ReplyKeyboard::naturalWidth() const { } int ReplyKeyboard::naturalHeight() const { - return (_rows.size() - 1) * _st->buttonSkip() + _rows.size() * _st->buttonHeight(); + return (_rows.size() - 1) * _st->buttonSkip() + + _rows.size() * _st->buttonHeight(); } void ReplyKeyboard::paint( @@ -979,7 +993,9 @@ void ReplyKeyboard::paint( } // just ignore the buttons that didn't layout well - if (rect.x() + rect.width() > _width) break; + if (rect.x() + rect.width() > _width) { + break; + } auto buttonRounding = Ui::BubbleRounding(); using Corner = Ui::BubbleCornerRounding; @@ -1024,9 +1040,15 @@ ClickHandlerPtr ReplyKeyboard::getLink(QPoint point) const { QRect rect(button.rect); // just ignore the buttons that didn't layout well - if (rect.x() + rect.width() > _width) break; + if (rect.x() + rect.width() > _width) { + break; + } if (rect.contains(point)) { + if (_item->isAdminLogEntry() + && button.type != HistoryMessageMarkupButton::Type::Url) { + return ClickHandlerPtr(); + } _savedCoords = point; return button.link; } @@ -1048,8 +1070,12 @@ ClickHandlerPtr ReplyKeyboard::getLinkByIndex(int index) const { return ClickHandlerPtr(); } -void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { - if (!p) return; +void ReplyKeyboard::clickHandlerActiveChanged( + const ClickHandlerPtr &p, + bool active) { + if (!p) { + return; + } _savedActive = active ? p : ClickHandlerPtr(); auto coords = findButtonCoordsByClickHandler(p); @@ -1058,7 +1084,8 @@ void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool act } } -ReplyKeyboard::ButtonCoords ReplyKeyboard::findButtonCoordsByClickHandler(const ClickHandlerPtr &p) { +ReplyKeyboard::ButtonCoords ReplyKeyboard::findButtonCoordsByClickHandler( + const ClickHandlerPtr &p) { for (int i = 0, rows = _rows.size(); i != rows; ++i) { auto &row = _rows[i]; for (int j = 0, cols = row.size(); j != cols; ++j) { @@ -1074,7 +1101,9 @@ void ReplyKeyboard::clickHandlerPressedChanged( const ClickHandlerPtr &handler, bool pressed, Ui::BubbleRounding rounding) { - if (!handler) return; + if (!handler) { + return; + } _savedPressed = pressed ? handler : ClickHandlerPtr(); auto coords = findButtonCoordsByClickHandler(handler); @@ -1174,7 +1203,7 @@ void ReplyKeyboard::Style::paintButton( int outerWidth, const ReplyKeyboard::Button &button, Ui::BubbleRounding rounding) const { - const QRect &rect = button.rect; + const auto &rect = button.rect; paintButtonBg(p, st, rect, rounding, button.howMuchOver); if (button.ripple) { const auto color = st ? &st->msgBotKbRippleBg()->c : nullptr; @@ -1422,7 +1451,7 @@ TextWithEntities ComposeTodoTasksList( } HistoryDocumentCaptioned::HistoryDocumentCaptioned() -: caption(st::msgFileMinWidth - st::msgPadding.left() - st::msgPadding.right()) { +: caption(st::msgFileMinWidth - rect::m::sum::h(st::msgPadding)) { } HistoryDocumentVoicePlayback::HistoryDocumentVoicePlayback( diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 35efd6720a..79683e18a3 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_credits_graphics.h" #include "storage/storage_account.h" #include "ui/boxes/confirm_box.h" +#include "ui/boxes/emoji_stake_box.h" // InsufficientTonBox #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" @@ -520,10 +521,12 @@ bool SendPaymentHelper::check( done); return false; } + const auto session = &peer->session(); if (checkSuggestPriceTon - && checkSuggestPriceTon > peer->session().credits().tonBalance()) { + && checkSuggestPriceTon > session->credits().tonBalance()) { using namespace HistoryView; - show->show(Box(InsufficientTonBox, peer, checkSuggestPriceTon)); + show->show( + Box(Ui::InsufficientTonBox, session, checkSuggestPriceTon)); return false; } return true; @@ -747,9 +750,10 @@ ClickHandlerPtr JumpToMessageClickHandler( : peer->session().tryResolveWindow(peer); if (controller) { auto params = Window::SectionShow{ - Window::SectionShow::Way::Forward + Window::SectionShow::Way::Forward, }; params.highlight = highlight; + params.allowDuplicateInStack = true; params.origin = Window::SectionShow::OriginMessage{ returnToId }; @@ -859,6 +863,9 @@ MessageFlags FlagsFromMTP( ? Flag::TonPaidSuggested : (flags & MTP::f_paid_suggested_post_stars) ? Flag::StarsPaidSuggested + : Flag()) + | ((flags & MTP::f_summary_from_language) + ? Flag::CanBeSummarized : Flag()); } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 995c4748ce..063f930adf 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -95,6 +95,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_inner_widget.h" #include "history/history_item_components.h" #include "history/history_unread_things.h" +#include "history/admin_log/history_admin_log_section.h" #include "history/view/controls/history_view_characters_limit.h" #include "history/view/controls/history_view_compose_search.h" #include "history/view/controls/history_view_forward_panel.h" @@ -653,6 +654,17 @@ HistoryWidget::HistoryWidget( } }, lifetime()); + session().data().itemShowHighlightRequest( + ) | rpl::on_next([=](not_null item) { + const auto history = item->history(); + if (history == _history || history == _migrated) { + if (item->mainView()) { + enqueueMessageHighlight({ item }); + animatedScrollToItem(item->id); + } + } + }, lifetime()); + session().data().itemDataChanges( ) | rpl::filter([=](not_null item) { return !_list && (item->mainView() != nullptr); @@ -2247,6 +2259,16 @@ void HistoryWidget::setupShortcuts() { return false; }); } + const auto channel = _peer ? _peer->asChannel() : nullptr; + const auto hasRecentActions = channel + && (channel->hasAdminRights() || channel->amCreator()); + if (hasRecentActions) { + request->check(Command::ShowAdminLog, 1) && request->handle([=] { + controller()->showSection( + std::make_shared(channel)); + return true; + }); + } if (session().supportMode()) { request->check( Command::SupportToggleMuted diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp index e3a982a427..a363e11c72 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -205,8 +205,8 @@ PreviewWrap::PreviewWrap( const auto session = &_history->session(); session->data().viewRepaintRequest( - ) | rpl::on_next([=](not_null view) { - if (_views.contains(view)) { + ) | rpl::on_next([=](Data::RequestViewRepaint data) { + if (_views.contains(data.view)) { update(); } }, lifetime()); @@ -443,6 +443,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { auto context = _theme->preparePaintContext( _style.get(), rect(), + rect(), e->rect(), !window()->isActiveWindow()); for (const auto &entry : _entries) { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp index 51147d5c3f..72a565978e 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.cpp @@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/controls/history_view_suggest_options.h" -#include "base/event_filter.h" #include "base/unixtime.h" #include "boxes/star_gift_box.h" #include "chat_helpers/compose/compose_show.h" @@ -21,7 +20,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "history/history_item_components.h" #include "history/history_item_helpers.h" -#include "info/channel_statistics/earn/earn_format.h" #include "info/channel_statistics/earn/earn_icons.h" #include "lang/lang_keys.h" #include "lottie/lottie_icon.h" @@ -32,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/boxes/choose_date_time.h" +#include "ui/boxes/emoji_stake_box.h" #include "ui/boxes/single_choice_box.h" #include "ui/controls/ton_common.h" #include "ui/widgets/fields/number_input.h" @@ -75,19 +74,6 @@ namespace { })); } -[[nodiscard]] not_null AddMoneyInputIcon( - not_null parent, - Ui::Text::PaletteDependentEmoji emoji) { - auto helper = Ui::Text::CustomEmojiHelper(); - auto text = helper.paletteDependent(std::move(emoji)); - return Ui::CreateChild( - parent, - rpl::single(std::move(text)), - st::defaultFlatLabel, - st::defaultPopupMenu, - helper.context()); -} - } // namespace void ChooseSuggestTimeBox( @@ -123,90 +109,6 @@ void ChooseSuggestTimeBox( }); } -void AddApproximateUsd( - not_null field, - not_null session, - rpl::producer price) { - auto value = std::move(price) | rpl::map([=](CreditsAmount amount) { - if (!amount) { - return QString(); - } - const auto appConfig = &session->appConfig(); - const auto rate = amount.ton() - ? appConfig->currencySellRate() - : (appConfig->starsSellRate() / 100.); - return Info::ChannelEarn::ToUsd(amount, rate, 2); - }); - const auto usd = Ui::CreateChild( - field, - std::move(value), - st::suggestPriceEstimate); - const auto move = [=] { - usd->moveToRight(0, st::suggestPriceEstimateTop); - }; - base::install_event_filter(field, [=](not_null e) { - if (e->type() == QEvent::Resize) { - move(); - } - return base::EventFilterResult::Continue; - }); - usd->widthValue() | rpl::on_next(move, usd->lifetime()); -} - -not_null AddStarsInputField( - not_null container, - StarsInputFieldArgs &&args) { - const auto wrap = container->add( - object_ptr( - container, - st::editTagField.heightMin), - st::boxRowPadding); - const auto result = Ui::CreateChild( - wrap, - st::editTagField, - rpl::single(u"0"_q), - args.value ? QString::number(*args.value) : QString(), - args.max ? args.max : std::numeric_limits::max()); - const auto icon = AddMoneyInputIcon( - result, - Ui::Earn::IconCreditsEmoji()); - - wrap->widthValue() | rpl::on_next([=](int width) { - icon->move(st::starsFieldIconPosition); - result->move(0, 0); - result->resize(width, result->height()); - wrap->resize(width, result->height()); - }, wrap->lifetime()); - - return result; -} - -not_null AddTonInputField( - not_null container, - TonInputFieldArgs &&args) { - const auto wrap = container->add( - object_ptr( - container, - st::editTagField.heightMin), - st::boxRowPadding); - const auto result = Ui::CreateTonAmountInput( - wrap, - rpl::single('0' + Ui::TonAmountSeparator() + '0'), - args.value); - const auto icon = AddMoneyInputIcon( - result, - Ui::Earn::IconCurrencyEmoji()); - - wrap->widthValue() | rpl::on_next([=](int width) { - icon->move(st::tonFieldIconPosition); - result->move(0, 0); - result->resize(width, result->height()); - wrap->resize(width, result->height()); - }, wrap->lifetime()); - - return result; -} - StarsTonPriceInput AddStarsTonPriceInput( not_null container, StarsTonPriceArgs &&args) { @@ -763,7 +665,8 @@ void ChooseSuggestPriceBox( state->savePending = true; return; } else if (credits->tonBalance() < value) { - box->uiShow()->show(Box(InsufficientTonBox, usePeer, value)); + box->uiShow()->show( + Box(Ui::InsufficientTonBox, session, value)); return; } } @@ -934,60 +837,6 @@ QString FormatAfterCommissionPercent( return QString::number(mul / 10.) + '%'; } -void InsufficientTonBox( - not_null box, - not_null peer, - CreditsAmount required) { - box->setStyle(st::suggestPriceBox); - box->addTopButton(st::boxTitleClose, [=] { - box->closeBox(); - }); - - auto icon = Settings::CreateLottieIcon( - box->verticalLayout(), - { - .name = u"diamond"_q, - .sizeOverride = st::normalBoxLottieSize, - }, - {}); - box->setShowFinishedCallback([animate = std::move(icon.animate)] { - animate(anim::repeat::loop); - }); - box->addRow(std::move(icon.widget), st::lowTonIconPadding); - const auto add = required - peer->session().credits().tonBalance(); - const auto nano = add.whole() * Ui::kNanosInOne + add.nano(); - const auto amount = Ui::FormatTonAmount(nano).full; - box->addRow( - object_ptr( - box, - tr::lng_suggest_low_ton_title(tr::now, lt_amount, amount), - st::boxTitle), - st::boxRowPadding + st::lowTonTitlePadding, - style::al_top); - const auto label = box->addRow( - object_ptr( - box, - tr::lng_suggest_low_ton_text(tr::rich), - st::lowTonText), - st::boxRowPadding + st::lowTonTextPadding, - style::al_top); - label->setTryMakeSimilarLines(true); - label->resizeToWidth( - st::boxWidth - st::boxRowPadding.left() - st::boxRowPadding.right()); - - const auto url = tr::lng_suggest_low_ton_fragment_url(tr::now); - const auto button = box->addButton( - tr::lng_suggest_low_ton_fragment(), - [=] { UrlClickHandler::Open(url); }); - const auto buttonWidth = st::boxWidth - - rect::m::sum::h(st::suggestPriceBox.buttonPadding); - button->widthValue() | rpl::filter([=] { - return (button->widthNoMargins() != buttonWidth); - }) | rpl::on_next([=] { - button->resizeToWidth(buttonWidth); - }, button->lifetime()); -} - SuggestOptionsBar::SuggestOptionsBar( std::shared_ptr show, not_null peer, diff --git a/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.h b/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.h index 91c186c9d8..fb5fd4fea0 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_suggest_options.h @@ -51,21 +51,6 @@ void ChooseSuggestTimeBox( not_null box, SuggestTimeBoxArgs &&args); -struct StarsInputFieldArgs { - std::optional value; - int64 max = 0; -}; -[[nodiscard]] not_null AddStarsInputField( - not_null container, - StarsInputFieldArgs &&args); - -struct TonInputFieldArgs { - int64 value = 0; -}; -[[nodiscard]] not_null AddTonInputField( - not_null container, - TonInputFieldArgs &&args); - struct StarsTonPriceInput { Fn focusCallback; Fn()> computeResult; @@ -115,11 +100,6 @@ void ChooseSuggestPriceBox( not_null session, CreditsAmount price); -void InsufficientTonBox( - not_null box, - not_null peer, - CreditsAmount required); - class SuggestOptionsBar final { public: SuggestOptionsBar( diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index 9328c80c69..22313f3749 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/message_bubble.h" #include "ui/chat/chat_style.h" #include "ui/effects/reaction_fly_animation.h" +#include "ui/text/custom_emoji_helper.h" #include "ui/text/format_values.h" #include "ui/text/text_options.h" #include "ui/text/text_utilities.h" @@ -149,7 +150,8 @@ bool BottomInfo::isWide() const { || !_data.author.isEmpty() || !_views.isEmpty() || !_replies.isEmpty() - || _effect; + || _effect + || _data.tonStake; } TextState BottomInfo::textState( @@ -486,18 +488,33 @@ void BottomInfo::layoutDateText() { : name.isEmpty() ? (deleted + date) : (deleted + name + afterAuthor); + auto helper = Ui::Text::CustomEmojiHelper( + Core::TextContext({ .session = &_reactionsOwner->session() })); auto marked = TextWithEntities(); if (const auto count = _data.stars) { marked.append( Ui::Text::IconEmoji(&st::starIconEmojiSmall) ).append(Lang::FormatCountToShort(count).string).append(u", "_q); } + if (const auto stake = _data.tonStake) { + marked.append( + QString::number(stake / 1e9) + ).append(helper.image({ + .image = Ui::Emoji::SinglePixmap( + Ui::Emoji::Find(QString::fromUtf8("\xf0\x9f\x92\x8e")), + Ui::Emoji::GetSizeNormal()).toImage().scaledToHeight( + st::stakeIconEmojiSize * style::DevicePixelRatio(), + Qt::SmoothTransformation), + .margin = QMargins(0, st::stakeIconEmojiTop, 0, 0), + .textColor = false, + })).append(" "); + } marked.append(full); _authorEditedDate.setMarkedText( st::msgDateTextStyle, marked, Ui::NameTextOptions(), - Core::TextContext({ .session = &_reactionsOwner->session() })); + helper.context()); } else { TextWithEntities deleted; if (_data.flags & Data::Flag::AyuDeleted) { @@ -549,25 +566,34 @@ void BottomInfo::layoutDateText() { full.append(deleted).append(name).append(afterAuthor); } + auto helper = Ui::Text::CustomEmojiHelper( + Core::TextContext({ .session = &_reactionsOwner->session() })); auto marked = TextWithEntities(); if (const auto count = _data.stars) { marked.append( Ui::Text::IconEmoji(&st::starIconEmojiSmall) ).append(Lang::FormatCountToShort(count).string).append(u", "_q); } + if (const auto stake = _data.tonStake) { + marked.append( + QString::number(stake / 1e9) + ).append(helper.image({ + .image = Ui::Emoji::SinglePixmap( + Ui::Emoji::Find(QString::fromUtf8("\xf0\x9f\x92\x8e")), + Ui::Emoji::GetSizeNormal()).toImage().scaledToHeight( + st::stakeIconEmojiSize * style::DevicePixelRatio(), + Qt::SmoothTransformation), + .margin = QMargins(0, st::stakeIconEmojiTop, 0, 0), + .textColor = false, + })).append(" "); + } marked.append(full); - const auto context = Core::TextContext({ - .session = &_reactionsOwner->session(), - .repaint = [] {}, - .customEmojiLoopLimit = 0, - }); - _authorEditedDate.setMarkedText( st::msgDateTextStyle, marked, Ui::NameTextOptions(), - context); + helper.context()); } } @@ -721,16 +747,21 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null message) { result.flags |= Flag::Sending; } if (!item->history()->peer->isUser()) { - const auto media = message->media(); const auto mine = PaidInformation{ .messages = 1, .stars = item->starsPaid(), }; + const auto media = message->media(); auto info = media ? media->paidInformation().value_or(mine) : mine; if (const auto total = info.stars) { result.stars = total; } } + if (const auto media = item->media()) { + if (const auto outcome = media->diceGameOutcome()) { + result.tonStake = outcome.stakeNanoTon; + } + } const auto forwarded = item->Get(); if (forwarded && forwarded->imported) { result.flags |= Flag::Imported; diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.h b/Telegram/SourceFiles/history/view/history_view_bottom_info.h index 4c0f7ccf4d..a76afb2df8 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.h +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.h @@ -52,6 +52,7 @@ public: QDateTime date; QString author; EffectId effectId = 0; + int64 tonStake = 0; int stars = 0; TimeId scheduleRepeatPeriod = 0; std::optional views; diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index 4d2500d1d3..fe2e362e63 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -60,7 +60,7 @@ class Item final , private ListDelegate , private CornerButtonsDelegate { public: - Item(not_null parent, not_null thread); + Item(not_null parent, not_null thread); [[nodiscard]] not_null action() const override; [[nodiscard]] bool isEnabled() const override; @@ -274,7 +274,7 @@ struct StatusFields { }); } -Item::Item(not_null parent, not_null thread) +Item::Item(not_null parent, not_null thread) : Ui::Menu::ItemBase(parent, st::previewMenu.menu) , _dummyAction(new QAction(parent)) , _session(&thread->session()) @@ -860,6 +860,11 @@ Ui::ChatPaintContext Item::listPreparePaintContext( Ui::ChatPaintContextArgs &&args) { const auto visibleAreaTopLocal = mapFromGlobal( args.visibleAreaPositionGlobal).y(); + const auto area = QRect( + 0, + args.visibleAreaTop, + args.visibleAreaWidth, + args.visibleAreaHeight); const auto viewport = QRect( 0, args.visibleAreaTop - visibleAreaTopLocal, @@ -868,6 +873,7 @@ Ui::ChatPaintContext Item::listPreparePaintContext( return args.theme->preparePaintContext( _chatStyle.get(), viewport, + area, args.clip, false); } @@ -960,7 +966,7 @@ ChatPreview MakeChatPreview( }; const auto menu = result.menu.get(); - auto action = base::make_unique_q(menu, thread); + auto action = base::make_unique_q(menu->menu(), thread); result.actions = action->actions(); menu->addAction(std::move(action)); if (const auto topic = thread->asTopic()) { diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp index f03dc14fb6..75f22b2667 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp @@ -480,6 +480,9 @@ ChatWidget::~ChatWidget() { } if (_topic) { if (_topic->creating()) { + if (controller()->activeChatCurrent().topic() == _topic) { + controller()->setActiveChatEntry(Dialogs::Key()); + } _emptyPainter = nullptr; _topic->discard(); _topic = nullptr; diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 03dadd1a8d..485df86b7e 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -2030,7 +2030,7 @@ void AddEmojiPacksAction( st::historyHasCustomEmojiPosition, std::move(text)); const auto weak = base::make_weak(controller); - button->setClickedCallback([=] { + button->setActionTriggered([=] { const auto strong = weak.get(); if (!strong) { return; diff --git a/Telegram/SourceFiles/history/view/history_view_cursor_state.h b/Telegram/SourceFiles/history/view/history_view_cursor_state.h index 95acba349d..f1fde98bda 100644 --- a/Telegram/SourceFiles/history/view/history_view_cursor_state.h +++ b/Telegram/SourceFiles/history/view/history_view_cursor_state.h @@ -25,6 +25,7 @@ enum class CursorState : char { Enlarge, Forwarded, FromPhoto, + LogAdminService, }; struct TextState { diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 145edbcf5b..20be37a49b 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_element.h" +#include "apiwrap.h" +#include "api/api_transcribes.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_message.h" #include "history/view/media/history_view_media_generic.h" @@ -619,7 +621,7 @@ QString DateTooltipText(not_null view) { const auto parsed = base::unixtime::parse( forwarded->savedFromDate); if (parsed != view->dateTime()) { - dateText += '\n' + tr::lng_forwarded_saved_date( + dateText += '\n' + tr::lng_forwarded_forwarded_date( tr::now, lt_date, locale.toString(parsed, format)); @@ -767,7 +769,10 @@ void ForumThreadBar::init( st::semiboldTextStyle, topic->titleWithIconOrLogo(), kMarkupTextOptions, - Core::TextContext({ .session = &topic->session() })); + Core::TextContext({ + .session = &topic->session(), + .customEmojiLoopLimit = -1, // First frame only + })); } const auto skip = st::monoforumBarUserpicSkip; const auto userpic = sublist @@ -841,7 +846,10 @@ int ForumThreadBar::PaintForGetWidth( st::semiboldTextStyle, topic->titleWithIconOrLogo(), kMarkupTextOptions, - Core::TextContext({ .session = &topic->session() })); + Core::TextContext({ + .session = &topic->session(), + .customEmojiLoopLimit = -1, // First frame only + })); } else { text.setText(st::semiboldTextStyle, sublist->sublistPeer()->name()); } @@ -933,6 +941,7 @@ void ForumThreadBar::Paint( text.draw(p, { .position = { textLeft, textTop }, .availableWidth = available, + .paused = true, .elisionLines = 1, }); @@ -948,7 +957,9 @@ void ServicePreMessage::init( not_null view, PreparedServiceText string, ClickHandlerPtr fullClickHandler, - std::unique_ptr media) { + std::unique_ptr media, + bool below) { + this->below = below; text = Ui::Text::String( st::serviceTextStyle, string.text, @@ -1014,7 +1025,9 @@ void ServicePreMessage::paint( ElementChatMode mode) const { if (media && media->hideServiceText()) { const auto left = (width - media->width()) / 2; - const auto top = g.top() - height - st::msgMargin.bottom(); + const auto top = below + ? (g.top() + g.height() - st::msgServiceMargin.top() + st::msgServiceMargin.bottom()) + : (g.top() - height - st::msgMargin.bottom()); const auto position = QPoint(left, top); p.translate(position); media->draw(p, context.selected() @@ -1022,7 +1035,9 @@ void ServicePreMessage::paint( : context.translated(-position).withSelection({})); p.translate(-position); } else { - const auto top = g.top() - height - st::msgMargin.top(); + const auto top = below + ? (g.top() + g.height() - st::msgServiceMargin.top() + st::msgServiceMargin.bottom()) + : (g.top() - height - st::msgMargin.top()); p.translate(0, top); const auto rect = QRect(0, 0, width, height) @@ -1060,11 +1075,15 @@ ClickHandlerPtr ServicePreMessage::textState( QRect g) const { if (media && media->hideServiceText()) { const auto left = (width - media->width()) / 2; - const auto top = g.top() - height - st::msgMargin.bottom(); + const auto top = below + ? (g.top() + g.height() - st::msgServiceMargin.top() + st::msgServiceMargin.bottom()) + : (g.top() - height - st::msgMargin.bottom()); const auto position = QPoint(left, top); return media->textState(point - position, request).link; } - const auto top = g.top() - height - st::msgMargin.top(); + const auto top = below + ? (g.top() + g.height() - st::msgServiceMargin.top() + st::msgServiceMargin.bottom()) + : (g.top() - height - st::msgMargin.top()); const auto rect = QRect(0, top, width, height) - st::msgServiceMargin; const auto trect = rect - st::msgServicePadding; @@ -1261,8 +1280,8 @@ void Element::prepareCustomEmojiPaint( } } -void Element::repaint() const { - history()->owner().requestViewRepaint(this); +void Element::repaint(QRect r) const { + history()->owner().requestViewRepaint(this, r); } void Element::paintHighlight( @@ -1697,7 +1716,23 @@ void Element::validateText() { } // Albums may show text of a different item than the parent one. + // Media::itemForText may initialize data within the object. _textItem = _media ? _media->itemForText() : item.get(); + + const auto &summary = item->summaryEntry(); + const auto summaryShownWas = (_flags & Flag::SummaryShown) != 0; + const auto summaryShownNow = !summary.result.empty() && summary.shown; + const auto summaryShownChanged = (summaryShownWas != summaryShownNow); + if (summaryShownNow) { + _flags |= Flag::SummaryShown; + if (summaryShownChanged) { + setTextWithLinks(summary.result); + } + return; + } else { + _flags &= ~Flag::SummaryShown; + } + if (!_textItem) { if (!_text.isEmpty()) { setTextWithLinks({}); @@ -1705,7 +1740,7 @@ void Element::validateText() { return; } const auto &text = _textItem->_text; - if (_text.isEmpty() == text.empty()) { + if (!summaryShownChanged && _text.isEmpty() == text.empty()) { } else if (_flags & Flag::ServiceMessage) { const auto contextDependentText = contextDependentServiceText(); const auto &markedText = contextDependentText.text.empty() @@ -2106,7 +2141,28 @@ void Element::setServicePreMessage( this, std::move(text), std::move(fullClickHandler), - std::move(media)); + std::move(media), + false); + setPendingResize(); + } else if (Has()) { + RemoveComponents(ServicePreMessage::Bit()); + setPendingResize(); + } +} + +void Element::setServicePostMessage( + PreparedServiceText text, + ClickHandlerPtr fullClickHandler, + std::unique_ptr media) { + if (!text.text.empty() || media) { + AddComponents(ServicePreMessage::Bit()); + const auto service = Get(); + service->init( + this, + std::move(text), + std::move(fullClickHandler), + std::move(media), + true); setPendingResize(); } else if (Has()) { RemoveComponents(ServicePreMessage::Bit()); @@ -2404,6 +2460,7 @@ void Element::itemTextUpdated() { if (const auto media = _media.get()) { media->parentTextUpdated(); } + _flags &= ~Flag::SummaryShown; clearSpecialOnlyEmoji(); _text = Ui::Text::String(st::msgMinWidth); _textWidth = -1; diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index e7f2a67c17..f252fe9a78 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -317,7 +317,8 @@ struct ServicePreMessage : RuntimeComponent { not_null view, PreparedServiceText string, ClickHandlerPtr fullClickHandler, - std::unique_ptr media = nullptr); + std::unique_ptr media, + bool below); int resizeToWidth(int newWidth, ElementChatMode mode); @@ -336,6 +337,7 @@ struct ServicePreMessage : RuntimeComponent { ClickHandlerPtr handler; int width = 0; int height = 0; + bool below = false; }; @@ -395,6 +397,7 @@ public: TopicRootReply = 0x0400, MediaOverriden = 0x0800, HeavyCustomEmoji = 0x1000, + SummaryShown = 0x2000, }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } @@ -483,6 +486,10 @@ public: PreparedServiceText text, ClickHandlerPtr fullClickHandler = nullptr, std::unique_ptr media = nullptr); + void setServicePostMessage( + PreparedServiceText text, + ClickHandlerPtr fullClickHandler = nullptr, + std::unique_ptr media = nullptr); bool computeIsAttachToPrevious(not_null previous); @@ -634,7 +641,7 @@ public: const Reactions::InlineList &reactions) const; void clearCustomEmojiRepaint() const; void hideSpoilers(); - void repaint() const; + void repaint(QRect r = QRect()) const; [[nodiscard]] ClickHandlerPtr fromPhotoLink() const { return fromLink(); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index fefcb98ea0..8e397ff714 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -420,9 +420,9 @@ ListWidget::ListWidget( setMouseTracking(true); _scrollDateHideTimer.setCallback([this] { scrollDateHideByTimer(); }); _session->data().viewRepaintRequest( - ) | rpl::on_next([this](auto view) { - if (view->delegate() == this) { - repaintItem(view); + ) | rpl::on_next([this](Data::RequestViewRepaint data) { + if (data.view->delegate() == this) { + repaintItem(data.view, data.rect); } }, lifetime()); _session->data().viewResizeRequest( @@ -437,6 +437,10 @@ ListWidget::ListWidget( refreshItem(view); } }, lifetime()); + _session->data().itemShowHighlightRequest( + ) | rpl::on_next([this](auto item) { + showItemHighlight(item); + }, lifetime()); _session->data().viewLayoutChanged( ) | rpl::on_next([this](auto view) { if (view->delegate() == this) { @@ -2215,6 +2219,7 @@ Ui::ChatPaintContext ListWidget::preparePaintContext( .visibleAreaPositionGlobal = mapToGlobal(QPoint(0, _visibleTop)), .visibleAreaTop = _visibleTop, .visibleAreaWidth = width(), + .visibleAreaHeight = _visibleBottom - _visibleTop, }); } @@ -4046,6 +4051,17 @@ void ListWidget::repaintItem(const Element *view) { } } +void ListWidget::repaintItem(const Element *view, QRect rect) { + if (rect.isNull()) { + return repaintItem(view); + } + if (!view) { + return; + } + const auto top = itemTop(view); + update(rect.translated(0, top)); +} + void ListWidget::repaintItem(FullMsgId itemId) { if (const auto view = viewForItem(itemId)) { repaintItem(view); @@ -4140,6 +4156,25 @@ void ListWidget::refreshItem(not_null view) { } } +void ListWidget::showItemHighlight(not_null item) { + const auto history = _delegate->listTranslateHistory(); + if (history && item->history() != history) { + return; + } else if (!history && !viewForItem(item)) { + return; + } + const auto position = item->position(); + auto params = Window::SectionShow{ + Window::SectionShow::Way::Forward + }; + params.animated = anim::type::normal; + if (!showAtPositionNow(position, params, nullptr)) { + showAroundPosition(position, [=, this] { + return showAtPositionNow(position, params, nullptr); + }); + } +} + void ListWidget::viewReplaced(not_null was, Element *now) { if (_visibleTopItem == was) _visibleTopItem = now; if (_scrollDateLastItem == was) _scrollDateLastItem = now; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 4ebab44e01..4caeb5a855 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -569,8 +569,10 @@ private: int itemTop(not_null view) const; void repaintItem(FullMsgId itemId); void repaintItem(const Element *view); + void repaintItem(const Element *view, QRect rect); void resizeItem(not_null view); void refreshItem(not_null view); + void showItemHighlight(not_null item); void itemRemoved(not_null item); QPoint mapPointToItem(QPoint point, const Element *view) const; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index ae4bc0f807..4cb98e5872 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_message.h" #include "api/api_suggest_post.h" +#include "api/api_transcribes.h" #include "base/qt/qt_key_modifiers.h" #include "base/unixtime.h" #include "core/click_handler_types.h" // ClickHandlerContext @@ -22,6 +23,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/reactions/history_view_reactions_button.h" #include "history/view/history_view_group_call_bar.h" // UserpicInRow. #include "history/view/history_view_reply.h" +#include "history/view/history_view_transcribe_button.h" +#include "history/view/history_view_summary_header.h" #include "history/view/history_view_view_button.h" // ViewButton. #include "history/history.h" #include "boxes/premium_preview_box.h" @@ -61,6 +64,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { +constexpr auto kSummarizeThreshold = 512; constexpr auto kPlayStatusLimit = 12; const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_"; @@ -494,11 +498,19 @@ QSize Message::performCountOptimalSize() { const auto item = data(); const auto replyData = item->Get(); + const auto &summary = item->summaryEntry(); + const auto showSummaryReply = !summary.result.empty() && summary.shown; + if (replyData && !_hideReply) { AddComponents(Reply::Bit()); } else { RemoveComponents(Reply::Bit()); } + if (showSummaryReply) { + AddComponents(SummaryHeader::Bit()); + } else { + RemoveComponents(SummaryHeader::Bit()); + } if (item->history()->peer->isMonoforum()) { if (const auto suggest = item->Get()) { @@ -538,6 +550,12 @@ QSize Message::performCountOptimalSize() { return embedReactionsInBubble() ? 0 : 1; }; const auto oldKey = reactionsKey(); + if (_summarize) { + const auto &summary = item->summaryEntry(); + if (_summarize->loading() != summary.loading) { + _summarize->setLoading(summary.loading); + } + } validateText(); validateInlineKeyboard(markup); updateViewButtonExistence(); @@ -577,6 +595,12 @@ QSize Message::performCountOptimalSize() { if (reply) { reply->update(this, replyData); } + const auto summaryHeader = Get(); + if (summaryHeader) { + if (showSummaryReply) { + summaryHeader->update(this); + } + } if (drawBubble()) { const auto forwarded = item->Get(); @@ -728,6 +752,12 @@ QSize Message::performCountOptimalSize() { + st::msgPadding.right(); accumulate_max(maxWidth, replyw); } + if (summaryHeader) { + const auto summaryHeaderWidth = st::msgPadding.left() + + summaryHeader->maxWidth() + + st::msgPadding.right(); + accumulate_max(maxWidth, summaryHeaderWidth); + } if (check) { accumulate_max(maxWidth, check->maxWidth()); minHeight += check->minHeight(); @@ -809,7 +839,7 @@ int Message::marginTop() const { result += bar->height(); } if (const auto service = Get()) { - if (!AyuFeatures::MessageShot::isTakingShot()) { + if (!service->below && !AyuFeatures::MessageShot::isTakingShot()) { result += service->height; } } @@ -824,6 +854,11 @@ int Message::marginBottom() const { return 0; } auto result = st::msgMargin.bottom(); + if (const auto service = Get()) { + if (service->below) { + result += service->height; + } + } if (const auto margins = Get()) { result += margins->bottom; } @@ -1086,6 +1121,7 @@ void Message::draw(Painter &p, const PaintContext &context) const { paintForwardedInfo(p, trect, context); paintViaBotIdInfo(p, trect, context); paintReplyInfo(p, trect, context); + paintSummaryHeaderInfo(p, trect, context); } if (entry) { trect.setHeight(trect.height() - entry->height()); @@ -1220,24 +1256,48 @@ void Message::draw(Painter &p, const PaintContext &context) const { p.setOpacity(o); } } - if (const auto size = rightActionSize()) { - const auto fastShareSkip = std::clamp( - (g.height() - size->height()) / 2, - 0, - st::historyFastShareBottom); + ensureSummarizeButton(); + if (const auto size = rightActionSize(); size || _summarize) { + const auto rightActionWidth = size + ? size->width() + : _summarize->size().width(); + const auto fastShareSkip = size + ? std::clamp( + (g.height() - size->height()) / 2, + 0, + st::historyFastShareBottom) + : st::historyFastShareBottom; const auto fastShareLeft = hasRightLayout() - ? (g.left() - size->width() - st::historyFastShareLeft) + ? (g.left() + - (_summarize ? 0 : rightActionWidth) + - st::historyFastShareLeft) : (g.left() + g.width() + st::historyFastShareLeft); - const auto fastShareTop = data()->isSponsored() - ? g.top() + fastShareSkip - : g.top() + g.height() - fastShareSkip - size->height(); - const auto o = p.opacity(); - if (selectionModeResult.progress > 0) { - p.setOpacity(1. - selectionModeResult.progress); + const auto fastShareTop = g.top() + (data()->isSponsored() + ? fastShareSkip + : g.height() - fastShareSkip - (size ? size->height() : 0)); + if (size) { + const auto o = p.opacity(); + if (selectionModeResult.progress > 0) { + p.setOpacity(1. - selectionModeResult.progress); + } + drawRightAction( + p, + context, + fastShareLeft, + fastShareTop, + width()); + if (selectionModeResult.progress > 0) { + p.setOpacity(o); + } } - drawRightAction(p, context, fastShareLeft, fastShareTop, width()); - if (selectionModeResult.progress > 0) { - p.setOpacity(o); + if (_summarize) { + paintSummarize( + p, + fastShareLeft, + fastShareTop, + !context.outbg, + context, + g); } } @@ -1833,6 +1893,23 @@ void Message::paintReplyInfo( } } +void Message::paintSummaryHeaderInfo( + Painter &p, + QRect &trect, + const PaintContext &context) const { + if (const auto summaryHeader = Get()) { + summaryHeader->paint( + p, + this, + context, + trect.x(), + trect.y(), + trect.width(), + true); + trect.setY(trect.y() + summaryHeader->height()); + } +} + void Message::paintViaBotIdInfo( Painter &p, QRect &trect, @@ -1859,7 +1936,6 @@ void Message::paintText( const auto stm = context.messageStyle(); p.setPen(stm->historyTextFg); p.setFont(st::msgFont); - prepareCustomEmojiPaint(p, context, text()); if (const auto botTop = Get()) { botTop->text.drawLeftElided( p, @@ -1869,6 +1945,12 @@ void Message::paintText( width()); trect.setY(trect.y() + botTop->height); } + if (!context.clip.intersects(trect) + && context.skipDrawingParts == PaintContext::SkipDrawingParts::None + && !context.gestureHorizontal.translation) { + return; + } + prepareCustomEmojiPaint(p, context, text()); auto highlightRequest = context.computeHighlightCache(); text().draw(p, { .position = trect.topLeft(), @@ -2024,6 +2106,15 @@ void Message::clickHandlerPressedChanged( } else if (const auto reply = Get() ; reply && (handler == reply->link())) { toggleReplyRipple(pressed); + } else if (const auto summaryHeader = Get() + ; summaryHeader && (handler == summaryHeader->link())) { + toggleSummaryHeaderRipple(pressed); + } else if (_summarize && (handler == _summarize->link())) { + if (pressed) { + _summarize->addRipple([=] { repaint(); }); + } else { + _summarize->stopRipple(); + } } } @@ -2088,6 +2179,26 @@ void Message::toggleReplyRipple(bool pressed) { } } +void Message::toggleSummaryHeaderRipple(bool pressed) { + const auto summaryHeader = Get(); + if (!summaryHeader) { + return; + } + + if (pressed) { + if (!unwrapped()) { + const auto size = QSize( + countGeometry().width() - rect::m::sum::h(st::msgPadding), + summaryHeader->height() + - rect::m::sum::v(summaryHeader->margins())); + summaryHeader->createRippleAnimation(this, size); + } + summaryHeader->addRipple(); + } else { + summaryHeader->stopLastRipple(); + } +} + BottomRippleMask Message::bottomRippleMask(int buttonHeight) const { using namespace Ui; using namespace Images; @@ -2218,6 +2329,9 @@ void Message::unloadHeavyPart() { _fromNameStatus->custom = nullptr; _fromNameStatus->id = EmojiStatusId(); } + if (const auto summaryHeader = Get()) { + summaryHeader->unloadHeavyPart(); + } } bool Message::hasFromPhoto() const { @@ -2317,7 +2431,7 @@ TextState Message::textState( keyboardHeight = keyboard->naturalHeight(); g.setHeight(g.height() - st::msgBotKbButton.margin - keyboardHeight); - if (item->isHistoryEntry()) { + if (item->isHistoryEntry() || item->isAdminLogEntry()) { const auto keyboardPosition = QPoint(g.left(), g.top() + g.height() + st::msgBotKbButton.margin); if (QRect(keyboardPosition, QSize(g.width(), keyboardHeight)).contains(point)) { result.symbol += visibleMediaTextLen + visibleTextLen; @@ -2406,6 +2520,9 @@ TextState Message::textState( if (getStateReplyInfo(point, trect, &result)) { return result; } + if (getStateSummaryHeaderInfo(point, trect, &result)) { + return result; + } } if (entry) { auto entryHeight = entry->height(); @@ -2516,6 +2633,9 @@ TextState Message::textState( - QPoint(fastShareLeft, fastShareTop)); } } + if (_summarize && _summarize->contains(point)) { + result.link = _summarize->link(); + } } else if (media && media->isDisplayed()) { result = media->textState(point - g.topLeft(), request); if (request.onlyMessageText) { @@ -2832,6 +2952,32 @@ bool Message::getStateReplyInfo( return false; } +bool Message::getStateSummaryHeaderInfo( + QPoint point, + QRect &trect, + not_null outResult) const { + if (const auto summaryHeader = Get()) { + const auto margins = summaryHeader->margins(); + const auto height = summaryHeader->height(); + if (point.y() >= trect.top() && point.y() < trect.top() + height) { + const auto g = QRect( + trect.x(), + trect.y() + margins.top(), + trect.width(), + height - margins.top() - margins.bottom()); + if (g.contains(point)) { + if (const auto link = summaryHeader->link()) { + outResult->link = summaryHeader->link(); + summaryHeader->saveRipplePoint(point - g.topLeft()); + } + } + return true; + } + trect.setTop(trect.top() + height); + } + return false; +} + bool Message::getStateViaBotIdInfo( QPoint point, QRect &trect, @@ -2917,6 +3063,9 @@ void Message::updatePressed(QPoint point) { if (const auto reply = Get()) { trect.setTop(trect.top() + reply->height()); } + if (const auto summaryHeader = Get()) { + trect.setTop(trect.top() + summaryHeader->height()); + } if (item->Has()) { if (!displayFromName() && !displayForwardedFrom()) { trect.setTop(trect.top() + st::msgNameFont->height); @@ -3324,8 +3473,9 @@ bool Message::updateBottomInfo() { void Message::itemDataChanged() { const auto infoChanged = updateBottomInfo(); const auto reactionsChanged = updateReactions(); - - if (infoChanged || reactionsChanged) { + const auto media = this->media(); + const auto mediaChanged = media && media->updateItemData(); + if (infoChanged || reactionsChanged || mediaChanged) { history()->owner().requestViewResize(this); } else { repaint(); @@ -4426,6 +4576,11 @@ int Message::resizeContentGetHeight(int newWidth) { - st::msgPadding.left() - st::msgPadding.right()); } + if (const auto summaryHeader = Get()) { + newHeight += summaryHeader->resizeToWidth(contentWidth + - st::msgPadding.left() + - st::msgPadding.right()); + } if (needInfoDisplay()) { newHeight += (bottomInfoHeight - st::msgDateFont->height); } @@ -4573,4 +4728,42 @@ const HistoryMessageEdited *Message::displayedEditBadge() const { return data()->Get(); } +void Message::ensureSummarizeButton() const { + if (data()->canBeSummarized() + /*&& item->originalText().text.size() >= kSummarizeThreshold*/) { + if (!_summarize) { + _summarize + = std::make_unique(data(), false, true); + } + } else { + _summarize = nullptr; + } +} + +void Message::paintSummarize( + Painter &p, + int x, + int y, + bool right, + const PaintContext &context, + QRect g) const { + if (!_summarize) { + return; + } + const auto s = _summarize->size(); + const auto bottomY = y - s.height() - st::msgDateImgDelta; + if (bottomY < g.top()) { + return; + } + const auto topY = g.top(); + const auto buttonY = std::min( + std::max(topY, context.area.y() + st::msgDateImgDelta), + bottomY); + _summarize->paint( + p, + x - (right ? 0 : s.width()), + buttonY, + context); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index a873adc690..5d865850bc 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -31,6 +31,7 @@ namespace HistoryView { class ViewButton; class WebPage; +class TranscribeButton; namespace Reactions { class InlineList; @@ -197,6 +198,7 @@ private: void toggleRightActionRipple(bool pressed); void toggleReplyRipple(bool pressed); + void toggleSummaryHeaderRipple(bool pressed); void paintCommentsButton( Painter &p, @@ -218,6 +220,10 @@ private: Painter &p, QRect &trect, const PaintContext &context) const; + void paintSummaryHeaderInfo( + Painter &p, + QRect &trect, + const PaintContext &context) const; // This method draws "via @bot" if it is not painted // in forwarded info or in from name. void paintViaBotIdInfo( @@ -250,6 +256,10 @@ private: QPoint point, QRect &trect, not_null outResult) const; + bool getStateSummaryHeaderInfo( + QPoint point, + QRect &trect, + not_null outResult) const; bool getStateViaBotIdInfo( QPoint point, QRect &trect, @@ -292,6 +302,15 @@ private: void refreshInfoSkipBlock(HistoryItem *textItem); [[nodiscard]] int monospaceMaxWidth() const; + void ensureSummarizeButton() const; + void paintSummarize( + Painter &p, + int x, + int y, + bool right, + const PaintContext &context, + QRect g) const; + void updateViewButtonExistence(); [[nodiscard]] int viewButtonHeight() const; @@ -311,6 +330,7 @@ private: mutable std::unique_ptr _viewButton; std::unique_ptr _topicButton; mutable std::unique_ptr _comments; + mutable std::unique_ptr _summarize; mutable Ui::Text::String _fromName; mutable std::unique_ptr _fromNameStatus; diff --git a/Telegram/SourceFiles/history/view/history_view_reply.cpp b/Telegram/SourceFiles/history/view/history_view_reply.cpp index 70d996a373..bffd8f9c57 100644 --- a/Telegram/SourceFiles/history/view/history_view_reply.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reply.cpp @@ -428,6 +428,7 @@ void Reply::setLinkFrom( not_null data) { const auto weak = base::make_weak(view); const auto &fields = data->fields(); + const auto isAdminLogEntry = view->data()->isAdminLogEntry(); const auto externalChannelId = peerToChannel(fields.externalPeerId); const auto messageId = fields.messageId; const auto highlight = MessageHighlightId{ @@ -477,7 +478,9 @@ void Reply::setLinkFrom( }; const auto message = data->resolvedMessage.get(); const auto story = data->resolvedStory.get(); - _link = message + _link = isAdminLogEntry + ? std::make_shared(externalLink) + : message ? JumpToMessageClickHandler(message, returnToId, highlight) : story ? JumpToStoryClickHandler(story) @@ -824,9 +827,11 @@ void Reply::paint( } if (_ripple.animation) { + _ripple.lastPaintedPoint = { x, y }; _ripple.animation->paint(p, x, y, w, &cache->bg2); if (_ripple.animation->empty()) { _ripple.animation.reset(); + _ripple.lastPaintedPoint = {}; } } @@ -988,7 +993,7 @@ void Reply::createRippleAnimation( Ui::RippleAnimation::RoundRectMask( size, st::messageQuoteStyle.radius), - [=] { view->repaint(); }); + [=] { view->repaint(QRect(_ripple.lastPaintedPoint, size)); }); } void Reply::saveRipplePoint(QPoint point) const { diff --git a/Telegram/SourceFiles/history/view/history_view_reply.h b/Telegram/SourceFiles/history/view/history_view_reply.h index 297e4cc9d1..7cd8cd35a8 100644 --- a/Telegram/SourceFiles/history/view/history_view_reply.h +++ b/Telegram/SourceFiles/history/view/history_view_reply.h @@ -139,8 +139,9 @@ private: mutable PeerData *_externalSender = nullptr; mutable PeerData *_colorPeer = nullptr; mutable struct { - mutable std::unique_ptr animation; + std::unique_ptr animation; QPoint lastPoint; + QPoint lastPaintedPoint; } _ripple; mutable Ui::Text::String _name; mutable Ui::Text::String _text; diff --git a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp index ed8b38125f..e540660cb8 100644 --- a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp +++ b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp @@ -77,7 +77,8 @@ bool CanScheduleUntilOnline(not_null peer) { return !user->isSelf() && !user->isBot() && !user->lastseen().isHidden() - && !user->starsPerMessageChecked(); + && !user->starsPerMessageChecked() + && !user->isNotificationsUser(); } return false; } diff --git a/Telegram/SourceFiles/history/view/history_view_summary_header.cpp b/Telegram/SourceFiles/history/view/history_view_summary_header.cpp new file mode 100644 index 0000000000..1855bcd62b --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_summary_header.cpp @@ -0,0 +1,319 @@ +/* +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 "history/view/history_view_summary_header.h" + +#include "api/api_transcribes.h" +#include "apiwrap.h" +#include "core/click_handler_types.h" +#include "core/ui_integration.h" +#include "data/data_session.h" +#include "history/history_item_components.h" +#include "history/history.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "ui/boxes/about_cocoon_box.h" +#include "ui/chat/chat_style.h" +#include "ui/effects/ripple_animation.h" +#include "ui/layers/generic_box.h" +#include "ui/painter.h" +#include "ui/power_saving.h" +#include "ui/rect.h" +#include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" +#include "ui/ui_utility.h" +#include "window/window_session_controller.h" +#include "styles/style_chat.h" + +namespace HistoryView { + +SummaryHeader::SummaryHeader() +: _name(st::maxSignatureSize / 2) +, _text(st::maxSignatureSize / 2) { +} + +SummaryHeader &SummaryHeader::operator=(SummaryHeader &&other) = default; + +SummaryHeader::~SummaryHeader() = default; + +void SummaryHeader::update(not_null view) { + const auto item = view->data(); + + if (!_animation) { + ensureAnimation(); + } + if (!_lottie) { + ensureLottie(); + } + + _text.setText( + st::defaultTextStyle, + tr::lng_summarize_header_about(tr::now)); + + _name.setText( + st::msgNameStyle, + tr::lng_summarize_header_title(tr::now)); + + _maxWidth = st::historyReplyPadding.left() + + st::maxSignatureSize / 2 + + st::historyReplyPadding.right(); + + const auto session = &item->history()->session(); + const auto itemId = item->fullId(); + _link = std::make_shared([=](ClickContext context) { + if (Rect(iconRect().size()).contains(_iconRipple.lastPoint)) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + controller->show(Box(Ui::AboutCocoonBox)); + return; + } + } + if (const auto item = session->data().message(itemId)) { + session->api().transcribes().toggleSummary(item, nullptr); + } + }); +} + +bool SummaryHeader::isNameUpdated(not_null view) const { + return false; +} + +int SummaryHeader::resizeToWidth(int width) const { + _ripple.animation = nullptr; + _height = st::historyReplyPadding.top() + + st::msgServiceNameFont->height * 2 + + st::historyReplyPadding.bottom(); + _width = width; + return _height; +} + +int SummaryHeader::height() const { + return _height + st::historyReplyTop + st::historyReplyBottom; +} + +QMargins SummaryHeader::margins() const { + return QMargins(0, st::historyReplyTop, 0, st::historyReplyBottom); +} + +void SummaryHeader::paint( + Painter &p, + not_null view, + const Ui::ChatPaintContext &context, + int x, + int y, + int w, + bool inBubble) const { + const auto st = context.st; + const auto stm = context.messageStyle(); + + y += st::historyReplyTop; + const auto rect = QRect(x, y, w, _height); + const auto colorPattern = 0; + const auto cache = !inBubble + ? st->serviceReplyCache(colorPattern).get() + : stm->replyCache[colorPattern].get(); + const auto "eSt = st::messageQuoteStyle; + const auto rippleColor = cache->bg; + const auto nameColor = !inBubble + ? st->msgImgReplyBarColor()->c + : stm->msgServiceFg->c; + if (!inBubble) { + cache->bg = QColor(0, 0, 0, 0); + } + Ui::Text::ValidateQuotePaintCache(*cache, quoteSt); + Ui::Text::FillQuotePaint(p, rect, *cache, quoteSt); + if (!inBubble) { + cache->bg = rippleColor; + } + + if (!_lottie) { + ensureLottie(); + } + { + const auto r = iconRect().translated(x, y); + const auto lottieX = r.x() + st::historySummaryHeaderIconSizeInner; + const auto lottieY = r.y() + st::historySummaryHeaderIconSizeInner; + _lottie->paint(p, lottieX, lottieY, nameColor); + if (_iconRipple.animation) { + _iconRipple.animation->paint( + p, + r.x(), + r.y(), + r.width(), + &rippleColor); + if (_iconRipple.animation->empty()) { + _iconRipple.animation.reset(); + } + } + } + + if (!_animation) { + ensureAnimation(); + } + { + const auto size = QSize(w, _height); + if (_animation->cachedSize != size) { + _animation->path = QPainterPath(); + _animation->path.addRoundedRect( + QRect(0, 0, w, _height), + quoteSt.radius, + quoteSt.radius); + _animation->cachedSize = size; + } + p.translate(x, y); + p.setClipPath(_animation->path); + _animation->particles.setColor(nameColor); + const auto paused = context.paused || On(PowerSaving::kChatEffects); + _animation->particles.paint( + p, + QRect(0, 0, w, _height), + context.now, + paused); + if (!paused) { + const auto session = &view->history()->session(); + const auto r = QRect(x, y, w, _height); + Ui::PostponeCall(session, [=, itemId = view->data()->fullId()] { + if (const auto i = session->data().message(itemId)) { + session->data().requestItemRepaint(i, r); + } + }); + } + p.setClipping(false); + p.translate(-x, -y); + } + + auto textLeft = x + st::historyReplyPadding.left(); + auto textTop = y + st::historyReplyPadding.top() + + st::msgServiceNameFont->height; + if (w > st::historyReplyPadding.left()) { + const auto iconSpace = st::historySummaryHeaderIconSize + + st::historySummaryHeaderIconSizeInner * 2; + const auto textw = w + - st::historyReplyPadding.left() + - st::historyReplyPadding.right() + - iconSpace; + const auto namew = textw; + if (namew > 0) { + p.setPen(nameColor); + _name.drawLeftElided( + p, + x + st::historyReplyPadding.left(), + y + st::historyReplyPadding.top(), + namew, + w + 2 * x, + 1); + + p.setPen(inBubble + ? stm->historyTextFg + : st->msgImgReplyBarColor()); + view->prepareCustomEmojiPaint(p, context, _text); + auto replyToTextPalette = &(!inBubble + ? st->imgReplyTextPalette() + : stm->replyTextPalette); + _text.draw(p, { + .position = { textLeft, textTop }, + .availableWidth = textw, + .palette = replyToTextPalette, + .spoiler = Ui::Text::DefaultSpoilerCache(), + .now = context.now, + .pausedEmoji = (context.paused + || On(PowerSaving::kEmojiChat)), + .pausedSpoiler = (context.paused + || On(PowerSaving::kChatSpoiler)), + .elisionLines = 1, + }); + p.setTextPalette(stm->textPalette); + } + } + + if (_ripple.animation) { + _ripple.animation->paint(p, x, y, w, &rippleColor); + if (_ripple.animation->empty()) { + _ripple.animation.reset(); + } + } +} + +void SummaryHeader::createRippleAnimation( + not_null view, + QSize size) { + _ripple.animation = std::make_unique( + st::defaultRippleAnimation, + Ui::RippleAnimation::RoundRectMask( + size, + st::messageQuoteStyle.radius), + [=] { view->repaint(); }); + const auto rippleIconSize = st::historySummaryHeaderIconSize; + _iconRipple.animation = std::make_unique( + st::defaultRippleAnimation, + Ui::RippleAnimation::EllipseMask(Size(rippleIconSize)), + [=] { view->repaint(); }); +} + +void SummaryHeader::saveRipplePoint(QPoint point) const { + _ripple.lastPoint = point; + const auto rect = iconRect(); + if (rect.contains(point)) { + _iconRipple.lastPoint = point - rect.topLeft(); + } else { + _iconRipple.lastPoint = QPoint(-1, -1); + } +} + +void SummaryHeader::addRipple() { + if (_iconRipple.lastPoint.x() >= 0 && _iconRipple.animation) { + _iconRipple.animation->add(_iconRipple.lastPoint); + } else if (_ripple.animation) { + _ripple.animation->add(_ripple.lastPoint); + } +} + +void SummaryHeader::stopLastRipple() { + if (_ripple.animation) { + _ripple.animation->lastStop(); + } + if (_iconRipple.animation) { + _iconRipple.animation->lastStop(); + } +} + +void SummaryHeader::unloadHeavyPart() { + _unloadTime = crl::now(); + _animation = nullptr; + _ripple.animation = nullptr; + _iconRipple.animation = nullptr; + _lottie = nullptr; +} + +QRect SummaryHeader::iconRect() const { + const auto size = st::historySummaryHeaderIconSize; + const auto shift = st::historySummaryHeaderIconSizeInner; + return QRect(_width - size - shift, (_height - size) / 2, size, size); +} + +void SummaryHeader::ensureAnimation() const { + using namespace Ui; + _animation = std::make_unique(Animation{ + .particles = StarParticles( + StarParticles::Type::Right, + 15, + st::lineWidth * 8), + }); + _animation->particles.setSpeed(0.05); +} + +void SummaryHeader::ensureLottie() const { + _lottie = Lottie::MakeIcon(Lottie::IconDescriptor{ + .name = u"cocoon"_q, + .color = &st::attentionButtonFg, + .sizeOverride = Size(st::historySummaryHeaderIconSize + - st::historySummaryHeaderIconSizeInner * 2), + .colorizeUsingAlpha = true, + }); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_summary_header.h b/Telegram/SourceFiles/history/view/history_view_summary_header.h new file mode 100644 index 0000000000..d9f37050ea --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_summary_header.h @@ -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 +*/ +#pragma once + +#include "history/view/history_view_element.h" +#include "ui/effects/ministar_particles.h" +#include "lottie/lottie_icon.h" + +namespace HistoryView { + +class SummaryHeader final : public RuntimeComponent { +public: + SummaryHeader(); + SummaryHeader(const SummaryHeader &other) = delete; + SummaryHeader(SummaryHeader &&other) = delete; + SummaryHeader &operator=(const SummaryHeader &other) = delete; + SummaryHeader &operator=(SummaryHeader &&other); + ~SummaryHeader(); + + void update(not_null view); + + [[nodiscard]] bool isNameUpdated(not_null view) const; + [[nodiscard]] int resizeToWidth(int width) const; + [[nodiscard]] int height() const; + [[nodiscard]] QMargins margins() const; + + void paint( + Painter &p, + not_null view, + const Ui::ChatPaintContext &context, + int x, + int y, + int w, + bool inBubble) const; + + void createRippleAnimation(not_null view, QSize size); + void saveRipplePoint(QPoint point) const; + void addRipple(); + void stopLastRipple(); + + [[nodiscard]] int maxWidth() const { + return _maxWidth; + } + [[nodiscard]] ClickHandlerPtr link() const { + return _link; + } + + void unloadHeavyPart(); + +private: + void ensureAnimation() const; + void ensureLottie() const; + + [[nodiscard]] QRect iconRect() const; + struct Animation { + Ui::StarParticles particles; + QPainterPath path; + QSize cachedSize; + }; + + ClickHandlerPtr _link; + mutable std::unique_ptr _animation; + mutable struct { + mutable std::unique_ptr animation; + QPoint lastPoint; + } _ripple; + mutable struct { + mutable std::unique_ptr animation; + QPoint lastPoint; + } _iconRipple; + mutable Ui::Text::String _name; + mutable Ui::Text::String _text; + mutable int _maxWidth = 0; + mutable int _height = 0; + mutable int _width = 0; + mutable std::unique_ptr _lottie; + mutable crl::time _unloadTime = 0; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index f87374a6bd..1c963a981b 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -84,6 +84,16 @@ namespace { constexpr auto kEmojiInteractionSeenDuration = 3 * crl::time(1000); +class MenuToggleButton final : public Ui::IconButton { +public: + using IconButton::IconButton; + +protected: + void contextMenuEvent(QContextMenuEvent *e) override { + Ui::AbstractButton::clicked(Qt::KeyboardModifiers(), Qt::LeftButton); + } +}; + [[nodiscard]] inline bool HasGroupCallMenu(not_null peer) { return !peer->isUser() && !peer->groupCall() @@ -135,7 +145,9 @@ TopBarWidget::TopBarWidget( , _groupCall(this, st::topBarGroupCall) , _search(this, st::topBarSearch) , _infoToggle(this, st::topBarInfo) -, _menuToggle(this, st::topBarMenuToggle) +, _menuToggle( + object_ptr::fromRaw( + Ui::CreateChild(this, st::topBarMenuToggle))) , _recentActions(this, st::topBarRecentActions) , _admins(this, st::topBarAdmins) , _titlePeerText(st::windowMinWidth / 3) diff --git a/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp b/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp index 1688b8f6c1..8a3a5606fa 100644 --- a/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp @@ -16,14 +16,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "main/main_session.h" #include "lang/lang_keys.h" +#include "settings/settings_premium.h" #include "ui/chat/chat_style.h" #include "ui/effects/radial_animation.h" #include "ui/effects/ripple_animation.h" +#include "ui/effects/ministar_particles.h" #include "ui/painter.h" +#include "ui/power_saving.h" #include "ui/rect.h" +#include "ui/ui_utility.h" #include "api/api_transcribes.h" #include "apiwrap.h" #include "styles/style_chat.h" +#include "styles/style_media_view.h" #include "window/window_session_controller.h" namespace HistoryView { @@ -51,10 +56,12 @@ void ClipPainterForLock(QPainter &p, bool roundview, const QRect &r) { TranscribeButton::TranscribeButton( not_null item, - bool roundview) + bool roundview, + bool summarize) : _item(item) , _roundview(roundview) -, _size(!roundview +, _summarize(summarize) +, _size(!roundview && !_summarize ? st::historyTranscribeSize : QSize(st::historyFastShareSize, st::historyFastShareSize)) { } @@ -65,21 +72,34 @@ QSize TranscribeButton::size() const { return _size; } -void TranscribeButton::setLoading(bool loading, Fn update) { +void TranscribeButton::setLoading(bool loading) { if (_loading == loading) { return; } _loading = loading; if (_loading) { + const auto session = &_item->history()->session(); _animation = std::make_unique( - update, - st::defaultInfiniteRadialAnimation); + [=, itemId = _item->fullId()] { + if (const auto item = session->data().message(itemId)) { + session->data().requestItemRepaint( + item, + _lastPaintedPoint.isNull() + ? QRect() + : QRect(_lastPaintedPoint, size())); + } + }, + st::historyTranscribeRadialAnimation); _animation->start(); } else if (_animation) { - _animation->stop(); + _animation->stopWithFade(); } } +bool TranscribeButton::loading() const { + return _loading; +} + void TranscribeButton::paint( QPainter &p, int x, @@ -88,7 +108,7 @@ void TranscribeButton::paint( auto hq = PainterHighQualityEnabler(p); const auto opened = _openedAnimation.value(_opened ? 1. : 0.); const auto stm = context.messageStyle(); - if (_roundview) { + if (_roundview || _summarize) { _lastPaintedPoint = { x, y }; const auto r = QRect(QPoint(x, y), size()); @@ -110,7 +130,43 @@ void TranscribeButton::paint( p.setBrush(context.st->msgServiceBg()); p.drawEllipse(r); - if (!_loading && hasLock()) { + if (_summarize) { + if (!_particles) { + _particles = std::make_unique( + Ui::StarParticles::Type::RadialInside, + 7, + st::lineWidth * 4); + _particles->setColor(st::msgServiceFg->c); + _particles->setSpeed(0.2); + } + p.setClipRegion(QRegion(r, QRegion::Ellipse)); + const auto paused = context.paused + || (!_summarizeHovered && !_loading) + || On(PowerSaving::kChatEffects); + _particles->paint(p, r, context.now, paused); + p.setClipping(false); + if (hasLock()) { + context.st->historyFastTranscribeLock().paint( + p, + r.topLeft() + st::historyFastSummaryLockPos, + r.width()); + } + if (!paused) { + const auto session = &_item->history()->session(); + Ui::PostponeCall(session, [=, itemId = _item->fullId()] { + if (const auto i = session->data().message(itemId)) { + session->data().requestItemRepaint(i, r); + } + }); + } + (_item->history()->session().api().transcribes().summary( + _item).shown + ? st::mediaviewFullScreenButton.icon + : st::mediaviewFullScreenOutIcon).paintInCenter( + p, + r, + st::msgServiceFg->c); + } else if (!_loading && hasLock()) { ClipPainterForLock(p, true, r); context.st->historyFastTranscribeIcon().paintInCenter(p, r); p.setClipping(false); @@ -222,10 +278,14 @@ void TranscribeButton::paint( bool TranscribeButton::hasLock() const { const auto session = &_item->history()->session(); + if (session->premium()) { + return false; + } const auto transcribes = &session->api().transcribes(); - if (session->premium() - || transcribes->freeFor(_item) - || transcribes->trialsCount()) { + if (_summarize) { + return transcribes->summary(_item).premiumRequired; + } + if (transcribes->freeFor(_item) || transcribes->trialsCount()) { return false; } const auto until = transcribes->trialsRefreshAt(); @@ -259,20 +319,28 @@ ClickHandlerPtr TranscribeButton::link() { } const auto session = &_item->history()->session(); const auto id = _item->fullId(); + const auto summarize = _summarize; _link = std::make_shared([=](ClickContext context) { const auto item = session->data().message(id); if (!item) { return; } if (session->premium()) { - return session->api().transcribes().toggle(item); + auto &transcribes = session->api().transcribes(); + return summarize + ? transcribes.toggleSummary(item, nullptr) + : transcribes.toggle(item); } const auto my = context.other.value(); if (hasLock()) { if (const auto controller = my.sessionWindow.get()) { - ShowPremiumPreviewBox( - controller, - PremiumFeature::VoiceToText); + if (summarize) { + Settings::ShowPremium(controller, u"summary"_q); + } else { + ShowPremiumPreviewBox( + controller, + PremiumFeature::VoiceToText); + } } } else { const auto max = session->api().transcribes().trialsMaxLengthMs(); @@ -288,7 +356,19 @@ ClickHandlerPtr TranscribeButton::link() { } } } - session->api().transcribes().toggle(item); + if (summarize) { + const auto weak = my.sessionWindow; + session->api().transcribes().toggleSummary(item, [=] { + if (const auto strong = weak.get()) { + Settings::ShowPremium(strong, u"summary"_q); + // ShowPremiumPreviewBox( + // strong, + // PremiumFeature::VoiceToText); + } + }); + } else { + session->api().transcribes().toggle(item); + } } }); return _link; @@ -296,7 +376,12 @@ ClickHandlerPtr TranscribeButton::link() { bool TranscribeButton::contains(const QPoint &p) { _lastStatePoint = p - _lastPaintedPoint; - return QRect(_lastPaintedPoint, size()).contains(p); + if (_summarize) { + _summarizeHovered = QRect(_lastPaintedPoint, size()).contains(p); + return _summarizeHovered; + } else { + return QRect(_lastPaintedPoint, size()).contains(p); + } } void TranscribeButton::addRipple(Fn callback) { diff --git a/Telegram/SourceFiles/history/view/history_view_transcribe_button.h b/Telegram/SourceFiles/history/view/history_view_transcribe_button.h index db318eb5a2..f6f431743b 100644 --- a/Telegram/SourceFiles/history/view/history_view_transcribe_button.h +++ b/Telegram/SourceFiles/history/view/history_view_transcribe_button.h @@ -13,6 +13,7 @@ namespace Ui { struct ChatPaintContext; class InfiniteRadialAnimation; class RippleAnimation; +class StarParticles; } // namespace Ui namespace HistoryView { @@ -21,13 +22,17 @@ using PaintContext = Ui::ChatPaintContext; class TranscribeButton final { public: - explicit TranscribeButton(not_null item, bool roundview); + explicit TranscribeButton( + not_null item, + bool roundview, + bool summarize = false); ~TranscribeButton(); [[nodiscard]] QSize size() const; void setOpened(bool opened, Fn update); - void setLoading(bool loading, Fn update); + void setLoading(bool loading); + [[nodiscard]] bool loading() const; void paint(QPainter &p, int x, int y, const PaintContext &context); void addRipple(Fn callback); void stopRipple() const; @@ -40,17 +45,20 @@ private: const not_null _item; const bool _roundview = false; + const bool _summarize = false; const QSize _size; - mutable std::unique_ptr _animation; - std::unique_ptr _ripple; ClickHandlerPtr _link; - QString _text; + std::unique_ptr _animation; + std::unique_ptr _particles; + std::unique_ptr _ripple; Ui::Animations::Simple _openedAnimation; - bool _loading = false; - bool _opened = false; + QString _text; QPoint _lastPaintedPoint; QPoint _lastStatePoint; + bool _summarizeHovered = false; + bool _loading = false; + bool _opened = false; }; diff --git a/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp b/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp index 48085dc18b..887c7ff0d3 100644 --- a/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp @@ -8,25 +8,34 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_translate_bar.h" #include "boxes/translate_box.h" +#include "ui/boxes/about_cocoon_box.h" +#include "chat_helpers/stickers_lottie.h" #include "core/application.h" #include "core/core_settings.h" +#include "core/ui_integration.h" +#include "data/stickers/data_custom_emoji.h" #include "data/data_changes.h" +#include "data/data_document.h" #include "history/history.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "settings/settings_credits_graphics.h" // CreditsEntryBoxStyleOverrides +#include "ui/widgets/labels.h" +#include "ui/widgets/shadow.h" #include "ui/effects/ripple_animation.h" #include "ui/boxes/choose_language_box.h" // EditSkipTranslationLanguages. #include "ui/layers/box_content.h" +#include "ui/layers/generic_box.h" #include "ui/widgets/menu/menu_item_base.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" +#include "ui/widgets/menu/menu_multiline_action.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/labels.h" #include "ui/widgets/popup_menu.h" -#include "ui/widgets/shadow.h" #include "ui/painter.h" #include "window/window_session_controller.h" #include "styles/style_chat.h" +#include "styles/style_layers.h" #include "styles/style_menu_icons.h" #include @@ -39,7 +48,7 @@ constexpr auto kToastDuration = 4 * crl::time(1000); class TwoTextAction final : public Ui::Menu::ItemBase { public: TwoTextAction( - not_null parent, + not_null parent, const style::Menu &st, const QString &text1, const QString &text2, @@ -81,7 +90,7 @@ TextParseOptions MenuTextOptions = { }; TwoTextAction::TwoTextAction( - not_null parent, + not_null parent, const style::Menu &st, const QString &text1, const QString &text2, @@ -89,7 +98,7 @@ TwoTextAction::TwoTextAction( const style::icon *icon, const style::icon *iconOver) : ItemBase(parent, st) -, _dummyAction(new QAction(parent)) +, _dummyAction(Ui::CreateChild(parent)) , _st(st) , _icon(icon) , _iconOver(iconOver) @@ -98,8 +107,8 @@ TwoTextAction::TwoTextAction( + _st.itemStyle.font->height + st::ttlItemTimerFont->height + st::ttlItemPadding.bottom()) { - initResizeHook(parent->sizeValue()); - setClickedCallback(std::move(callback)); + fitToMenuWidth(); + setActionTriggered(std::move(callback)); paintRequest( ) | rpl::on_next([=] { @@ -476,6 +485,36 @@ void TranslateBar::showMenu(base::unique_qptr menu) { tr::lng_translate_menu_hide(tr::now), hideBar, &st::menuIconCancel); + _menu->addSeparator(); + + const auto cocoon = ChatHelpers::GenerateLocalTgsSticker( + &_history->session(), + u"cocoon"_q); + cocoon->overrideEmojiUsesTextColor(true); + auto item = base::make_unique_q( + _menu->menu(), + st::defaultMenu, + st::historyTranslateCocoonLabel, + QPoint( + st::defaultMenu.itemPadding.left(), + st::defaultMenu.itemPadding.top()), + tr::lng_translate_cocoon_menu( + tr::now, + lt_emoji, + Data::SingleCustomEmoji(cocoon), + lt_link, + tr::link(tr::lng_translate_cocoon_link(tr::now, tr::bold)), + tr::rich), + Core::TextContext({ + .session = &_history->session(), + .customEmojiLoopLimit = -1, + })); + item->clicks( + ) | rpl::on_next([controller = _controller] { + controller->show(Box(Ui::AboutCocoonBox)); + }, item->lifetime()); + _menu->addAction(std::move(item)); + _menu->popup(_wrap.mapToGlobal( QPoint(_wrap.width(), 0) + st::historyTranslateMenuPosition)); } diff --git a/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp index 948b7a2517..1cb8540cd3 100644 --- a/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp +++ b/Telegram/SourceFiles/history/view/history_view_translate_tracker.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "api/api_text_entities.h" +#include "api/api_transcribes.h" #include "core/application.h" #include "core/core_settings.h" #include "data/data_changes.h" @@ -157,6 +158,8 @@ bool TranslateTracker::add( void TranslateTracker::switchTranslation( not_null item, LanguageId id) { + _history->session().api().transcribes().checkSummaryToTranslate( + item->fullId()); if (item->translationShowRequiresRequest(id)) { _itemsToRequest.emplace( item->fullId(), diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp index 0ce2500b49..93eed91e1f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp @@ -12,7 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/history_item_components.h" +#include "history/history_item_helpers.h" #include "history/view/history_view_element.h" +#include "lang/lang_keys.h" #include "main/main_session.h" #include "styles/style_chat.h" @@ -42,10 +44,55 @@ Dice::Dice(not_null parent, not_null dice) if (_showLastFrame) { _drawingEnd = true; } + if (const auto outcome = _dice->diceGameOutcome()) { + _outcomeSet = true; + _outcomeValue = _dice->value(); + _outcomeNanoTon = outcome.nanoTon; + _outcomeStakeNanoTon = outcome.stakeNanoTon; + _outcomeStartedUnknown = (_outcomeValue == 0); + updateOutcomeMessage(); + } } Dice::~Dice() = default; +void Dice::updateOutcomeMessage() { + if ((_outcomeStartedUnknown && !_outcomeLastPainted) + || (!_outcomeValue && !_outcomeNanoTon)) { + _parent->setServicePostMessage({}); + return; + } + const auto item = _parent->data(); + const auto from = item->from(); + const auto out = item->out() || from->isSelf(); + const auto won = (_outcomeNanoTon - _outcomeStakeNanoTon); + const auto amount = tr::marked(QString::fromUtf8("\xf0\x9f\x92\x8e") + + " " + + QString::number(std::abs(won) / 1e9)); + const auto text = out + ? (won >= 0 + ? tr::lng_action_stake_game_won_you + : tr::lng_action_stake_game_lost_you)( + tr::now, + lt_amount, + amount, + tr::marked) + : (won >= 0 + ? tr::lng_action_stake_game_won + : tr::lng_action_stake_game_lost)( + tr::now, + lt_from, + tr::link(st::wrap_rtl(from->name()), 1), + lt_amount, + amount, + tr::marked); + auto prepared = PreparedServiceText{ text }; + if (!out) { + prepared.links.push_back(from->createOpenLink()); + } + _parent->setServicePostMessage(prepared, _link); +} + QSize Dice::countOptimalSize() { return _start ? _start->countOptimalSize() : Sticker::EmojiSize(); } @@ -54,6 +101,28 @@ ClickHandlerPtr Dice::link() { return _link; } +bool Dice::updateItemData() { + const auto outcome = _dice->diceGameOutcome(); + const auto outcomeSet = !!outcome; + const auto outcomeNanoTon = outcomeSet ? outcome.nanoTon : 0; + const auto outcomeStakeNanoTon = outcomeSet ? outcome.stakeNanoTon : 0; + const auto outcomeValue = _dice->value(); + if (_outcomeSet == outcomeSet + && _outcomeNanoTon == outcomeNanoTon + && _outcomeStakeNanoTon == outcomeStakeNanoTon + && _outcomeValue == outcomeValue) { + return false; + } + _outcomeSet = outcomeSet; + _outcomeNanoTon = outcomeNanoTon; + _outcomeStakeNanoTon = outcomeStakeNanoTon; + _outcomeValue = outcomeValue; + if (_outcomeSet) { + updateOutcomeMessage(); + } + return true; +} + void Dice::draw(Painter &p, const PaintContext &context, const QRect &r) { if (!_start) { if (const auto document = Lookup(_parent, _dice->emoji(), 0)) { @@ -76,6 +145,15 @@ void Dice::draw(Painter &p, const PaintContext &context, const QRect &r) { } if (_drawingEnd) { _end->draw(p, context, r); + if (!_outcomeLastPainted && _end->stoppedOnLastFrame()) { + _outcomeLastPainted = true; + if (_outcomeSet) { + crl::on_main(this, [=] { + updateOutcomeMessage(); + _parent->history()->owner().requestViewResize(_parent); + }); + } + } } else if (_start) { _start->draw(p, context, r); if (_end diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.h b/Telegram/SourceFiles/history/view/media/history_view_dice.h index 68a5368d0f..4a225ce74a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.h +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/weak_ptr.h" #include "history/view/media/history_view_media_unwrapped.h" #include "history/view/media/history_view_sticker.h" @@ -16,7 +17,9 @@ class MediaDice; namespace HistoryView { -class Dice final : public UnwrappedMedia::Content { +class Dice final + : public UnwrappedMedia::Content + , public base::has_weak_ptr { public: Dice(not_null parent, not_null dice); ~Dice(); @@ -42,14 +45,24 @@ public: } } + bool updateItemData() override; + private: + void updateOutcomeMessage(); + const not_null _parent; const not_null _dice; ClickHandlerPtr _link; std::optional _start; std::optional _end; + int64 _outcomeNanoTon = 0; + int64 _outcomeStakeNanoTon = 0; + int _outcomeValue = 0; mutable bool _showLastFrame = false; mutable bool _drawingEnd = false; + bool _outcomeSet = false; + bool _outcomeLastPainted = false; + bool _outcomeStartedUnknown = false; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 7d49b07fec..2193e2810c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "lottie/lottie_icon.h" #include "storage/localstorage.h" -#include "lottie/lottie_icon.h" #include "main/main_session.h" #include "media/player/media_player_float.h" // Media::Player::RoundPainter. #include "media/audio/media_audio.h" @@ -447,13 +446,12 @@ QSize Document::countOptimalSize() { const auto &entry = transcribes->entry(_realParent); const auto update = [=] { repaint(); }; voice->transcribe->setLoading( - entry.shown && (entry.requestId || entry.pending), - update); + entry.shown && (entry.requestId || entry.pending)); const auto pending = entry.pending; auto descriptor = pending ? Lottie::IconDescriptor{ .name = u"transcribe_loading"_q, - .color = &st::historyTextInFg, + .color = &st::attentionButtonFg, // Any contrast. .sizeOverride = Size(st::historyTranscribeLoadingSize), .colorizeUsingAlpha = true, } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 5b925bc90a..84e2cd8e2e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -272,8 +272,7 @@ QSize Gif::countOptimalSize() { const auto &entry = _data->session().api().transcribes().entry( _realParent); _transcribe->setLoading( - entry.shown && (entry.requestId || entry.pending), - [=] { repaint(); }); + entry.shown && (entry.requestId || entry.pending)); } const auto minWidth = std::clamp( diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index adf66c7c3d..1cf8f4747e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -354,6 +354,9 @@ public: [[nodiscard]] virtual QMargins bubbleRollRepaintMargins() const { return QMargins(); } + virtual bool updateItemData() { + return false; + } virtual void paintBubbleFireworks( Painter &p, const QRect &bubble, diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h index 0259392d6f..bf2c406c87 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h @@ -58,6 +58,9 @@ public: virtual bool hasTextForCopy() const { return false; } + virtual bool updateItemData() { + return false; + } virtual ~Content() = default; }; @@ -93,6 +96,10 @@ public: std::optional reactionButtonCenterOverride() const override; QPoint resolveCustomInfoRightBottom() const override; + bool updateItemData() override { + return _content->updateItemData(); + } + void stickerClearLoopPlayed() override { _content->stickerClearLoopPlayed(); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_save_document_action.cpp b/Telegram/SourceFiles/history/view/media/history_view_save_document_action.cpp index 7e9e41c495..ad42de3592 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_save_document_action.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_save_document_action.cpp @@ -87,7 +87,7 @@ void AddSaveDocumentAction( menu->addSeparator(&st::expandedMenuSeparator); auto item = base::make_unique_q( - menu, + menu->menu(), st::saveMusicInfoMenu, st::historyHasCustomEmoji, QPoint( diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index ba2078959c..5b506f3acc 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -294,6 +294,10 @@ DocumentData *Sticker::document() { return _data; } +bool Sticker::stoppedOnLastFrame() const { + return _stopOnLastFrame && (!_lastFrameCached.isNull() || atTheEnd()); +} + void Sticker::stickerClearLoopPlayed() { if (!_playingOnce) { _oncePlayed = false; diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.h b/Telegram/SourceFiles/history/view/media/history_view_sticker.h index c975a5c1d0..536e12dd4c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.h +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.h @@ -51,6 +51,7 @@ public: ClickHandlerPtr link() override; [[nodiscard]] bool ready() const; + [[nodiscard]] bool stoppedOnLastFrame() const; DocumentData *document() override; void stickerClearLoopPlayed() override; std::unique_ptr stickerTakePlayer( diff --git a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp index 700d0481f7..038a7a5214 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp @@ -659,20 +659,7 @@ auto AuctionBg( if (state->particles) { p.setClipRect(full); - if (context.paused) { - if (!state->pausedAt) { - state->pausedAt = crl::now(); - } - const auto diff = state->pausedAt - state->pauseOffset; - state->particles->paint(p, full, diff); - } else { - if (state->pausedAt) { - state->pauseOffset += crl::now() - state->pausedAt; - state->pausedAt = 0; - } - const auto diff = context.now - state->pauseOffset; - state->particles->paint(p, full, diff); - } + state->particles->paint(p, full, context.now, context.paused); p.setClipping(false); } diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index 3bae3ce374..3b97f627be 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -915,9 +915,18 @@ void Selector::mousePressEvent(QMouseEvent *e) { } void Selector::mouseReleaseEvent(QMouseEvent *e) { - if (!_strip || _pressed != lookupSelectedIndex(e->pos())) { + if (!_strip) { return; } + if (_pressed != lookupSelectedIndex(e->pos())) { +#ifdef Q_OS_UNIX + if (!_over || e->button() != Qt::RightButton) { + return; + } +#else + return; +#endif // !Q_OS_UNIX + } _pressed = -1; const auto selected = _strip->selected(); if (selected == Strip::AddedButton::Expand) { diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp index 9391fbd48b..64c791018b 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_channel_earn_list.cpp @@ -765,13 +765,13 @@ void InnerWidget::fill() { creditsSecondLabel->resizeToWidth( available - creditsSecondLabel->pos().x()); if (!showCredits) { - const auto x = std::numeric_limits::max(); + const auto x = std::numeric_limits::max() / 2; icon->moveToLeft(x, 0); creditsLabel->moveToLeft(x, 0); creditsSecondLabel->moveToLeft(x, 0); } if (!showCurrency) { - const auto x = std::numeric_limits::max(); + const auto x = std::numeric_limits::max() / 2; majorLabel->moveToLeft(x, 0); minorLabel->moveToLeft(x, 0); secondMinorLabel->moveToLeft(x, 0); @@ -1502,19 +1502,23 @@ void AddEmojiToMajor( value ) | rpl::on_next([=](CreditsAmount v) { auto helper = Ui::Text::CustomEmojiHelper(); - auto icon = helper.paletteDependent({ .factory = [=] { - return Ui::Earn::IconCurrencyColored( - st.style.font, - !isIn - ? st::currencyFg->c - : (*isIn) - ? st::boxTextFgGood->c - : st::menuIconAttentionColor->c); - }, .margin = margins + auto icon = helper.paletteDependent({ + .factory = [=] { + return Ui::Earn::IconCurrencyColored( + st.style.font, + !isIn + ? st::currencyFg->c + : (*isIn) + ? st::boxTextFgGood->c + : st::menuIconAttentionColor->c); + }, + .margin = margins ? *margins - : st::channelEarnCurrencyCommonMargins }); + : st::channelEarnCurrencyCommonMargins + }); + auto value = MajorPart(v.abs()); label->setMarkedText( - base::duplicate(prepended).append(icon).append(MajorPart(v)), + base::duplicate(prepended).append(icon).append(value), helper.context()); }, label->lifetime()); } diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp index 0106bcb4da..cd0ea6fc25 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp @@ -53,7 +53,12 @@ constexpr auto kGiftsPerRow = 3; not_null peer) { using Type = Api::DisallowedGiftType; const auto user = peer->asUser(); - if (!user || user->isSelf()) { + if (!user) { + return gift.resale + || (!gift.info.requirePremium + && !gift.info.auction() + && !gift.info.limitedCount); + } else if (user->isSelf()) { return true; } const auto disallowedTypes = user ? user->disallowedGiftTypes() : Type(); diff --git a/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp index 619a5206a8..ff5e942166 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.cpp @@ -30,7 +30,9 @@ MemberListRow::MemberListRow( void MemberListRow::setType(Type type) { _type = type; - PeerListRowWithLink::setActionLink(!_type.adminRank.isEmpty() + PeerListRowWithLink::setActionLink(_type.canRemove + ? tr::lng_profile_delete_removed(tr::now) + : !_type.adminRank.isEmpty() ? _type.adminRank : (_type.rights == Rights::Creator) ? tr::lng_owner_badge(tr::now) @@ -44,11 +46,19 @@ MemberListRow::Type MemberListRow::type() const { } bool MemberListRow::rightActionDisabled() const { - return true; + return !canRemove(); } QMargins MemberListRow::rightActionMargins() const { const auto skip = st::contactsCheckPosition.x(); + if (canRemove()) { + const auto &st = st::defaultPeerListItem; + return QMargins( + skip, + (st.height - st.nameStyle.font->height) / 2, + st.photoPosition.x() + skip, + 0); + } return QMargins( skip, st::defaultPeerListItem.namePosition.y(), @@ -72,6 +82,10 @@ void MemberListRow::refreshStatus() { } } +bool MemberListRow::canRemove() const { + return _type.canRemove; +} + std::unique_ptr CreateMembersController( not_null navigation, not_null peer) { diff --git a/Telegram/SourceFiles/info/profile/info_profile_members_controllers.h b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.h index c867d731d5..c1c0b2f9cc 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_members_controllers.h +++ b/Telegram/SourceFiles/info/profile/info_profile_members_controllers.h @@ -28,6 +28,7 @@ public: }; struct Type { Rights rights; + bool canRemove = false; QString adminRank; }; @@ -42,6 +43,7 @@ public: not_null user() const; private: + [[nodiscard]] bool canRemove() const; Type _type; }; diff --git a/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp b/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp index a6506b646b..7722885745 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp @@ -26,7 +26,7 @@ namespace { class TextItem final : public Ui::Menu::ItemBase { public: TextItem( - not_null parent, + not_null parent, const style::Menu &st, rpl::producer &&text); @@ -67,7 +67,7 @@ private: } TextItem::TextItem( - not_null parent, + not_null parent, const style::Menu &st, rpl::producer &&text) : ItemBase(parent, st) @@ -95,7 +95,7 @@ TextItem::TextItem( }, lifetime()); _label->resizeToWidth(parent->width() - added); - initResizeHook(parent->sizeValue()); + fitToMenuWidth(); } not_null TextItem::action() const { diff --git a/Telegram/SourceFiles/info/profile/info_profile_top_bar.cpp b/Telegram/SourceFiles/info/profile/info_profile_top_bar.cpp index d6c06f0c4d..7f860acfeb 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_top_bar.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_top_bar.cpp @@ -969,8 +969,10 @@ void TopBar::setupActions(not_null controller) { return; } if (!topic - && ((chat && !chat->amCreator()) - || (channel && !channel->amCreator()))) { + && ((chat && !chat->amCreator() && !chat->hasAdminRights()) + || (channel + && !channel->amCreator() + && !channel->hasAdminRights()))) { const auto show = controller->uiShow(); const auto reportButton = Ui::CreateChild( this, @@ -2602,9 +2604,6 @@ void TopBar::setupStoryOutline(const QRect &geometry) { } void TopBar::updateStoryOutline(std::optional edgeColor) { - if (width() <= 0) { - return; - } const auto user = _peer->asUser(); const auto channel = _peer->asChannel(); if (!user && !channel) { diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 58b8236208..306c04531c 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -978,7 +978,7 @@ void CreditsRow::init() { TextWithEntities() .append(_entry.in ? QChar('+') : kMinus) .append(isCurrency - ? Info::ChannelEarn::MajorPart(_entry.credits) + ? Info::ChannelEarn::MajorPart(_entry.credits.abs()) : Lang::FormatCreditsAmountDecimal(_entry.credits.abs())) .append(QChar(' ')) .append(isCurrency diff --git a/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp b/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp index ebd237acc5..08fbe4ef5e 100644 --- a/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp +++ b/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp @@ -291,6 +291,8 @@ void InnerWidget::setupTop() { } void InnerWidget::startTop() { + _albumsTabs = nullptr; + _albumsWrap = nullptr; _top.create(this); _top->show(); _topHeight = _top->heightValue(); diff --git a/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_menu_item.cpp b/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_menu_item.cpp index 835dd03126..8dfaf73f42 100644 --- a/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_menu_item.cpp +++ b/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_menu_item.cpp @@ -175,7 +175,7 @@ void AddEmojiBuilderAction( }; const auto state = menu->lifetime().make_state(controller); auto item = base::make_unique_q( - menu.get(), + menu->menu(), menu->st().menu, Ui::Menu::CreateAction( menu.get(), diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 24f11abaee..a1fb2ef227 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -613,7 +613,7 @@ void ConfirmEmojiStatusBox( class BotAction final : public Ui::Menu::ItemBase { public: BotAction( - not_null parent, + not_null parent, std::shared_ptr show, const style::Menu &st, const AttachWebViewBot &bot, @@ -654,14 +654,14 @@ private: }; BotAction::BotAction( - not_null parent, + not_null parent, std::shared_ptr show, const style::Menu &st, const AttachWebViewBot &bot, Fn callback) : ItemBase(parent, st) , _show(std::move(show)) -, _dummyAction(new QAction(parent)) +, _dummyAction(Ui::CreateChild(parent)) , _st(st) , _bot(bot) , _icon(this, _bot.media) @@ -669,8 +669,8 @@ BotAction::BotAction( + _st.itemStyle.font->height + _st.itemPadding.bottom()) { setAcceptBoth(false); - initResizeHook(parent->sizeValue()); - setClickedCallback(std::move(callback)); + fitToMenuWidth(); + setActionTriggered(std::move(callback)); _icon.move(_st.itemIconPosition); @@ -2729,7 +2729,7 @@ std::unique_ptr MakeAttachBotsMenu( }); }; auto action = base::make_unique_q( - raw, + raw->menu(), controller->uiShow(), raw->menu()->st(), bot, diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_confirm_prepared.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_confirm_prepared.cpp index a3c7fefc06..8fe8193197 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_confirm_prepared.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_confirm_prepared.cpp @@ -109,8 +109,8 @@ PreviewWrap::PreviewWrap( using namespace HistoryView; _history->owner().viewRepaintRequest( - ) | rpl::on_next([=](not_null view) { - if (view == _item.get()) { + ) | rpl::on_next([=](Data::RequestViewRepaint data) { + if (data.view == _item.get()) { update(); } }, lifetime()); @@ -166,6 +166,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { auto context = _theme->preparePaintContext( _style.get(), rect(), + rect(), e->rect(), !window()->isActiveWindow()); p.translate(_position); diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index be40feb2cd..caa0cadb31 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -69,11 +69,11 @@ class ItemZoom final , public Ui::AbstractTooltipShower { public: ItemZoom( - not_null parent, + not_null parent, const not_null delegate, const style::Menu &st) : Ui::Menu::Action( - parent, + parent->menu(), st, Ui::CreateChild(parent), nullptr, diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index 944538aba4..469cb33e50 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -280,6 +280,27 @@ bool AppConfig::settingsDisplayPasskeys() const { return get(u"settings_display_passkeys"_q, false); } +int64 AppConfig::stakeDiceNanoTonMin() const { + return get(u"ton_stakedice_stake_amount_min"_q, 100'000'000LL); +} + +int64 AppConfig::stakeDiceNanoTonMax() const { + return get(u"ton_stakedice_stake_amount_max"_q, 50'000'000'000LL); +} + +std::vector AppConfig::stakeDiceNanoTonSuggested() const { + return get>( + u"ton_stakedice_stake_suggested_amounts"_q, + std::vector{ + 100'000'000LL, + 1'000'000'000LL, + 2'000'000'000LL, + 5'000'000'000LL, + 10'000'000'000LL, + 20'000'000'000LL, + }); +} + void AppConfig::refresh(bool force) { if (_requestId || !_api) { if (force) { @@ -464,6 +485,27 @@ std::vector AppConfig::getIntArray( }); } +std::vector AppConfig::getInt64Array( + const QString &key, + std::vector &&fallback) const { + return getValue(key, [&](const MTPJSONValue &value) { + return value.match([&](const MTPDjsonArray &data) { + auto result = std::vector(); + result.reserve(data.vvalue().v.size()); + for (const auto &entry : data.vvalue().v) { + if (entry.type() != mtpc_jsonNumber) { + return std::move(fallback); + } + result.push_back( + int64(base::SafeRound(entry.c_jsonNumber().vvalue().v))); + } + return result; + }, [&](const auto &data) { + return std::move(fallback); + }); + }); +} + bool AppConfig::newRequirePremiumFree() const { return get( u"new_noncontact_peers_require_premium_without_ownpremium"_q, diff --git a/Telegram/SourceFiles/main/main_app_config.h b/Telegram/SourceFiles/main/main_app_config.h index 500d8f57f6..5e9cc97659 100644 --- a/Telegram/SourceFiles/main/main_app_config.h +++ b/Telegram/SourceFiles/main/main_app_config.h @@ -47,6 +47,8 @@ public: return getStringMap(key, std::move(fallback)); } else if constexpr (std::is_same_v>) { return getIntArray(key, std::move(fallback)); + } else if constexpr (std::is_same_v>) { + return getInt64Array(key, std::move(fallback)); } else if constexpr (std::is_same_v) { return getBool(key, fallback); } @@ -128,6 +130,10 @@ public: [[nodiscard]] int passkeysAccountPasskeysMax() const; [[nodiscard]] bool settingsDisplayPasskeys() const; + [[nodiscard]] int64 stakeDiceNanoTonMin() const; + [[nodiscard]] int64 stakeDiceNanoTonMax() const; + [[nodiscard]] std::vector stakeDiceNanoTonSuggested() const; + using StarsColoring = Calls::Group::Ui::StarsColoring; [[nodiscard]] std::vector groupCallColorings() const; @@ -159,6 +165,9 @@ private: [[nodiscard]] std::vector getIntArray( const QString &key, std::vector &&fallback) const; + [[nodiscard]] std::vector getInt64Array( + const QString &key, + std::vector &&fallback) const; void updateIgnoredRestrictionReasons(std::vector was); diff --git a/Telegram/SourceFiles/main/main_session_settings.cpp b/Telegram/SourceFiles/main/main_session_settings.cpp index 4290012d0a..0be24c19c7 100644 --- a/Telegram/SourceFiles/main/main_session_settings.cpp +++ b/Telegram/SourceFiles/main/main_session_settings.cpp @@ -66,6 +66,8 @@ QByteArray SessionSettings::serialize() const { + Serialize::stringSize(auth.location); } size += sizeof(qint32); // _setupEmailState + size += sizeof(qint32) // _moderateCommonGroups size + + (_moderateCommonGroups.size() * sizeof(quint64)); auto result = QByteArray(); result.reserve(size); @@ -147,6 +149,10 @@ QByteArray SessionSettings::serialize() const { << auth.location; } stream << qint32(static_cast(_setupEmailState)); + stream << qint32(_moderateCommonGroups.size()); + for (const auto &peerId : _moderateCommonGroups) { + stream << SerializePeerId(peerId); + } } Ensures(result.size() == size); @@ -219,6 +225,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { base::flat_set ratedTranscriptions; std::vector unreviewed; qint32 setupEmailState = 0; + std::vector moderateCommonGroups; stream >> versionTag; if (versionTag == kVersionTag) { @@ -635,6 +642,23 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> setupEmailState; } + if (!stream.atEnd()) { + auto count = qint32(0); + stream >> count; + if (stream.status() == QDataStream::Ok) { + for (auto i = 0; i != count; ++i) { + quint64 peerId; + stream >> peerId; + if (stream.status() != QDataStream::Ok) { + LOG(("App Error: " + "Bad data for SessionSettings::addFromSerialized()" + "with moderateCommonGroups")); + return; + } + moderateCommonGroups.emplace_back(DeserializePeerId(peerId)); + } + } + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for SessionSettings::addFromSerialized()")); @@ -698,6 +722,8 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { break; } + _moderateCommonGroups = std::move(moderateCommonGroups); + if (version < 2) { app.setLastSeenWarningSeen(appLastSeenWarningSeen == 1); for (const auto &[key, value] : appSoundOverrides) { diff --git a/Telegram/SourceFiles/main/main_session_settings.h b/Telegram/SourceFiles/main/main_session_settings.h index 95eabcf23e..16e4f07ab2 100644 --- a/Telegram/SourceFiles/main/main_session_settings.h +++ b/Telegram/SourceFiles/main/main_session_settings.h @@ -176,6 +176,13 @@ public: void setSetupEmailState(Data::SetupEmailState state); [[nodiscard]] Data::SetupEmailState setupEmailState() const; + void setModerateCommonGroups(std::vector groups) { + _moderateCommonGroups = std::move(groups); + } + [[nodiscard]] const std::vector &moderateCommonGroups() const { + return _moderateCommonGroups; + } + private: static constexpr auto kDefaultSupportChatsLimitSlice = 7 * 24 * 60 * 60; static constexpr auto kPhotoEditorHintMaxShowsCount = 5; @@ -222,6 +229,8 @@ private: Data::SetupEmailState _setupEmailState; + std::vector _moderateCommonGroups; + }; } // namespace Main diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index d2562603e6..8ef6cc6668 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1417,18 +1417,21 @@ void MainWidget::showHistory( } _stack.clear(); } else { - for (auto i = 0, s = int(_stack.size()); i < s; ++i) { - if (_stack.at(i)->type() == HistoryStackItem && _stack.at(i)->peer()->id == peerId) { - foundInStack = true; - while (int(_stack.size()) > i + 1) { - ClearBotStartToken(_stack.back()->peer()); + if (!params.allowDuplicateInStack) { + for (auto i = 0, s = int(_stack.size()); i < s; ++i) { + if (_stack.at(i)->type() == HistoryStackItem + && _stack.at(i)->peer()->id == peerId) { + foundInStack = true; + while (int(_stack.size()) > i + 1) { + ClearBotStartToken(_stack.back()->peer()); + _stack.pop_back(); + } _stack.pop_back(); + if (!back) { + back = true; + } + break; } - _stack.pop_back(); - if (!back) { - back = true; - } - break; } } if (const auto activeChat = _controller->activeChatCurrent()) { @@ -2902,7 +2905,7 @@ int MainWidget::backgroundFromY() const { bool MainWidget::contentOverlapped(const QRect &globalRect) { return _history->contentOverlapped(globalRect) - || _playerPlaylist->overlaps(globalRect); + /*|| _playerPlaylist->overlaps(globalRect)*/; } void MainWidget::activate() { diff --git a/Telegram/SourceFiles/media/player/media_player.style b/Telegram/SourceFiles/media/player/media_player.style index 18d26254b7..4a64006d29 100644 --- a/Telegram/SourceFiles/media/player/media_player.style +++ b/Telegram/SourceFiles/media/player/media_player.style @@ -160,8 +160,8 @@ mediaPlayerCancelIcon: icon{ mediaPlayerMenu: DropdownMenu(defaultDropdownMenu) { wrap: InnerDropdown(defaultInnerDropdown) { - scrollPadding: margins(0px, 4px, 0px, 4px); - padding: margins(10px, 2px, 10px, 10px); + scrollPadding: margins(0px, 6px, 0px, 6px); + padding: margins(10px, 10px, 10px, 10px); } } mediaPlayerMenuCheck: icon {{ "player/player_check", mediaPlayerActiveFg }}; diff --git a/Telegram/SourceFiles/media/player/media_player_dropdown.cpp b/Telegram/SourceFiles/media/player/media_player_dropdown.cpp index c44a0aff57..cc563ab53b 100644 --- a/Telegram/SourceFiles/media/player/media_player_dropdown.cpp +++ b/Telegram/SourceFiles/media/player/media_player_dropdown.cpp @@ -50,7 +50,7 @@ constexpr auto kSpeedStickedValues class SpeedSliderItem final : public Ui::Menu::ItemBase { public: SpeedSliderItem( - not_null parent, + not_null parent, const style::MediaSpeedMenu &st, rpl::producer value); @@ -84,7 +84,7 @@ private: }; SpeedSliderItem::SpeedSliderItem( - not_null parent, + not_null parent, const style::MediaSpeedMenu &st, rpl::producer value) : Ui::Menu::ItemBase(parent, st.dropdown.menu) @@ -95,7 +95,7 @@ SpeedSliderItem::SpeedSliderItem( + st.dropdown.menu.itemStyle.font->height + st.sliderPadding.bottom()) , _debounceTimer([=] { _debounced.fire(current()); }) { - initResizeHook(parent->sizeValue()); + fitToMenuWidth(); enableMouseSelecting(); enableMouseSelecting(_slider.get()); @@ -382,7 +382,7 @@ void Dropdown::paintEvent(QPaintEvent *e) { // draw shadow auto shadowedRect = rect().marginsRemoved(getMargin()); auto shadowedSides = RectPart::Left | RectPart::Right | RectPart::Bottom; - Ui::Shadow::paint(p, shadowedRect, width(), st::defaultRoundShadow, shadowedSides); + Ui::Shadow::paint(p, shadowedRect, width(), st::roundShadowRadius8px, shadowedSides); const auto &corners = Ui::CachedCornerPixmaps(Ui::MenuCorners); const auto fill = Ui::CornersPixmaps{ .p = { QPixmap(), QPixmap(), corners.p[2], corners.p[3] }, @@ -677,7 +677,7 @@ void OrderController::fillMenu(not_null menu) { Unexpected("Order mode in addOrderAction."); }(); menu->addAction(base::make_unique_q( - menu, + menu->menu(), (active ? st::mediaPlayerOrderMenuActive : st::mediaPlayerOrderMenu), diff --git a/Telegram/SourceFiles/media/player/media_player_panel.cpp b/Telegram/SourceFiles/media/player/media_player_panel.cpp index 0f4217c5c7..763773c956 100644 --- a/Telegram/SourceFiles/media/player/media_player_panel.cpp +++ b/Telegram/SourceFiles/media/player/media_player_panel.cpp @@ -158,7 +158,7 @@ void Panel::paintEvent(QPaintEvent *e) { | RectPart::Bottom | (rtl() ? RectPart::Left : RectPart::Right) | RectPart::Top; - Ui::Shadow::paint(p, shadowedRect, width(), st::defaultRoundShadow, shadowedSides); + Ui::Shadow::paint(p, shadowedRect, width(), st::roundShadowRadius8px, shadowedSides); Ui::FillRoundRect(p, shadowedRect, st::menuBg, Ui::MenuCorners); } diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index 6f235f77cf..968ab232f3 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -163,10 +163,13 @@ Widget::Widget( updateLabelsGeometry(); }, lifetime()); - instance()->tracksFinished( - ) | rpl::filter([=](AudioMsgId::Type type) { - return (type == AudioMsgId::Type::Voice); - }) | rpl::on_next([=](AudioMsgId::Type type) { + rpl::merge( + instance()->tracksFinished( + ) | rpl::filter([=](AudioMsgId::Type type) { + return (type == AudioMsgId::Type::Voice); + }) | rpl::to_empty, + instance()->stops(AudioMsgId::Type::Voice) + ) | rpl::on_next([=] { _voiceIsActive = false; const auto currentSong = instance()->current(AudioMsgId::Type::Song); const auto songState = instance()->getState(AudioMsgId::Type::Song); diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.cpp b/Telegram/SourceFiles/media/stories/media_stories_header.cpp index c9d2062923..17a1d55eff 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_header.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_header.cpp @@ -549,7 +549,7 @@ void Header::setVideoStreamViewers(rpl::producer viewers) { auto helper = Ui::Text::CustomEmojiHelper(); const auto badge = helper.paletteDependent( Ui::Text::CustomEmojiTextBadge( - tr::lng_video_stream_live(tr::now), + tr::lng_video_stream_live(tr::now).toUpper(), st::groupCallMessageBadge, st::groupCallMessageBadgeMargin)); const auto context = helper.context(); diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index fe46dbc017..6dd3b2f483 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -256,8 +256,8 @@ ReactionView::ReactionView( - st::msgMargin.bottom(); session->data().viewRepaintRequest( - ) | rpl::on_next([=](not_null element) { - if (element == view) { + ) | rpl::on_next([=](Data::RequestViewRepaint data) { + if (data.view == view) { update(); } }, lifetime()); diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp index 2e3e36acf1..c40e80be6e 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp @@ -500,7 +500,7 @@ void RecentViews::showMenu() { _menu->setForcedVerticalOrigin(PopupMenu::VerticalOrigin::Bottom); _menu->popup(QPoint( geometry.x() + (_widget->width() - size.width()) / 2, - geometry.y() + _widget->height())); + geometry.y())); _menuEntriesCount = _menuEntriesCount.current() + added; } diff --git a/Telegram/SourceFiles/menu/gift_resale_filter.cpp b/Telegram/SourceFiles/menu/gift_resale_filter.cpp index 35cad8ae74..5926e0ba75 100644 --- a/Telegram/SourceFiles/menu/gift_resale_filter.cpp +++ b/Telegram/SourceFiles/menu/gift_resale_filter.cpp @@ -51,7 +51,7 @@ namespace { } // namespace GiftResaleFilterAction::GiftResaleFilterAction( - not_null parent, + not_null parent, const style::Menu &st, const TextWithEntities &text, const Text::MarkedContext &context, diff --git a/Telegram/SourceFiles/menu/gift_resale_filter.h b/Telegram/SourceFiles/menu/gift_resale_filter.h index 24e9e2e0b7..43a74e4af4 100644 --- a/Telegram/SourceFiles/menu/gift_resale_filter.h +++ b/Telegram/SourceFiles/menu/gift_resale_filter.h @@ -15,7 +15,7 @@ namespace Ui { class GiftResaleFilterAction final : public Menu::Action { public: GiftResaleFilterAction( - not_null parent, + not_null parent, const style::Menu &st, const TextWithEntities &text, const Text::MarkedContext &context, diff --git a/Telegram/SourceFiles/menu/menu_check_item.cpp b/Telegram/SourceFiles/menu/menu_check_item.cpp index e7cb08fcc2..f0e5f46f8c 100644 --- a/Telegram/SourceFiles/menu/menu_check_item.cpp +++ b/Telegram/SourceFiles/menu/menu_check_item.cpp @@ -15,7 +15,7 @@ namespace Menu { void ItemWithCheck::init(bool checked) { enableMouseSelecting(); - AbstractButton::setDisabled(true); + ItemBase::setPreventClose(true); _checkView = std::make_unique(st::defaultCheck, false); _checkView->checkedChanges( @@ -24,11 +24,8 @@ void ItemWithCheck::init(bool checked) { }, lifetime()); _checkView->setChecked(checked, anim::type::normal); - AbstractButton::clicks( - ) | rpl::on_next([=] { - _checkView->setChecked( - !_checkView->checked(), - anim::type::normal); + ItemBase::clicks() | rpl::on_next([=] { + _checkView->setChecked(!_checkView->checked(), anim::type::normal); }, lifetime()); } diff --git a/Telegram/SourceFiles/menu/menu_item_rate_transcribe.cpp b/Telegram/SourceFiles/menu/menu_item_rate_transcribe.cpp index 1561924487..192e64aa14 100644 --- a/Telegram/SourceFiles/menu/menu_item_rate_transcribe.cpp +++ b/Telegram/SourceFiles/menu/menu_item_rate_transcribe.cpp @@ -28,14 +28,20 @@ constexpr auto kDuration = crl::time(5000); } // namespace RateTranscribe::RateTranscribe( - not_null parent, + not_null popupMenu, const style::Menu &st, Fn rate) -: Ui::Menu::ItemBase(parent, st) -, _dummyAction(Ui::CreateChild(this)) { +: Ui::Menu::ItemBase(popupMenu->menu(), st) +, _dummyAction(Ui::CreateChild(this)) +, _leftButton(Ui::CreateSimpleCircleButton( + this, + st::defaultRippleAnimation)) +, _rightButton(Ui::CreateSimpleCircleButton( + this, + st::defaultRippleAnimation)) { setAcceptBoth(true); - initResizeHook(parent->sizeValue()); + fitToMenuWidth(); enableMouseSelecting(); @@ -53,8 +59,8 @@ RateTranscribe::RateTranscribe( setMinWidth( label->st().style.font->width( tr::lng_context_rate_transcription(tr::now))); - widthValue() | rpl::on_next([=](int w) { - content->resizeToWidth(parentWidget()->width()); + widthValue() | rpl::on_next([=, menu = popupMenu->menu()](int w) { + content->resizeToWidth(menu->width()); }, content->lifetime()); Ui::AddSkip(content); @@ -64,31 +70,25 @@ RateTranscribe::RateTranscribe( // const auto rightButton = Ui::CreateChild( // this, // st::menuTranscribeDummyButton); - const auto leftButton = Ui::CreateSimpleCircleButton( - this, - st::defaultRippleAnimation); { - leftButton->resize(Size(st::menuTranscribeDummyButton.width)); + _leftButton->resize(Size(st::menuTranscribeDummyButton.width)); const auto label = Ui::CreateChild( - leftButton, + _leftButton, QString::fromUtf8("\U0001F44D")); label->setAttribute(Qt::WA_TransparentForMouseEvents, true); - leftButton->sizeValue() | rpl::on_next([=](QSize s) { + _leftButton->sizeValue() | rpl::on_next([=](QSize s) { label->moveToLeft( (s.width() - label->width()) / 2, (s.height() - label->height()) / 2); }, label->lifetime()); } - const auto rightButton = Ui::CreateSimpleCircleButton( - this, - st::defaultRippleAnimation); { - rightButton->resize(Size(st::menuTranscribeDummyButton.width)); + _rightButton->resize(Size(st::menuTranscribeDummyButton.width)); const auto label = Ui::CreateChild( - rightButton, + _rightButton, QString::fromUtf8("\U0001F44E")); label->setAttribute(Qt::WA_TransparentForMouseEvents, true); - rightButton->sizeValue() | rpl::on_next([=](QSize s) { + _rightButton->sizeValue() | rpl::on_next([=](QSize s) { label->moveToLeft( (s.width() - label->width()) / 2, (s.height() - label->height()) / 2); @@ -96,7 +96,7 @@ RateTranscribe::RateTranscribe( } { const auto showToast = [=, - weak = base::make_weak(parent->parentWidget())]{ + weak = base::make_weak(popupMenu->parentWidget())]{ if (const auto strong = weak.get()) { base::call_delayed( st::universalDuration * 1.1, @@ -110,19 +110,19 @@ RateTranscribe::RateTranscribe( })); } }; - const auto hideMenu = [=, weak = base::make_weak(parent)] { + const auto hideMenu = [=, weak = base::make_weak(popupMenu)] { if (const auto strong = weak.get()) { base::call_delayed( st::universalDuration, crl::guard(strong, [=] { strong->hideMenu(false); })); } }; - leftButton->setClickedCallback([=] { + _leftButton->setClickedCallback([=] { rate(true); showToast(); hideMenu(); }); - rightButton->setClickedCallback([=] { + _rightButton->setClickedCallback([=] { rate(false); showToast(); hideMenu(); @@ -137,18 +137,30 @@ RateTranscribe::RateTranscribe( ) | rpl::on_next([=]( const QRect &contentRect, const QRect &labelRect) { - leftButton->moveToLeft( + _leftButton->moveToLeft( labelRect.x(), rect::bottom(contentRect)); - rightButton->moveToLeft( - rect::right(labelRect) - rightButton->width(), + _rightButton->moveToLeft( + rect::right(labelRect) - _rightButton->width(), rect::bottom(contentRect)); _desiredHeight = rect::m::sum::v(st::menuTranscribeItemPadding) - + leftButton->height() + + _leftButton->height() + labelRect.height(); - }, leftButton->lifetime()); - leftButton->show(); - rightButton->show(); + }, _leftButton->lifetime()); + _leftButton->show(); + _rightButton->show(); + + selects() | rpl::on_next([=](const Ui::Menu::CallbackData &data) { + if (data.selected + && data.source == Ui::Menu::TriggeredSource::Keyboard) { + _leftButton->setForceRippled(true); + _selectedButton = SelectedButton::Left; + } else if (!data.selected) { + _leftButton->setForceRippled(false); + _rightButton->setForceRippled(false); + _selectedButton = SelectedButton::None; + } + }, lifetime()); } not_null RateTranscribe::action() const { @@ -163,4 +175,43 @@ int RateTranscribe::contentHeight() const { return _desiredHeight; } -} // namespace Menu +void RateTranscribe::handleKeyPress(not_null e) { + const auto key = e->key(); + if (key == Qt::Key_Left) { + if (_selectedButton == SelectedButton::Right) { + _rightButton->setForceRippled(false); + _leftButton->setForceRippled(true); + _selectedButton = SelectedButton::Left; + } else if (_selectedButton == SelectedButton::Left) { + _leftButton->setForceRippled(false); + _rightButton->setForceRippled(true); + _selectedButton = SelectedButton::Right; + } else { + _leftButton->setForceRippled(true); + _selectedButton = SelectedButton::Left; + } + } else if (key == Qt::Key_Right) { + if (_selectedButton == SelectedButton::Left) { + _leftButton->setForceRippled(false); + _rightButton->setForceRippled(true); + _selectedButton = SelectedButton::Right; + } else if (_selectedButton == SelectedButton::Right) { + _rightButton->setForceRippled(false); + _leftButton->setForceRippled(true); + _selectedButton = SelectedButton::Left; + } else { + _leftButton->setForceRippled(true); + _selectedButton = SelectedButton::Left; + } + } else if (key == Qt::Key_Return + || key == Qt::Key_Enter + || key == Qt::Key_Space) { + if (_selectedButton == SelectedButton::Left) { + _leftButton->clicked(Qt::KeyboardModifiers(), Qt::LeftButton); + } else if (_selectedButton == SelectedButton::Right) { + _rightButton->clicked(Qt::KeyboardModifiers(), Qt::LeftButton); + } + } +} + +} // namespace Menu \ No newline at end of file diff --git a/Telegram/SourceFiles/menu/menu_item_rate_transcribe.h b/Telegram/SourceFiles/menu/menu_item_rate_transcribe.h index 1a147c2f50..c1ac40d99a 100644 --- a/Telegram/SourceFiles/menu/menu_item_rate_transcribe.h +++ b/Telegram/SourceFiles/menu/menu_item_rate_transcribe.h @@ -29,10 +29,20 @@ public: protected: int contentHeight() const override; + void handleKeyPress(not_null e) override; private: + enum class SelectedButton { + None, + Left, + Right, + }; + int _desiredHeight = 0; not_null _dummyAction; + not_null _leftButton; + not_null _rightButton; + SelectedButton _selectedButton = SelectedButton::None; }; diff --git a/Telegram/SourceFiles/menu/menu_mute.cpp b/Telegram/SourceFiles/menu/menu_mute.cpp index 409360e37f..3e038b3518 100644 --- a/Telegram/SourceFiles/menu/menu_mute.cpp +++ b/Telegram/SourceFiles/menu/menu_mute.cpp @@ -69,7 +69,7 @@ void IconWithText::paintEvent(QPaintEvent *e) { class MuteItem final : public Ui::Menu::Action { public: MuteItem( - not_null parent, + not_null parent, const style::Menu &st, Descriptor descriptor); @@ -85,7 +85,7 @@ private: }; MuteItem::MuteItem( - not_null parent, + not_null parent, const style::Menu &st, Descriptor descriptor) : Ui::Menu::Action( @@ -113,7 +113,7 @@ MuteItem::MuteItem( }, lifetime()); _animation.stop(); - setClickedCallback([=] { + setActionTriggered([=] { descriptor.updateMutePeriod(_isMuted ? 0 : kMuteForeverValue); }); } @@ -340,7 +340,7 @@ void FillMuteMenu( }; auto item = base::make_unique_q( - menu, + menu->menu(), st, Ui::Menu::CreateAction( menu->menu().get(), @@ -361,7 +361,10 @@ void FillMuteMenu( &st::menuIconMuteFor); menu->addAction( - base::make_unique_q(menu, menu->st().menu, descriptor)); + base::make_unique_q( + menu->menu(), + menu->st().menu, + descriptor)); } void SetupMuteMenu( diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 6e88ea8225..b572d932af 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -458,6 +458,7 @@ void EffectPreview::repaintBackground() { _chatStyle.get(), rect, rect, + rect, false); context.outbg = _item->hasOutLayout(); _item->draw(p, context); diff --git a/Telegram/SourceFiles/menu/menu_sponsored.cpp b/Telegram/SourceFiles/menu/menu_sponsored.cpp index aa6994d590..b6278272bc 100644 --- a/Telegram/SourceFiles/menu/menu_sponsored.cpp +++ b/Telegram/SourceFiles/menu/menu_sponsored.cpp @@ -408,7 +408,7 @@ void FillSponsored( }; for (const auto &i : info) { auto item = base::make_unique_q( - menu, + menu->menu(), dark ? st::storiesMenu : st::defaultMenu, (dark ? st::historySponsorInfoItemDark diff --git a/Telegram/SourceFiles/menu/menu_ttl.cpp b/Telegram/SourceFiles/menu/menu_ttl.cpp index 26100e5e85..9c4db841f8 100644 --- a/Telegram/SourceFiles/menu/menu_ttl.cpp +++ b/Telegram/SourceFiles/menu/menu_ttl.cpp @@ -109,7 +109,7 @@ TextItem::TextItem( (s.height() - _label->height()) / 2); }, lifetime()); - initResizeHook(parent->sizeValue()); + fitToMenuWidth(); } not_null TextItem::action() const { diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 8803fc0ef8..a249507623 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -47,6 +47,7 @@ inputMediaStory#89fdd778 peer:InputPeer id:int = InputMedia; inputMediaWebPage#c21b8849 flags:# force_large_media:flags.0?true force_small_media:flags.1?true optional:flags.2?true url:string = InputMedia; inputMediaPaidMedia#c4103386 flags:# stars_amount:long extended_media:Vector payload:flags.0?string = InputMedia; inputMediaTodo#9fc55fde todo:TodoList = InputMedia; +inputMediaStakeDice#f3a9244a game_hash:string ton_amount:long client_seed:bytes = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#bdcdaec0 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.3?VideoSize = InputChatPhoto; @@ -117,7 +118,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#b92f76cf flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true flags2:# offline:flags2.1?true video_processing_pending:flags2.4?true paid_suggested_post_stars:flags2.8?true paid_suggested_post_ton:flags2.9?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags2.2?long factcheck:flags2.3?FactCheck report_delivery_until_date:flags2.5?int paid_message_stars:flags2.6?long suggested_post:flags2.7?SuggestedPost schedule_repeat_period:flags2.10?int = Message; +message#9cb490e9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true flags2:# offline:flags2.1?true video_processing_pending:flags2.4?true paid_suggested_post_stars:flags2.8?true paid_suggested_post_ton:flags2.9?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags2.2?long factcheck:flags2.3?FactCheck report_delivery_until_date:flags2.5?int paid_message_stars:flags2.6?long suggested_post:flags2.7?SuggestedPost schedule_repeat_period:flags2.10?int summary_from_language:flags2.11?string = Message; messageService#7a800e0a flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true reactions_are_possible:flags.9?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer saved_peer_id:flags.28?Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction reactions:flags.20?MessageReactions ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -132,7 +133,7 @@ messageMediaGame#fdb19008 game:Game = MessageMedia; messageMediaInvoice#f6a548d3 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string extended_media:flags.4?MessageExtendedMedia = MessageMedia; messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int proximity_notification_radius:flags.1?int = MessageMedia; messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; -messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; +messageMediaDice#8cbec07 flags:# value:int emoticon:string game_outcome:flags.0?messages.EmojiGameOutcome = MessageMedia; messageMediaStory#68cb6283 flags:# via_mention:flags.1?true peer:Peer id:int story:flags.0?StoryItem = MessageMedia; messageMediaGiveaway#aa073beb flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.2?true channels:Vector countries_iso2:flags.1?Vector prize_description:flags.3?string quantity:int months:flags.4?int stars:flags.5?long until_date:int = MessageMedia; messageMediaGiveawayResults#ceaa3ea1 flags:# only_new_subscribers:flags.0?true refunded:flags.2?true channel_id:long additional_peers_count:flags.3?int launch_msg_id:int winners_count:int unclaimed_count:int winners:Vector months:flags.4?int stars:flags.5?long prize_description:flags.1?string until_date:int = MessageMedia; @@ -452,6 +453,7 @@ updatePinnedForumTopics#def143d0 flags:# peer:Peer order:flags.0?Vector = U updateDeleteGroupCallMessages#3e85e92c call:InputGroupCall messages:Vector = Update; updateStarGiftAuctionState#48e246c2 gift_id:long state:StarGiftAuctionState = Update; updateStarGiftAuctionUserState#dc58f31e gift_id:long user_state:StarGiftAuctionUserState = Update; +updateEmojiGameInfo#fb9c547a info:messages.EmojiGameInfo = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -2097,6 +2099,7 @@ inputPasskeyResponseRegister#3e63935c client_data:DataJSON attestation_data:byte inputPasskeyResponseLogin#c31fc14a client_data:DataJSON authenticator_data:bytes signature:bytes user_handle:string = InputPasskeyResponse; inputPasskeyCredentialPublicKey#3c27b78f id:string raw_id:string response:InputPasskeyResponse = InputPasskeyCredential; +inputPasskeyCredentialFirebasePNV#5b1ccb28 pnv_token:string = InputPasskeyCredential; starGiftBackground#aff56398 center_color:int edge_color:int text_color:int = StarGiftBackground; @@ -2105,6 +2108,11 @@ starGiftAuctionRoundExtendable#aa021e5 num:int duration:int extend_top:int exten payments.starGiftUpgradeAttributes#46c6e36f attributes:Vector = payments.StarGiftUpgradeAttributes; +messages.emojiGameOutcome#da2ad647 seed:bytes stake_ton_amount:long ton_amount:long = messages.EmojiGameOutcome; + +messages.emojiGameUnavailable#59e65335 = messages.EmojiGameInfo; +messages.emojiGameDiceInfo#44e56023 flags:# game_hash:string prev_stake:long current_streak:int params:Vector plays_left:flags.0?int = messages.EmojiGameInfo; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2544,6 +2552,8 @@ messages.updatePinnedForumTopic#175df251 peer:InputPeer topic_id:int pinned:Bool messages.reorderPinnedForumTopics#e7841f0 flags:# force:flags.0?true peer:InputPeer order:Vector = Updates; messages.createForumTopic#2f98c3d5 flags:# title_missing:flags.4?true peer:InputPeer title:string icon_color:flags.0?int icon_emoji_id:flags.3?long random_id:long send_as:flags.2?InputPeer = Updates; messages.deleteTopicHistory#d2816f10 peer:InputPeer top_msg_id:int = messages.AffectedHistory; +messages.getEmojiGameInfo#fb7e8ca7 = messages.EmojiGameInfo; +messages.summarizeText#9d4104e2 flags:# peer:InputPeer id:int to_lang:flags.0?string = TextWithEntities; updates.getState#edd4882a = updates.State; updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference; @@ -2879,4 +2889,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; -// LAYER 220 +// LAYER 221 diff --git a/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp b/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp index 9e0314d218..3c3b5358d6 100644 --- a/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp @@ -915,14 +915,14 @@ void PaidReactionSlider( p.setClipPath(fullPath.subtracted(circlePath)); state->particles.setColor(Qt::white); - state->particles.paint(p, rect, crl::now()); + state->particles.paint(p, rect, crl::now(), false); p.setClipping(false); p.setClipPath(fullPath.intersected(circlePath.united(rightRect))); state->particles.setColor(activeFgOverride ? st::groupCallMemberInactiveIcon->c : st::creditsBg3->c); - state->particles.paint(p, rect, crl::now()); + state->particles.paint(p, rect, crl::now(), false); }, stars->lifetime()); } diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index 07552cecdb..0605b51a15 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -518,15 +518,19 @@ void MainWindow::createGlobalMenu() { QKeySequence::SelectAll); psSelectAll->setShortcutContext(Qt::WidgetShortcut); - edit->addSeparator(); - edit->addAction( - tr::lng_mac_menu_emoji_and_symbols( - tr::now, - Ui::Text::FixAmpersandInAction), - this, - [] { [NSApp orderFrontCharacterPalette:nil]; }, - QKeySequence(Qt::MetaModifier | Qt::ControlModifier | Qt::Key_Space) - )->setShortcutContext(Qt::WidgetShortcut); + if (!Platform::IsMac26_0OrGreater()) { + edit->addSeparator(); + edit->addAction( + tr::lng_mac_menu_emoji_and_symbols( + tr::now, + Ui::Text::FixAmpersandInAction), + this, + [] { [NSApp orderFrontCharacterPalette:nil]; }, + QKeySequence(Qt::MetaModifier + | Qt::ControlModifier + | Qt::Key_Space) + )->setShortcutContext(Qt::WidgetShortcut); + } QMenu *window = psMainMenu.addMenu(tr::lng_mac_menu_window(tr::now)); diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.mm b/Telegram/SourceFiles/platform/mac/specific_mac.mm index 841886278c..51a09b5b5f 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac.mm @@ -21,9 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/mac/base_confirm_quit.h" #include "base/platform/mac/base_utilities_mac.h" #include "base/platform/base_platform_info.h" -#include "main/main_session.h" -#include "window/window_session_controller.h" -#include "base/options.h" #include #include @@ -245,15 +242,6 @@ bool AutostartSkip() { } void NewVersionLaunched(int oldVersion) { - if (const auto window = Core::App().activeWindow()) { - if (const auto controller = window->sessionController()) { - const auto userId = controller->session().userId().bare; - const auto hash = std::hash{}(userId); - if ((hash % 100) < 15 || (userId % 100) == 91) { - base::options::lookup("text-recognition-mac").set(true); - } - } - } } QImage DefaultApplicationIcon() { diff --git a/Telegram/SourceFiles/platform/mac/text_recognition_mac.mm b/Telegram/SourceFiles/platform/mac/text_recognition_mac.mm index 66eac80bd0..4fab7d149e 100644 --- a/Telegram/SourceFiles/platform/mac/text_recognition_mac.mm +++ b/Telegram/SourceFiles/platform/mac/text_recognition_mac.mm @@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_text_recognition.h" #include "base/platform/mac/base_utilities_mac.h" -#include "base/options.h" #import #import @@ -17,21 +16,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Platform { namespace TextRecognition { -namespace { - -base::options::toggle TextRecognitionOption({ - .id = "text-recognition-mac", - .name = "Text Recognition", - .description = "Enable text recognition from images on macOS 10.15+.", - .defaultValue = false, - .scope = base::options::macos, -}); - -} // namespace - bool IsAvailable() { if (@available(macOS 10.15, *)) { - return TextRecognitionOption.value(); + return true; } return false; } @@ -84,7 +71,11 @@ Result RecognizeText(const QImage &image) { * imageSize.height; result.items.push_back({ NS2QString(text), - QRect(x, y, width, height) + QRect( + style::ConvertScale(x), + style::ConvertScale(y), + style::ConvertScale(width), + style::ConvertScale(height)) }); } } diff --git a/Telegram/SourceFiles/profile/profile_back_button.cpp b/Telegram/SourceFiles/profile/profile_back_button.cpp index 5de124d152..410d00469a 100644 --- a/Telegram/SourceFiles/profile/profile_back_button.cpp +++ b/Telegram/SourceFiles/profile/profile_back_button.cpp @@ -7,63 +7,124 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "profile/profile_back_button.h" -//#include "history/view/history_view_top_bar_widget.h" -#include "main/main_session.h" -#include "data/data_session.h" #include "ui/painter.h" #include "styles/style_widgets.h" #include "styles/style_window.h" #include "styles/style_profile.h" #include "styles/style_info.h" +#include "styles/style_chat.h" +#include "styles/style_dialogs.h" + +#include namespace Profile { -BackButton::BackButton( - QWidget *parent, - not_null session, - const QString &text, - rpl::producer oneColumnValue) -: Ui::AbstractButton(parent) -, _session(session) -, _text(text) { +BackButton::BackButton(QWidget *parent) : Ui::AbstractButton(parent) { setCursor(style::cur_pointer); - - std::move( - oneColumnValue - ) | rpl::on_next([=](bool oneColumn) { - if (!oneColumn) { - _unreadBadgeLifetime.destroy(); - } else if (!_unreadBadgeLifetime) { - _session->data().unreadBadgeChanges( - ) | rpl::on_next([=] { - rtlupdate( - 0, - 0, - st::titleUnreadCounterRight, - st::titleUnreadCounterTop); - }, _unreadBadgeLifetime); - } - }, lifetime()); } void BackButton::setText(const QString &text) { _text = text; + _cachedWidth = -1; + update(); +} + +void BackButton::setSubtext(const QString &subtext) { + _subtext = subtext; + _cachedWidth = -1; + update(); +} + +void BackButton::setWidget(not_null widget) { + _widget = widget; + _widget->setParent(this); + _widget->show(); + _widget->move( + st::historyAdminLogTopBarLeft + st::historyAdminLogTopBarUserpicSkip, + (st::profileTopBarHeight - _widget->height()) / 2); + _cachedWidth = -1; + update(); +} + +void BackButton::setOpacity(float64 opacity) { + _opacity = opacity; + if (_widget) { + if (opacity < 1.) { + if (!_opacityEffect) { + _opacityEffect = Ui::CreateChild( + _widget); + _widget->setGraphicsEffect(_opacityEffect); + } + _opacityEffect->setOpacity(opacity); + } else { + _widget->setGraphicsEffect(nullptr); + _opacityEffect = nullptr; + } + } update(); } int BackButton::resizeGetHeight(int newWidth) { + _cachedWidth = -1; return st::profileTopBarHeight; } +void BackButton::updateCache() { + if (_cachedWidth == width()) { + return; + } + _cachedWidth = width(); + const auto widgetWidth = _widget + ? _widget->width() + st::historyAdminLogTopBarUserpicSkip + : 0; + const auto availableWidth = width() + - st::historyAdminLogTopBarLeft + - widgetWidth; + _cachedElidedText = st::semiboldFont->elided( + _text, + availableWidth); + _cachedElidedSubtext = st::dialogsTextFont->elided( + _subtext, + availableWidth); +} + void BackButton::paintEvent(QPaintEvent *e) { - Painter p(this); + updateCache(); + + auto p = Painter(this); p.fillRect(e->rect(), st::profileBg); - st::topBarBack.paint(p, (st::topBarArrowPadding.left() - st::topBarBack.width()) / 2, (st::topBarHeight - st::topBarBack.height()) / 2, width()); + st::topBarBack.paint( + p, + st::historyAdminLogTopBarLeft, + (st::topBarHeight - st::topBarBack.height()) / 2, + width()); + p.setOpacity(_opacity); - p.setFont(st::topBarButton.style.font); - p.setPen(st::topBarButton.textFg); - p.drawTextLeft(st::topBarArrowPadding.left(), st::topBarButton.padding.top() + st::topBarButton.textTop, width(), _text); + const auto textHeight = st::semiboldFont->height; + const auto subtextHeight = st::dialogsTextFont->height; + const auto totalHeight = _subtext.isEmpty() + ? textHeight + : textHeight + subtextHeight; + const auto startY = (height() - totalHeight) / 2 - st::lineWidth; + const auto widgetWidth = _widget + ? _widget->width() + st::historyAdminLogTopBarUserpicSkip + : 0; + const auto textX = st::historyAdminLogTopBarLeft + widgetWidth; + + p.setFont(st::semiboldFont); + p.setPen(st::dialogsNameFg); + p.drawTextLeft(textX, startY, width(), _cachedElidedText); + + if (!_subtext.isEmpty()) { + p.setFont(st::dialogsTextFont); + p.setPen(st::historyStatusFg); + p.drawTextLeft( + textX, + startY + textHeight + st::lineWidth * 2, + width(), + _cachedElidedSubtext); + } } void BackButton::onStateChanged(State was, StateChangeSource source) { diff --git a/Telegram/SourceFiles/profile/profile_back_button.h b/Telegram/SourceFiles/profile/profile_back_button.h index 4c3ca7ceab..f8df0a0bd8 100644 --- a/Telegram/SourceFiles/profile/profile_back_button.h +++ b/Telegram/SourceFiles/profile/profile_back_button.h @@ -9,21 +9,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/abstract_button.h" -namespace Main { -class Session; -} // namespace Main +class QGraphicsOpacityEffect; namespace Profile { class BackButton final : public Ui::AbstractButton { public: - BackButton( - QWidget *parent, - not_null session, - const QString &text, - rpl::producer oneColumnValue); + BackButton(QWidget *parent); void setText(const QString &text); + void setSubtext(const QString &subtext); + void setWidget(not_null widget); + void setOpacity(float64 opacity); protected: void paintEvent(QPaintEvent *e) override; @@ -32,10 +29,19 @@ protected: void onStateChanged(State was, StateChangeSource source) override; private: - const not_null _session; + void updateCache(); rpl::lifetime _unreadBadgeLifetime; QString _text; + QString _subtext; + Ui::RpWidget *_widget = nullptr; + + int _cachedWidth = -1; + QString _cachedElidedText; + QString _cachedElidedSubtext; + + float64 _opacity = 1.0; + QGraphicsOpacityEffect *_opacityEffect = nullptr; }; diff --git a/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp b/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp index 26e1bcdf53..34a7dcb70d 100644 --- a/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp +++ b/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp @@ -352,8 +352,8 @@ PreviewWrap::PreviewWrap( _style->apply(_theme.get()); session->data().viewRepaintRequest( - ) | rpl::on_next([=](not_null view) { - if (view == _view->view()) { + ) | rpl::on_next([=](Data::RequestViewRepaint data) { + if (data.view == _view->view()) { update(); } }, lifetime()); @@ -416,6 +416,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { auto context = _theme->preparePaintContext( _style.get(), rect(), + rect(), e->rect(), !window()->isActiveWindow()); p.translate(_position); diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 4935b11d39..d903fb8d40 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -1554,7 +1554,7 @@ void GenericCreditsEntryBody( if (uniqueGift) { AddSkip(content, st::defaultVerticalListSkip * 2); - AddUniqueCloseButton(box, st, [=](not_null menu) { + AddUniqueCloseMoreButton(box, st, [=](not_null menu) { const auto type = SavedStarGiftMenuType::View; FillUniqueGiftMenu(show, menu, e, type, st); }); @@ -3572,4 +3572,51 @@ void MaybeRequestBalanceIncrease( }, state->lifetime); } +void AddUniqueCloseMoreButton( + not_null box, + Settings::CreditsEntryBoxStyleOverrides st, + Fn)> fillMenu) { + const auto close = Ui::CreateChild( + box, + st::uniqueCloseButton); + const auto menu = fillMenu + ? Ui::CreateChild(box, st::uniqueMenuButton) + : nullptr; + close->show(); + close->raise(); + if (menu) { + menu->show(); + menu->raise(); + } + box->widthValue() | rpl::on_next([=](int width) { + close->moveToRight(0, 0, width); + close->raise(); + if (menu) { + menu->moveToRight(close->width(), 0, width); + menu->raise(); + } + }, close->lifetime()); + close->setClickedCallback([=] { + box->closeBox(); + }); + if (menu) { + const auto state = menu->lifetime().make_state< + base::unique_qptr + >(); + menu->setClickedCallback([=] { + if (*state) { + *state = nullptr; + return; + } + *state = base::make_unique_q( + menu, + st.menu ? *st.menu : st::popupMenuWithIcons); + fillMenu(state->get()); + if (!(*state)->empty()) { + (*state)->popup(QCursor::pos()); + } + }); + } +} + } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index ff1e7845f5..0d0fde918e 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -317,4 +317,9 @@ void AddMiniStars( int boxWidth, float64 heightRatio); +void AddUniqueCloseMoreButton( + not_null box, + Settings::CreditsEntryBoxStyleOverrides st, + Fn)> fillMenu = nullptr); + } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_experimental.cpp b/Telegram/SourceFiles/settings/settings_experimental.cpp index ef444d1aad..e30dd23405 100644 --- a/Telegram/SourceFiles/settings/settings_experimental.cpp +++ b/Telegram/SourceFiles/settings/settings_experimental.cpp @@ -18,8 +18,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/gl/gl_detection.h" #include "ui/chat/chat_style_radius.h" #include "base/options.h" +#include "boxes/moderate_messages_box.h" #include "core/application.h" #include "core/launcher.h" +#include "core/sandbox.h" #include "chat_helpers/tabbed_panel.h" #include "dialogs/dialogs_widget.h" #include "history/history_item_components.h" @@ -162,6 +164,7 @@ void SetupExperimental( addToggle(Window::Notifications::kOptionGNotification); addToggle(Core::kOptionFreeType); addToggle(Core::kOptionSkipUrlSchemeRegister); + addToggle(Core::kOptionDeadlockDetector); addToggle(Data::kOptionExternalVideoPlayer); addToggle(Window::kOptionNewWindowsSizeAsFirst); addToggle(MTP::details::kOptionPreferIPv6); @@ -170,10 +173,7 @@ void SetupExperimental( } addToggle(Window::kOptionDisableTouchbar); addToggle(Info::kAlternativeScrollProcessing); - -#ifdef Q_OS_MAC - addToggle("text-recognition-mac"); -#endif // Q_OS_MAC + addToggle(kModerateCommonGroups); } } // namespace diff --git a/Telegram/SourceFiles/settings/settings_global_ttl.cpp b/Telegram/SourceFiles/settings/settings_global_ttl.cpp index 8b970df6d9..845636edfe 100644 --- a/Telegram/SourceFiles/settings/settings_global_ttl.cpp +++ b/Telegram/SourceFiles/settings/settings_global_ttl.cpp @@ -312,9 +312,12 @@ void GlobalTTL::rebuildButtons(TimeId currentTTL) const { QString()); radio->setAttribute(Qt::WA_TransparentForMouseEvents); radio->show(); + const auto padding = button->st().padding; button->sizeValue( - ) | rpl::on_next([=] { - radio->moveToRight(0, radio->checkRect().top()); + ) | rpl::on_next([=](QSize s) { + radio->moveToLeft( + s.width() - radio->checkRect().width() - padding.left(), + radio->checkRect().top()); }, radio->lifetime()); } _buttons->resizeToWidth(width()); diff --git a/Telegram/SourceFiles/settings/settings_information.cpp b/Telegram/SourceFiles/settings/settings_information.cpp index 1a6afb3715..959bee6461 100644 --- a/Telegram/SourceFiles/settings/settings_information.cpp +++ b/Telegram/SourceFiles/settings/settings_information.cpp @@ -930,7 +930,13 @@ not_null*> AccountsList::setupAdd() { return; } else if (which != Qt::RightButton || !IsAltShift(button->clickModifiers())) { +#ifdef _DEBUG + if (which != Qt::RightButton) { + return; + } +#else // _DEBUG return; +#endif // !_DEBUG } _contextMenu = base::make_unique_q(_outer); _contextMenu->addAction("Production Server", [=] { diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index f17dc58334..5ff01ed465 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -206,7 +206,8 @@ AdminLog::OwnedItem GenerateForwardedItem( MTPint(), // report_delivery_until_date MTPlong(), // paid_message_stars MTPSuggestedPost(), - MTPint() // schedule_repeat_period + MTPint(), // schedule_repeat_period + MTPstring() // summary_from_language ).match([&](const MTPDmessage &data) { return history->makeMessage( history->nextNonHistoryEntryId(), @@ -1112,6 +1113,7 @@ object_ptr ForwardsPrivacyController::setupAboveWidget( _chatStyle.get(), widget->rect(), widget->rect(), + widget->rect(), controller->isGifPausedAtLeastFor( Window::GifPauseReason::Layer)); p.translate(padding / 2, padding + view->marginBottom()); diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp index 3802396ffc..4022608e00 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp @@ -660,6 +660,9 @@ void SetupLoginEmail( ) | rpl::map([](const State &state) { return state.loginEmailPattern; }); auto text = tr::lng_settings_cloud_login_email_section_title(); auto label = rpl::duplicate(email) | rpl::map([](QString email) { + if (email.contains(' ')) { + return tr::lng_settings_cloud_password_off(tr::now, tr::rich); + } return Ui::Text::WrapEmailPattern( email.replace(QRegularExpression("\\*{4,}"), "****")); }); diff --git a/Telegram/SourceFiles/settings/settings_shortcuts.cpp b/Telegram/SourceFiles/settings/settings_shortcuts.cpp index 88c7c2c095..6a38f972e8 100644 --- a/Telegram/SourceFiles/settings/settings_shortcuts.cpp +++ b/Telegram/SourceFiles/settings/settings_shortcuts.cpp @@ -107,6 +107,8 @@ struct Labeled { { C::RecordVoice, tr::lng_shortcuts_record_voice_message() }, { C::RecordRound, tr::lng_shortcuts_record_round_message() }, separator, + { C::ShowAdminLog, tr::lng_shortcuts_admin_log() }, + separator, { C::MediaViewerFullscreen, tr::lng_shortcuts_media_fullscreen() }, separator, { C::MediaPlay, tr::lng_shortcuts_media_play() }, diff --git a/Telegram/SourceFiles/support/support_autocomplete.cpp b/Telegram/SourceFiles/support/support_autocomplete.cpp index 16af43532c..6573ca2822 100644 --- a/Telegram/SourceFiles/support/support_autocomplete.cpp +++ b/Telegram/SourceFiles/support/support_autocomplete.cpp @@ -557,6 +557,7 @@ void ConfirmContactBox::paintEvent(QPaintEvent *e) { _chatStyle.get(), rect(), rect(), + rect(), controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Layer)); p.translate(st::boxPadding.left(), 0); if (_comment) { diff --git a/Telegram/SourceFiles/ui/boxes/about_cocoon_box.cpp b/Telegram/SourceFiles/ui/boxes/about_cocoon_box.cpp new file mode 100644 index 0000000000..2e18360744 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/about_cocoon_box.cpp @@ -0,0 +1,288 @@ +/* +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 "ui/boxes/about_cocoon_box.h" + +#include "lang/lang_keys.h" +#include "ui/controls/feature_list.h" +#include "ui/effects/ministar_particles.h" +#include "ui/layers/generic_box.h" +#include "ui/layers/generic_box.h" +#include "ui/painter.h" +#include "ui/text/text_utilities.h" +#include "ui/top_background_gradient.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/shadow.h" +#include "styles/style_chat.h" +#include "styles/style_credits.h" +#include "styles/style_info.h" // infoStarsUnderstood +#include "styles/style_layers.h" +#include "styles/style_menu_icons.h" + +namespace Ui { +namespace { + +void AddCocoonBoxCover(not_null container) { + const auto cover = container->add(object_ptr(container)); + + static const auto gradientEdge = QColor(0x06, 0x11, 0x29); + static const auto gradientCenter = QColor(0x09, 0x16, 0x43); + + static const auto gradientLeft = QColor(0x68, 0xc9, 0xff); + static const auto gradientRight = QColor(0xcb, 0x57, 0xff); + + static const auto textColor = QColor(0xb8, 0xc9, 0xef); + static const auto boldColor = QColor(0xff, 0xff, 0xff); + + const auto logoTop = st::cocoonLogoTop; + const auto logoSize = st::cocoonLogoSize; + const auto ratio = style::DevicePixelRatio(); + auto logo = QImage(u":/gui/art/cocoon.webp"_q).scaled( + QSize(logoSize, logoSize) * ratio, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + logo.setDevicePixelRatio(ratio); + const auto titleTop = logoTop + logoSize + st::cocoonTitleTop; + const auto subtitleTop = titleTop + + st::cocoonTitleFont->height + + st::cocoonSubtitleTop; + + const auto boldToColorized = [](QString text) { + auto result = tr::rich(text); + auto &entities = result.entities; + for (auto i = entities.begin(); i != entities.end(); ++i) { + if (i->type() == EntityType::Bold) { + i = entities.insert( + i, + EntityInText( + EntityType::Colorized, + i->offset(), + i->length())); + ++i; + } + } + return result; + }; + + struct State { + QImage gradient; + Ui::Animations::Basic animation; + std::optional particles; + style::owned_color subtitleFg = style::owned_color{ textColor }; + style::owned_color subtitleBoldFg = style::owned_color{ boldColor }; + style::FlatLabel subtitleSt = st::cocoonSubtitle; + }; + const auto state = cover->lifetime().make_state(); + state->subtitleSt.textFg = state->subtitleFg.color(); + state->subtitleSt.palette.linkFg = state->subtitleBoldFg.color(); + state->animation.init([=] { + cover->update(); + if (anim::Disabled()) { + state->animation.stop(); + } + }); + + constexpr auto kParticlesCount = 50; + state->particles.emplace( + Ui::StarParticles::Type::RadialInside, + kParticlesCount, + st::cocoonLogoSize / 12); + state->particles->setSpeed(0.05); + auto particleColors = std::vector(); + constexpr auto kParticleColors = 12; + particleColors.reserve(kParticleColors); + for (auto i = 0; i != kParticleColors; ++i) { + particleColors.push_back(anim::color( + gradientLeft, + gradientRight, + float64(i) / (kParticleColors - 1))); + } + state->particles->setColors(std::move(particleColors)); + + const auto subtitle = CreateChild( + cover, + tr::lng_translate_cocoon_subtitle( + lt_text, + tr::lng_translate_cocoon_explain(boldToColorized), + tr::rich), + state->subtitleSt); + subtitle->setTryMakeSimilarLines(true); + + cover->widthValue() | rpl::on_next([=](int width) { + const auto available = width + - st::boxRowPadding.left() + - st::boxRowPadding.right(); + subtitle->resizeToWidth(available); + subtitle->moveToLeft(st::boxRowPadding.left(), subtitleTop); + + const auto bottom = st::cocoonSubtitleBottom; + cover->resize(width, subtitle->y() + subtitle->height() + bottom); + }, cover->lifetime()); + + cover->paintRequest() | rpl::on_next([=] { + auto p = Painter(cover); + + const auto width = cover->width(); + const auto ratio = style::DevicePixelRatio(); + if (state->gradient.size() != cover->size() * ratio) { + state->gradient = Ui::CreateTopBgGradient( + cover->size(), + gradientCenter, + gradientEdge); + + auto font = st::cocoonTitleFont->f; + font.setWeight(QFont::Bold); + auto metrics = QFontMetrics(font); + + const auto text = tr::lng_translate_cocoon_title(tr::now); + const auto textw = metrics.horizontalAdvance(text); + const auto left = (width - textw) / 2; + + auto q = QPainter(&state->gradient); + auto hq = PainterHighQualityEnabler(q); + + auto gradient = QLinearGradient(left, 0, left + textw, 0); + gradient.setStops({ + { 0., gradientLeft }, + { 1., gradientRight }, + }); + auto pen = QPen(QBrush(gradient), 1.); + q.setPen(pen); + q.setFont(font); + q.setBrush(Qt::NoBrush); + + q.drawText(left, titleTop + metrics.ascent(), text); + } + p.drawImage(0, 0, state->gradient); + + const auto logoRect = QRect( + (width - logoSize) / 2, + logoTop, + logoSize, + logoSize); + const auto paddingAdd = int(base::SafeRound(logoTop * 1.2)); + const auto particlesRect = logoRect.marginsAdded( + { paddingAdd, paddingAdd, paddingAdd, paddingAdd }); + + state->particles->paint(p, particlesRect, crl::now(), false); + if (!anim::Disabled() && !state->animation.animating()) { + state->animation.start(); + } + p.drawImage(logoRect, logo); + }, cover->lifetime()); +} + +struct CocoonLinkInfo { + QString text; + QString link; +}; + +[[nodiscard]] CocoonLinkInfo CocoonMention() { + const auto mention = tr::lng_translate_cocoon_private_mention(tr::now); + const auto username = QString(mention).replace('@', QString()); + const auto link = u"tg://resolve?domain="_q + username; + return { mention, link }; +} + +[[nodiscard]] CocoonLinkInfo CocoonDomain() { + const auto domain = tr::lng_translate_cocoon_everyone_domain(tr::now); + const auto link = u"https://"_q + domain; + return { domain, link }; +} + +[[nodiscard]] CocoonLinkInfo CocoonDirect() { + const auto text = tr::lng_translate_cocoon_text_link(tr::now); + const auto link = u"https://"_q + text; + return { text, link }; +} + +} // namespace + +void AboutCocoonBox(not_null box) { + box->setWidth(st::boxWideWidth); + box->setStyle(st::stakeBox); + box->setNoContentMargin(true); + + const auto container = box->verticalLayout(); + AddCocoonBoxCover(container); + + AddUniqueCloseButton(box); + + const auto mention = CocoonMention(); + const auto domain = CocoonDomain(); + const auto direct = CocoonDirect(); + const auto features = std::vector{ + { + st::menuIconLock, + tr::lng_translate_cocoon_private_title(tr::now), + tr::lng_translate_cocoon_private_text( + tr::now, + lt_mention, + tr::link(mention.text, mention.link), + tr::rich), + }, + { + st::menuIconStats, + tr::lng_translate_cocoon_efficient_title(tr::now), + tr::lng_translate_cocoon_efficient_text(tr::now, tr::rich), + }, + { + st::menuIconGiftPremium, + tr::lng_translate_cocoon_everyone_title(tr::now), + tr::lng_translate_cocoon_everyone_text( + tr::now, + lt_domain, + tr::link(domain.text, domain.link), + tr::rich), + }, + }; + auto margin = QMargins(0, st::defaultVerticalListSkip, 0, 0); + for (const auto &feature : features) { + box->addRow( + MakeFeatureListEntry(box, feature), + st::boxRowPadding + margin); + margin = {}; + } + + box->addRow( + object_ptr(box), + st::cocoonJoinSeparatorPadding); + + box->addRow( + object_ptr( + box, + tr::lng_translate_cocoon_text( + lt_link, + rpl::single(tr::link(direct.text, direct.link)), + tr::rich), + st::cocoonJoinLabel), + style::al_top); + + box->addButton(rpl::single(QString()), [=] { + box->closeBox(); + })->setText(rpl::single(Ui::Text::IconEmoji( + &st::infoStarsUnderstood + ).append(' ').append(tr::lng_translate_cocoon_done(tr::now)))); +} + +void AddUniqueCloseButton(not_null box) { + const auto close = Ui::CreateChild( + box, + st::uniqueCloseButton); + close->show(); + close->raise(); + box->widthValue() | rpl::on_next([=](int width) { + close->moveToRight(0, 0, width); + close->raise(); + }, close->lifetime()); + close->setClickedCallback([=] { + box->closeBox(); + }); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/boxes/about_cocoon_box.h b/Telegram/SourceFiles/ui/boxes/about_cocoon_box.h new file mode 100644 index 0000000000..1399ca6a99 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/about_cocoon_box.h @@ -0,0 +1,17 @@ +/* +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 Ui { + +class GenericBox; + +void AboutCocoonBox(not_null box); +void AddUniqueCloseButton(not_null box); + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/boxes/emoji_stake_box.cpp b/Telegram/SourceFiles/ui/boxes/emoji_stake_box.cpp new file mode 100644 index 0000000000..3cf9b425cd --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/emoji_stake_box.cpp @@ -0,0 +1,651 @@ +/* +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 "ui/boxes/emoji_stake_box.h" + +#include "base/event_filter.h" +#include "base/object_ptr.h" +#include "chat_helpers/stickers_lottie.h" +#include "data/components/credits.h" +#include "data/stickers/data_custom_emoji.h" +#include "data/data_document.h" +#include "data/data_session.h" +#include "info/channel_statistics/earn/earn_format.h" +#include "info/channel_statistics/earn/earn_icons.h" +#include "lang/lang_keys.h" +#include "lottie/lottie_icon.h" +#include "main/main_app_config.h" +#include "main/main_session.h" +#include "settings/settings_common.h" // CreateLottieIcon +#include "settings/settings_credits_graphics.h" // AddBalanceWidget +#include "ui/controls/ton_common.h" +#include "ui/text/custom_emoji_helper.h" +#include "ui/text/custom_emoji_text_badge.h" +#include "ui/text/text_lottie_custom_emoji.h" +#include "ui/toast/toast.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/widgets/fields/number_input.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/padding_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/basic_click_handlers.h" // UrlClickHandler +#include "ui/painter.h" +#include "ui/rect.h" +#include "ui/vertical_list.h" +#include "styles/style_boxes.h" +#include "styles/style_calls.h" // confcallLinkFooterOr +#include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_credits.h" // creditsHistoryRightSkip +#include "styles/style_layers.h" +#include "styles/style_settings.h" // settingsCloudPasswordIconSize +#include "styles/style_widgets.h" + +#include + +namespace Ui { +namespace { + +[[nodiscard]] not_null AddMoneyInputIcon( + not_null parent, + Text::PaletteDependentEmoji emoji) { + auto helper = Text::CustomEmojiHelper(); + auto text = helper.paletteDependent(std::move(emoji)); + return CreateChild( + parent, + rpl::single(std::move(text)), + st::defaultFlatLabel, + st::defaultPopupMenu, + helper.context()); +} + +[[nodiscard]] QImage MakeEmojiFrame(int index, int size) { + const auto path = u":/gui/dice/dice%1.svg"_q.arg(index); + + const auto ratio = style::DevicePixelRatio(); + auto svg = QSvgRenderer(path); + auto result = QImage( + QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(ratio); + result.fill(Qt::transparent); + + auto p = QPainter(&result); + svg.render(&p, QRect(0, 0, size, size)); + p.end(); + + return result; +} + +[[nodiscard]] object_ptr MakeLogo(not_null box) { + const auto &size = st::settingsCloudPasswordIconSize; + auto icon = Settings::CreateLottieIcon( + box->verticalLayout(), + { .name = u"dice_6"_q, .sizeOverride = { size, size } }, + QMargins()); + const auto animate = std::move(icon.animate); + box->showFinishes() | rpl::take(1) | rpl::on_next([=] { + animate(anim::repeat::once); + }, box->lifetime()); + return std::move(icon.widget); +} + +[[nodiscard]] object_ptr MakeTable( + not_null parent, + const EmojiGameStakeArgs &args) { + auto result = object_ptr(parent); + const auto raw = result.data(); + + struct State { + std::array dices; + std::array multiplicators; + }; + const auto state = raw->lifetime().make_state(); + const auto serialize = [&](int milliReward) { + return QString(QChar(0xD7)) + QString::number(milliReward / 1000.); + }; + const auto esize = st::stakeEmojiSize; + for (auto i = 0; i != 6; ++i) { + state->dices[i] = MakeEmojiFrame(i + 1, esize); + + const auto value = (i < args.milliRewards.size()) + ? args.milliRewards[i] + : 0; + state->multiplicators[i].setText( + st::semiboldTextStyle, + serialize(value)); + } + state->multiplicators[6].setText( + st::semiboldTextStyle, + serialize(args.jackpotMilliReward)); + + raw->paintOn([=](QPainter &p) { + auto path = QPainterPath(); + const auto &st = st::defaultTable; + const auto border = st.border; + const auto half = border / 2.; + const auto add = QMargins(border, border, border, border); + const auto inner = raw->rect().marginsRemoved(add); + path.addRoundedRect(inner, st.radius, st.radius); + { + const auto y = border + inner.height() / 2.; + path.moveTo(border + half, y); + path.lineTo(border + inner.width() - half, y); + } + { + const auto x = border + inner.width() / 2.; + path.moveTo(x, border + half); + path.lineTo(x, border + inner.height() - half); + } + { + const auto x = border + (inner.width() - border) / 4.; + path.moveTo(x, border + half); + path.lineTo(x, border + inner.height() - half); + } + { + const auto x = border + + (inner.width() / 2.) + + (inner.width() - border) / 4.; + path.moveTo(x, border + half); + path.lineTo(x, border + (inner.height() / 2.) - half); + } + auto pen = st.borderFg->p; + pen.setWidth(st.border); + p.setPen(pen); + p.setBrush(Qt::NoBrush); + + auto hq = PainterHighQualityEnabler(p); + p.drawPath(path); + + const auto etop = st::stakeEmojiTop; + const auto ttop = etop + esize; + { + auto left = border + half; + const auto width = (inner.width() - 3 * border) / 4.; + auto top = border + half; + for (auto i = 0; i != 7; ++i, left += width + border) { + if (i == 4) { + left -= 4 * (width + border); + top += border + half + inner.height() / 2.; + } + const auto x = int(base::SafeRound(left)); + const auto y = int(base::SafeRound(top)); + const auto right = (i < 6) + ? int(base::SafeRound(left + width)) + : int(base::SafeRound(left + 2 * width + border)); + const auto w = (right - x); + + if (i < 6) { + p.drawImage( + x + (w - esize) / 2, + y + etop, + state->dices[i]); + } else { + const auto &last = state->dices.back(); + for (auto j = 0; j != 3; ++j) { + p.drawImage( + x + (w - 3 * esize) / 2 + (esize * j), + y + etop, + last); + } + } + + p.setPen(st::windowBoldFg); + const auto &mul = state->multiplicators[i]; + mul.draw(p, { + .position = { x + (w - mul.maxWidth()) / 2, y + ttop }, + }); + } + } + }); + raw->widthValue() | rpl::on_next([=](int width) { + raw->resize(width, width / 3); + }, raw->lifetime()); + return result; +} + +} // namespace + +not_null AddStarsInputField( + not_null container, + StarsInputFieldArgs &&args) { + const auto wrap = container->add( + object_ptr( + container, + st::editTagField.heightMin), + st::boxRowPadding); + const auto result = CreateChild( + wrap, + st::editTagField, + rpl::single(u"0"_q), + args.value ? QString::number(*args.value) : QString(), + args.max ? args.max : std::numeric_limits::max()); + const auto icon = AddMoneyInputIcon( + result, + Earn::IconCreditsEmoji()); + + wrap->widthValue() | rpl::on_next([=](int width) { + icon->move(st::starsFieldIconPosition); + result->move(0, 0); + result->resize(width, result->height()); + wrap->resize(width, result->height()); + }, wrap->lifetime()); + + return result; +} + +not_null AddTonInputField( + not_null container, + TonInputFieldArgs &&args) { + const auto wrap = container->add( + object_ptr( + container, + st::editTagField.heightMin), + st::boxRowPadding); + const auto result = CreateTonAmountInput( + wrap, + rpl::single('0' + TonAmountSeparator() + '0'), + args.value); + const auto icon = AddMoneyInputIcon( + result, + Earn::IconCurrencyEmoji()); + + wrap->widthValue() | rpl::on_next([=](int width) { + icon->move(st::tonFieldIconPosition); + result->move(0, 0); + result->resize(width, result->height()); + wrap->resize(width, result->height()); + }, wrap->lifetime()); + + return result; +} + +void AddApproximateUsd( + not_null field, + not_null session, + rpl::producer price) { + auto value = std::move(price) | rpl::map([=](CreditsAmount amount) { + if (!amount) { + return QString(); + } + const auto appConfig = &session->appConfig(); + const auto rate = amount.ton() + ? appConfig->currencySellRate() + : (appConfig->starsSellRate() / 100.); + return Info::ChannelEarn::ToUsd(amount, rate, 2); + }); + const auto usd = Ui::CreateChild( + field, + std::move(value), + st::suggestPriceEstimate); + const auto move = [=] { + usd->moveToRight(0, st::suggestPriceEstimateTop); + }; + base::install_event_filter(field, [=](not_null e) { + if (e->type() == QEvent::Resize) { + move(); + } + return base::EventFilterResult::Continue; + }); + usd->widthValue() | rpl::on_next(move, usd->lifetime()); +} + +void InsufficientTonBox( + not_null box, + not_null session, + CreditsAmount required) { + box->setStyle(st::suggestPriceBox); + box->addTopButton(st::boxTitleClose, [=] { + box->closeBox(); + }); + + auto icon = Settings::CreateLottieIcon( + box->verticalLayout(), + { + .name = u"diamond"_q, + .sizeOverride = st::normalBoxLottieSize, + }, + {}); + box->setShowFinishedCallback([animate = std::move(icon.animate)] { + animate(anim::repeat::loop); + }); + box->addRow(std::move(icon.widget), st::lowTonIconPadding); + const auto add = required - session->credits().tonBalance(); + const auto nano = add.whole() * Ui::kNanosInOne + add.nano(); + const auto amount = Ui::FormatTonAmount(nano).full; + box->addRow( + object_ptr( + box, + tr::lng_suggest_low_ton_title(tr::now, lt_amount, amount), + st::boxTitle), + st::boxRowPadding + st::lowTonTitlePadding, + style::al_top); + const auto label = box->addRow( + object_ptr( + box, + tr::lng_suggest_low_ton_text(tr::rich), + st::lowTonText), + st::boxRowPadding + st::lowTonTextPadding, + style::al_top); + label->setTryMakeSimilarLines(true); + label->resizeToWidth( + st::boxWidth - st::boxRowPadding.left() - st::boxRowPadding.right()); + + const auto url = tr::lng_suggest_low_ton_fragment_url(tr::now); + const auto button = box->addButton( + tr::lng_suggest_low_ton_fragment(), + [=] { UrlClickHandler::Open(url); }); + const auto buttonWidth = st::boxWidth + - rect::m::sum::h(st::suggestPriceBox.buttonPadding); + button->widthValue() | rpl::filter([=] { + return (button->widthNoMargins() != buttonWidth); + }) | rpl::on_next([=] { + button->resizeToWidth(buttonWidth); + }, button->lifetime()); +} + +void AddStakePresets( + not_null container, + not_null session, + Fn callback) { + const auto presets = session->appConfig().stakeDiceNanoTonSuggested(); + if (presets.empty()) { + return; + } + constexpr auto kPerRow = 4; + const auto count = int(presets.size()); + const auto rows = (count + kPerRow - 1) / kPerRow; + const auto smallrow = count / rows; + const auto bigrow = smallrow + 1; + const auto bigrows = count % rows; + struct State { + std::vector>> buttons; + }; + const auto wrap = container->add( + object_ptr( + container, + (rows * st::stakePresetButton.height + + (rows - 1) * st::stakePresetButtonSkip.y())), + (st::boxRowPadding + + QMargins(0, st::stakeBox.buttonPadding.top(), 0, 0))); + const auto diamond = QString::fromUtf8(" \xf0\x9f\x92\x8e"); + const auto state = wrap->lifetime().make_state(); + for (auto i = 0; i != rows; ++i) { + const auto big = (i < bigrows); + const auto cols = big ? bigrow : smallrow; + auto &row = state->buttons.emplace_back(); + row.reserve(cols); + for (auto j = 0; j != cols; ++j) { + const auto index = i * bigrow + j - std::max(0, i - bigrows); + const auto nanoTon = presets[index]; + const auto button = CreateChild( + wrap, + rpl::single(FormatTonAmount(nanoTon).full + diamond), + st::stakePresetButton); + button->setClickedCallback([=] { + callback(nanoTon); + }); + row.push_back(button); + } + } + wrap->widthValue() | rpl::on_next([=](int width) { + auto y = 0; + const auto xskip = st::stakePresetButtonSkip.x(); + const auto yskip = st::stakePresetButtonSkip.y(); + for (auto i = 0; i != state->buttons.size(); ++i) { + const auto &row = state->buttons[i]; + const auto cols = int(row.size()); + const auto singlew = (width - (cols - 1) * xskip) / cols; + auto x = 0; + for (const auto button : row) { + button->setFullWidth(singlew); + button->move(x, y); + x += singlew + xskip; + } + y += st::stakePresetButton.height + yskip; + } + }, wrap->lifetime()); +} + +void EmojiGameStakeBox( + not_null box, + EmojiGameStakeArgs &&args) { + box->setStyle(st::stakeBox); + box->setWidth(st::boxWideWidth); + box->setNoContentMargin(true); + const auto container = box->verticalLayout(); + + const auto skip = st::confcallLinkHeaderIconPadding.bottom(); + box->addRow( + MakeLogo(box), + st::boxRowPadding + QMargins(0, 0, 0, skip), + style::al_top); + + auto helper = Text::CustomEmojiHelper(); + const auto beta = helper.paletteDependent( + Text::CustomEmojiTextBadge( + tr::lng_stake_game_beta(tr::now).toUpper(), + st::customEmojiTextBadge)); + const auto sixText = helper.image({ + .image = MakeEmojiFrame(6, st::emojiSize), + .textColor = false, + }); + auto title = tr::lng_stake_game_title( + tr::marked + ) | rpl::map([=](TextWithEntities &&text) { + return text.append(' ').append(beta); + }); + box->addRow( + object_ptr( + box, + std::move(title), + st::boxTitle, + st::defaultPopupMenu, + helper.context()), + st::boxRowPadding + st::confcallLinkTitlePadding, + style::al_top); + box->addRow( + object_ptr( + box, + tr::lng_stake_game_about(tr::rich), + st::confcallLinkCenteredText), + st::boxRowPadding + QMargins(0, 0, 0, skip), + style::al_top + )->setTryMakeSimilarLines(true); + + const auto sub = st::boxRowPadding - st::defaultSubsectionTitlePadding; + const auto subtitlePadding = QMargins(sub.left(), 0, sub.right(), 0); + AddSubsectionTitle( + container, + tr::lng_stake_game_results(), + subtitlePadding); + + const auto half = (st::defaultTable.border + 1) / 2; + box->addRow( + MakeTable(box, args), + st::boxRowPadding + QMargins(-half, 0, -half, skip / 2), + style::al_top); + + const auto sixContext = helper.context(); + const auto factory = [=]( + QStringView data, + const Text::MarkedContext &context) { + if (auto result = sixContext.customEmojiFactory(data, context)) { + return std::make_unique( + std::move(result), + 0, + true); + } + return std::unique_ptr(); + }; + const auto context = Text::MarkedContext{ + .customEmojiFactory = factory, + }; + const auto resets = box->addRow( + object_ptr( + box, + tr::lng_stake_game_resets( + lt_emoji, + rpl::single(sixText), + tr::rich), + st::confcallLinkFooterOr, + st::defaultPopupMenu, + context), + st::boxRowPadding, + style::al_top); + resets->setTryMakeSimilarLines(true); + resets->setAnimationsPausedCallback([=] { + return FlatLabel::WhichAnimationsPaused::All; + }); + + AddSubsectionTitle( + container, + tr::lng_stake_game_your(), + subtitlePadding + QMargins(0, skip, 0, sub.bottom())); + const auto field = AddTonInputField(container, { + .value = args.currentStake, + }); + box->setFocusCallback([=] { + field->setFocusFast(); + }); + + const auto fromNanoTon = [](int64 nanoton) { + return CreditsAmount( + nanoton / Ui::kNanosInOne, + nanoton % Ui::kNanosInOne, + CreditsType::Ton); + }; + const auto price = field->lifetime().make_state< + rpl::variable + >(rpl::single( + args.currentStake + ) | rpl::then(field->changes() | rpl::map([=] { + return Ui::ParseTonAmountString(field->getLastText()).value_or(0); + })) | rpl::map(fromNanoTon)); + AddApproximateUsd( + field, + args.session, + price->value()); + + AddStakePresets(container, args.session, [=](int64 nanoTon) { + field->setText(Ui::FormatTonAmount(nanoTon).full); + }); + + const auto submit = args.submit; + const auto callback = [=] { + const auto text = field->getLastText(); + const auto now = price->current(); + const auto credits = &args.session->credits(); + const auto appConfig = &args.session->appConfig(); + const auto min = fromNanoTon(appConfig->stakeDiceNanoTonMin()); + const auto max = fromNanoTon(appConfig->stakeDiceNanoTonMax()); + if (!now || now < min || now > max) { + field->showError(); + } else if (credits->tonBalance() < now) { + box->uiShow()->show(Box(InsufficientTonBox, args.session, now)); + } else { + box->closeBox(); + submit(now.whole() * Ui::kNanosInOne + now.nano()); + } + }; + field->submits() | rpl::on_next(callback, field->lifetime()); + const auto button = box->addButton(rpl::single(QString()), callback); + + button->setText(tr::lng_stake_game_save_and_roll( + ) | rpl::map([=](QString text) { + return Text::IconEmoji( + &st::stakeEmojiIcon, + QString::fromUtf8("\xf0\x9f\x8e\xb2") + ).append(' ').append(text); + })); + + const auto close = CreateChild( + container, + st::boxTitleClose); + close->setClickedCallback([=] { box->closeBox(); }); + container->widthValue() | rpl::on_next([=](int) { + close->moveToRight(0, 0); + }, close->lifetime()); + + const auto session = args.session; + session->credits().tonLoad(true); + const auto balance = Settings::AddBalanceWidget( + container, + session, + session->credits().tonBalanceValue(), + false); + rpl::combine( + balance->sizeValue(), + container->sizeValue() + ) | rpl::on_next([=](const QSize &, const QSize &) { + balance->moveToLeft( + st::creditsHistoryRightSkip * 2, + st::creditsHistoryRightSkip); + balance->update(); + }, balance->lifetime()); +} + +Toast::Config MakeEmojiGameStakeToast( + std::shared_ptr show, + EmojiGameStakeArgs &&args) { + auto config = Toast::Config{ + .st = &st::historyDiceToast, + .duration = Ui::Toast::kDefaultDuration * 2, + }; + auto helper = Text::CustomEmojiHelper(); + static const auto makeBg = [] { + auto result = st::mediaviewTextLinkFg->c; + result.setAlphaF(0.12); + return result; + }; + struct State { + State() : bg(makeBg), badge(st::stakeChangeBadge) { + badge.textBg = badge.textBgOver = bg.color(); + } + + style::complex_color bg; + style::RoundButton badge; + }; + auto state = std::make_shared(); + const auto badge = helper.paletteDependent( + Ui::Text::CustomEmojiTextBadge( + tr::lng_about_random_stake_change(tr::now), + state->badge, + st::stateChangeBadgeMargin)); + const auto diamond = QString::fromUtf8("\xf0\x9f\x92\x8e"); + config.text.append( + tr::lng_about_random_stake( + tr::now, + lt_amount, + tr::bold(diamond + Ui::FormatTonAmount(args.currentStake).full), + tr::marked) + ).append(' ').append( + tr::link(badge, u"internal:stake_change"_q) + ).append(u" "_q.repeated(10)).append(tr::link( + tr::semibold(tr::lng_about_random_send(tr::now)), + u"internal:stake_send"_q)); + config.textContext = helper.context(); + + config.filter = [=, state = std::move(state)]( + const ClickHandlerPtr &handler, + Qt::MouseButton button) { + if (button == Qt::LeftButton) { + const auto url = handler ? handler->url() : QString(); + if (url == u"internal:stake_change"_q) { + show->show(Ui::MakeEmojiGameStakeBox(base::duplicate(args))); + } else if (url == u"internal:stake_send"_q) { + args.submit(args.currentStake); + } + } + return false; + }; + + return config; +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/boxes/emoji_stake_box.h b/Telegram/SourceFiles/ui/boxes/emoji_stake_box.h new file mode 100644 index 0000000000..71451ac10b --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/emoji_stake_box.h @@ -0,0 +1,69 @@ +/* +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 "ui/layers/generic_box.h" + +namespace Main { +class Session; +} // namespace Main + +namespace Ui::Toast { +struct Config; +} // namespace Ui::Toast + +namespace Ui { + +class Show; +class InputField; +class NumberInput; +class VerticalLayout; + +struct StarsInputFieldArgs { + std::optional value; + int64 max = 0; +}; +[[nodiscard]] not_null AddStarsInputField( + not_null container, + StarsInputFieldArgs &&args); + +struct TonInputFieldArgs { + int64 value = 0; +}; +[[nodiscard]] not_null AddTonInputField( + not_null container, + TonInputFieldArgs &&args); + +void AddApproximateUsd( + not_null field, + not_null session, + rpl::producer price); + +void InsufficientTonBox( + not_null box, + not_null session, + CreditsAmount required); + +struct EmojiGameStakeArgs { + not_null session; + int64 currentStake = 0; + std::array milliRewards; + int jackpotMilliReward = 0; + Fn submit; +}; +void EmojiGameStakeBox(not_null box, EmojiGameStakeArgs &&args); +[[nodiscard]] inline object_ptr MakeEmojiGameStakeBox( + EmojiGameStakeArgs &&args) { + return Box(EmojiGameStakeBox, std::move(args)); +} + +[[nodiscard]] Toast::Config MakeEmojiGameStakeToast( + std::shared_ptr show, + EmojiGameStakeArgs &&args); + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/cached_round_corners.cpp b/Telegram/SourceFiles/ui/cached_round_corners.cpp index 852ded08a0..7da69a2299 100644 --- a/Telegram/SourceFiles/ui/cached_round_corners.cpp +++ b/Telegram/SourceFiles/ui/cached_round_corners.cpp @@ -76,7 +76,7 @@ void CreateMaskCorners() { } void CreatePaletteCorners() { - PrepareCorners(MenuCorners, st::roundRadiusSmall, st::menuBg); + PrepareCorners(MenuCorners, st::innerDropdownRadius, st::menuBg); PrepareCorners(BoxCorners, st::boxRadius, st::boxBg); PrepareCorners(DateCorners, st::dateRadius, st::msgDateImgBg); PrepareCorners(OverviewVideoCorners, st::overviewVideoStatusRadius, st::msgDateImgBg); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_downloads.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_downloads.cpp index 7a4d8fb00c..037b3a4c91 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_downloads.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_downloads.cpp @@ -23,7 +23,7 @@ namespace { class Action final : public Menu::ItemBase { public: Action( - not_null parent, + not_null parent, const DownloadsEntry &entry, Fn callback); @@ -57,7 +57,7 @@ private: }; Action::Action( - not_null parent, + not_null parent, const DownloadsEntry &entry, Fn callback) : ItemBase(parent, st::defaultMenu) @@ -69,8 +69,8 @@ Action::Action( + st::ttlItemTimerFont->height + st::ttlItemPadding.bottom()) { setAcceptBoth(true); - initResizeHook(parent->sizeValue()); - setClickedCallback([=] { + fitToMenuWidth(); + setActionTriggered([=] { if (isEnabled()) { callback(DownloadsAction::Open); } @@ -243,7 +243,7 @@ FnMut)> FillAttachBotDownloadsSubmenu( i->action->refresh(entry); } else { auto action = base::make_unique_q( - menu, + menu->menu(), entry, [=](DownloadsAction type) { callback(id, type); }); state->rows.push_back({ diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index c70556184d..cf76d7411e 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -544,6 +544,14 @@ historyTranscribeOutLockSelected: icon {{ "chat/mini_lock", msgFileOutBgSelected historyTranscribeLockPos: point(17px, 9px); historyTranscribeLockOverlayPos: point(19px, 11px); historyTranscribeLockOverlaySize: size(5px, 10px); +historyTranscribeRadialAnimation: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { + linearPeriod: 700; + sinePeriod: 2400; + sineDuration: 700; + sineShift: 1100; + arcMin: 0.0625; + arcMax: 0.35; +} historyVideoMessageMute: icon {{ "volume_mute", historyFileThumbIconFg }}; historyVideoMessageMuteSelected: icon {{ "volume_mute", historyFileThumbIconFgSelected }}; @@ -574,6 +582,12 @@ historyAdminLogCancelSearch: CrossButton { } historyAdminLogSearchTop: 11px; historyAdminLogSearchSlideDuration: 150; +historyAdminLogTopBarLeft: 17px; +historyAdminLogTopBarUserpicSkip: 35px; +historyAdminLogWhatIsThis: IconButton(historyAttach) { + icon: icon {{ "menu/faq", windowActiveTextFg }}; + iconOver: icon {{ "menu/faq", windowActiveTextFg }}; +} historyFastShareSize: 31px; historyFastShareLeft: 13px; @@ -583,10 +597,11 @@ historyGoToOriginalIcon: icon {{ "filled_go_to_message", msgServiceFg, point(0px historyFastCommentsIcon: icon {{ "fast_comments", msgServiceFg }}; historyFastCloseSize: 30px; historyFastCloseIcon: icon {{ "box_button_close", msgServiceFg }}; -historyFastMoreIcon: icon {{ "title_menu_dots", msgServiceFg, point(0px, -2px) }}; +historyFastMoreIcon: icon {{ "title_menu_dots", msgServiceFg, point(0px, -2px) }}; historyFastTranscribeIcon: icon {{ "chat/voice_to_text", msgServiceFg }}; historyFastTranscribeLock: icon {{ "chat/mini_lock", msgServiceFg }}; historyFastTranscribeLockPos: point(18px, 13px); +historyFastSummaryLockPos: point(13px, 13px); historyFastTranscribeLockOverlayPos: point(21px, 13px); historyFastTranscribeLockOverlaySize: size(6px, 10px); @@ -971,6 +986,32 @@ historyTranslateSettings: IconButton(defaultIconButton) { } } historyTranslateMenuPosition: point(-6px, 30px); +historyTranslateCocoonLabel: FlatLabel(historyHasCustomEmoji) { + style: TextStyle(defaultTextStyle) { + font: font(12px); + } + margin: margins(4px, 8px, 4px, 8px); +} + +cocoonLogoTop: 32px; +cocoonLogoSize: 96px; +cocoonTitleFont: font(36px semibold); +cocoonTitleTop: 12px; +cocoonSubtitle: FlatLabel(defaultFlatLabel) { + minWidth: 120px; + align: align(top); +} +cocoonSubtitleTop: 8px; +cocoonSubtitleBottom: 16px; +cocoonJoinSeparatorPadding: margins(24px, 4px, 24px, 12px); +cocoonJoinLabel: FlatLabel(defaultFlatLabel) { + style: TextStyle(defaultTextStyle) { + font: font(12px); + } + textFg: windowSubTextFg; + minWidth: 120px; + align: align(top); +} historySponsoredAboutMenuLabelPosition: point(54px, 4px); @@ -1459,3 +1500,35 @@ messageGiftIconSkip: 30px; newChatIcon: icon{{ "chat/new_topic", windowFgActive }}; newChatIconPadding: margins(2px, 2px, 2px, 2px); + +stakeBox: Box(defaultBox) { + buttonPadding: margins(24px, 12px, 24px, 24px); + buttonHeight: 42px; + buttonWide: true; + button: RoundButton(defaultActiveButton) { + height: 42px; + textTop: 12px; + style: semiboldTextStyle; + } + shadowIgnoreTopSkip: true; +} +stakeEmojiSize: 20px; +stakeEmojiTop: 8px; +stakeEmojiIcon: IconEmoji { + icon: icon{{ "chat/mini_roll-18x18", activeButtonFg }}; + padding: margins(1px, 2px, 1px, 0px); +} +stakeIconEmojiSize: 12px; +stakeIconEmojiTop: 3px; +stakePresetButton: RoundButton(defaultLightButton) { + textBg: lightButtonBgOver; +} +stakePresetButtonSkip: point(8px, 8px); +stakeChangeBadge: RoundButton(defaultTableSmallButton) { + textFg: mediaviewTextLinkFg; + textFgOver: mediaviewTextLinkFg; +} +stateChangeBadgeMargin: margins(0px, 1px, 0px, 0px); + +historySummaryHeaderIconSize: 30px; +historySummaryHeaderIconSizeInner: 5px; diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 6a680cb864..5eb00a83e3 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -192,6 +192,7 @@ struct ChatPaintContext { const BubblePattern *bubblesPattern = nullptr; ReactionPaintInfo *reactionInfo = nullptr; QRect viewport; + QRect area; QRect clip; TextSelection selection; ChatPaintHighlight highlight; @@ -202,6 +203,7 @@ struct ChatPaintContext { void translate(int x, int y) { viewport.translate(x, y); + area.translate(x, y); clip.translate(x, y); highlightInterpolateTo.translate(x, y); } @@ -266,6 +268,7 @@ struct ChatPaintContextArgs { QPoint visibleAreaPositionGlobal; int visibleAreaTop = 0; int visibleAreaWidth = 0; + int visibleAreaHeight = 0; }; [[nodiscard]] int HistoryServiceMsgRadius(); diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.cpp b/Telegram/SourceFiles/ui/chat/chat_theme.cpp index 0d1424d4a1..a06261eacb 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_theme.cpp @@ -567,19 +567,20 @@ void ChatTheme::finishCreateOnMain() { ChatPaintContext ChatTheme::preparePaintContext( not_null st, QRect viewport, + QRect area, QRect clip, bool paused) { - const auto area = viewport.size(); + const auto size = viewport.size(); const auto now = crl::now(); if (!_bubblesBackgroundPrepared.isNull() - && _bubblesBackground.area != area) { + && _bubblesBackground.area != size) { if (!_cacheBubblesTimer) { _cacheBubblesTimer.emplace([=] { cacheBubbles(); }); } - if (_cacheBubblesArea != area + if (_cacheBubblesArea != size || (!_cacheBubblesTimer->isActive() && !_bubblesCachingRequest)) { - _cacheBubblesArea = area; + _cacheBubblesArea = size; _lastBubblesAreaChangeTime = now; _cacheBubblesTimer->callOnce(kCacheBackgroundFastTimeout); } @@ -588,6 +589,7 @@ ChatPaintContext ChatTheme::preparePaintContext( .st = st, .bubblesPattern = _bubblesBackgroundPattern.get(), .viewport = viewport, + .area = area, .clip = clip, .now = now, .paused = paused, diff --git a/Telegram/SourceFiles/ui/chat/chat_theme.h b/Telegram/SourceFiles/ui/chat/chat_theme.h index 5872613edf..05e52280a9 100644 --- a/Telegram/SourceFiles/ui/chat/chat_theme.h +++ b/Telegram/SourceFiles/ui/chat/chat_theme.h @@ -178,6 +178,7 @@ public: [[nodiscard]] ChatPaintContext preparePaintContext( not_null st, QRect viewport, + QRect area, QRect clip, bool paused); [[nodiscard]] const BackgroundState &backgroundState(QSize area); diff --git a/Telegram/SourceFiles/ui/controls/delete_message_context_action.cpp b/Telegram/SourceFiles/ui/controls/delete_message_context_action.cpp index 340a7d9830..9cecf76e10 100644 --- a/Telegram/SourceFiles/ui/controls/delete_message_context_action.cpp +++ b/Telegram/SourceFiles/ui/controls/delete_message_context_action.cpp @@ -23,7 +23,7 @@ namespace { class ActionWithTimer final : public Menu::ItemBase { public: ActionWithTimer( - not_null parent, + not_null parent, const style::Menu &st, TimeId destroyAt, Fn callback, @@ -66,7 +66,7 @@ TextParseOptions MenuTextOptions = { }; ActionWithTimer::ActionWithTimer( - not_null parent, + not_null parent, const style::Menu &st, TimeId destroyAt, Fn callback, @@ -83,8 +83,8 @@ ActionWithTimer::ActionWithTimer( + st::ttlItemTimerFont->height + st::ttlItemPadding.bottom()) { setAcceptBoth(true); - initResizeHook(parent->sizeValue()); - setClickedCallback(std::move(callback)); + fitToMenuWidth(); + setActionTriggered(std::move(callback)); paintRequest( ) | rpl::on_next([=] { diff --git a/Telegram/SourceFiles/ui/controls/emoji_button_factory.cpp b/Telegram/SourceFiles/ui/controls/emoji_button_factory.cpp index 7a8f59e932..ff58a750fc 100644 --- a/Telegram/SourceFiles/ui/controls/emoji_button_factory.cpp +++ b/Telegram/SourceFiles/ui/controls/emoji_button_factory.cpp @@ -28,7 +28,8 @@ namespace Ui { not_null box, not_null controller, not_null emojiPanel, - QPoint shift) { + QPoint shift, + bool fadeOnFocusChange) { const auto emojiToggle = Ui::CreateChild( field->parentWidget(), st::defaultComposeFiles.emoji); @@ -47,20 +48,22 @@ namespace Ui { } fade->paint(p); }, fadeTarget->lifetime()); - rpl::single(false) | rpl::then( - field->focusedChanges() - ) | rpl::on_next([=](bool shown) { - crl::on_main(emojiToggle, [=] { - if (!emojiToggle->isVisible()) { - return; - } - if (shown) { - fade->fadeIn(st::universalDuration); - } else { - fade->fadeOut(st::universalDuration); - } - }); - }, emojiToggle->lifetime()); + if (fadeOnFocusChange) { + rpl::single(false) | rpl::then( + field->focusedChanges() + ) | rpl::on_next([=](bool shown) { + crl::on_main(emojiToggle, [=] { + if (!emojiToggle->isVisible()) { + return; + } + if (shown) { + fade->fadeIn(st::universalDuration); + } else { + fade->fadeOut(st::universalDuration); + } + }); + }, emojiToggle->lifetime()); + } fade->fadeOut(1); fade->finish(); } diff --git a/Telegram/SourceFiles/ui/controls/emoji_button_factory.h b/Telegram/SourceFiles/ui/controls/emoji_button_factory.h index 3fa3dc728c..18eb6347f5 100644 --- a/Telegram/SourceFiles/ui/controls/emoji_button_factory.h +++ b/Telegram/SourceFiles/ui/controls/emoji_button_factory.h @@ -26,6 +26,7 @@ class InputField; not_null box, not_null controller, not_null emojiPanel, - QPoint shift); + QPoint shift, + bool fadeOnFocusChange = true); } // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/feature_list.cpp b/Telegram/SourceFiles/ui/controls/feature_list.cpp index 5ab504bab9..dcb31ce8df 100644 --- a/Telegram/SourceFiles/ui/controls/feature_list.cpp +++ b/Telegram/SourceFiles/ui/controls/feature_list.cpp @@ -42,6 +42,7 @@ object_ptr MakeFeatureListEntry( icon->show(); title->show(); about->show(); + about->setLinksTrusted(); widget->widthValue( ) | rpl::on_next([=](int width) { const auto left = st::infoStarsFeatureLabelLeft; diff --git a/Telegram/SourceFiles/ui/controls/stars_rating.cpp b/Telegram/SourceFiles/ui/controls/stars_rating.cpp index 2ed30adc7c..fb3c20353b 100644 --- a/Telegram/SourceFiles/ui/controls/stars_rating.cpp +++ b/Telegram/SourceFiles/ui/controls/stars_rating.cpp @@ -326,7 +326,7 @@ void AboutRatingBox( const QString &text, const style::RoundButton &st) { return helper.paletteDependent( - Text::CustomEmojiTextBadge(text, st)); + Text::CustomEmojiTextBadge(text.toUpper(), st)); }; const auto makeActive = [&](const QString &text) { return makeBadge(text, st::customEmojiTextBadge); diff --git a/Telegram/SourceFiles/ui/controls/userpic_button.cpp b/Telegram/SourceFiles/ui/controls/userpic_button.cpp index 0f51ea98bb..f3b64f94e7 100644 --- a/Telegram/SourceFiles/ui/controls/userpic_button.cpp +++ b/Telegram/SourceFiles/ui/controls/userpic_button.cpp @@ -153,6 +153,15 @@ void SetupSubButtonBackground( }, background->lifetime()); } +[[nodiscard]] QBrush CreateDefaultGradientBrush(int size) { + auto gradient = QLinearGradient(0, 0, 0, size); + gradient.setStops({ + { 0.0, st::historyPeer4UserpicBg->c }, + { 1.0, st::historyPeer4UserpicBg2->c }, + }); + return QBrush(std::move(gradient)); +} + } // namespace UserpicButton::UserpicButton( @@ -446,7 +455,7 @@ void UserpicButton::choosePhotoLocally() { auto UserpicButton::makeResetToOriginalAction() -> base::unique_qptr { auto item = base::make_unique_q( - _menu.get(), + _menu->menu(), _menu->st().menu, Menu::CreateAction( _menu.get(), @@ -583,9 +592,9 @@ void UserpicButton::paintEvent(QPaintEvent *e) { paintUserpicFrame(p, photoPosition); } - const auto fillTranslatedShape = [&](const style::color &color) { + const auto fillTranslatedShape = [&](QBrush brush) { p.translate(photoLeft, photoTop); - fillShape(p, color); + fillShape(p, std::move(brush)); p.translate(-photoLeft, -photoTop); }; @@ -593,16 +602,10 @@ void UserpicButton::paintEvent(QPaintEvent *e) { auto over = isOver() || isDown(); if (over) { fillTranslatedShape(_userpicHasImage - ? st::msgDateImgBg - : _st.changeButton.textBgOver); + ? st::msgDateImgBg->b + : st::shadowFg->b); } - paintRipple( - p, - photoLeft, - photoTop, - (_userpicHasImage - ? &st::shadowFg->c - : &_st.changeButton.ripple.color->c)); + paintRipple(p, photoLeft, photoTop, &st::shadowFg->c); if (over || !_userpicHasImage) { auto iconLeft = (_st.changeIconPosition.x() < 0) ? (_st.photoSize - _st.changeIcon.width()) / 2 @@ -1061,7 +1064,7 @@ void UserpicButton::showCustom(QImage &&image) { : Images::Circle(std::move(small))); } else { _userpic = CreateSquarePixmap(_st.photoSize, [&](Painter &p) { - fillShape(p, _st.changeButton.textBg); + fillShape(p, CreateDefaultGradientBrush(_st.photoSize)); }); } _userpic.setDevicePixelRatio(style::DevicePixelRatio()); @@ -1102,10 +1105,10 @@ rpl::producer<> UserpicButton::resetPersonalRequests() const { return _resetPersonalRequests.events(); } -void UserpicButton::fillShape(QPainter &p, const style::color &color) const { +void UserpicButton::fillShape(QPainter &p, QBrush brush) const { PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); - p.setBrush(color); + p.setBrush(brush); const auto size = _st.photoSize; if (useForumShape()) { const auto radius = size * Ui::ForumUserpicRadiusMultiplier(); @@ -1172,7 +1175,7 @@ void UserpicButton::prepareUserpicPixmap() { } } } else { - fillShape(p, _st.changeButton.textBg); + fillShape(p, CreateDefaultGradientBrush(_st.photoSize)); } }); _userpicUniqueKey = _userpicHasImage diff --git a/Telegram/SourceFiles/ui/controls/userpic_button.h b/Telegram/SourceFiles/ui/controls/userpic_button.h index 6703a3977f..b6776f7240 100644 --- a/Telegram/SourceFiles/ui/controls/userpic_button.h +++ b/Telegram/SourceFiles/ui/controls/userpic_button.h @@ -132,7 +132,7 @@ private: void processNewPeerPhoto(); void startNewPhotoShowing(); void prepareUserpicPixmap(); - void fillShape(QPainter &p, const style::color &color) const; + void fillShape(QPainter &p, QBrush brush) const; [[nodiscard]] QPoint countPhotoPosition() const; void startChangeOverlayAnimation(); void updateCursorInChangeOverlay(QPoint localPos); diff --git a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp index 83f52f269f..604b0e0b49 100644 --- a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp +++ b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp @@ -207,14 +207,13 @@ Action::Action( , _height(st::defaultWhoRead.itemPadding.top() + _st.itemStyle.font->height + st::defaultWhoRead.itemPadding.bottom()) { - const auto parent = parentMenu->menu(); const auto delay = anim::Disabled() ? 0 : parentMenu->st().duration; const auto checkAppeared = [=, now = crl::now()](bool force = false) { _appeared = force || ((crl::now() - now) >= delay); }; setAcceptBoth(true); - initResizeHook(parent->sizeValue()); + fitToMenuWidth(); std::move( content @@ -516,10 +515,8 @@ WhenAction::WhenAction( , _height(st::whenReadPadding.top() + st::whenReadStyle.font->height + st::whenReadPadding.bottom()) { - const auto parent = parentMenu->menu(); - setAcceptBoth(true); - initResizeHook(parent->sizeValue()); + fitToMenuWidth(); std::move( content @@ -724,7 +721,7 @@ int WhenAction::contentHeight() const { } // namespace WhoReactedEntryAction::WhoReactedEntryAction( - not_null parent, + not_null parent, CustomEmojiFactory customEmojiFactory, const style::Menu &st, Data &&data) @@ -735,7 +732,7 @@ WhoReactedEntryAction::WhoReactedEntryAction( , _height(st::defaultWhoRead.photoSkip * 2 + st::defaultWhoRead.photoSize) { setAcceptBoth(true); - initResizeHook(parent->sizeValue()); + fitToMenuWidth(); setData(std::move(data)); paintRequest( @@ -759,7 +756,7 @@ int WhoReactedEntryAction::contentHeight() const { } void WhoReactedEntryAction::setData(Data &&data) { - setClickedCallback(std::move(data.callback)); + setActionTriggered(std::move(data.callback)); _userpic = std::move(data.userpic); _text.setMarkedText(_st.itemStyle, { data.text }, MenuTextOptions); if (data.date.isEmpty()) { diff --git a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h index 919bb44893..2e0d1b14c1 100644 --- a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h +++ b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h @@ -93,7 +93,7 @@ public: using Data = WhoReactedEntryData; WhoReactedEntryAction( - not_null parent, + not_null parent, Text::CustomEmojiFactory factory, const style::Menu &st, Data &&data); diff --git a/Telegram/SourceFiles/ui/effects/ministar_particles.cpp b/Telegram/SourceFiles/ui/effects/ministar_particles.cpp index 1922044c0f..59fd7c49c6 100644 --- a/Telegram/SourceFiles/ui/effects/ministar_particles.cpp +++ b/Telegram/SourceFiles/ui/effects/ministar_particles.cpp @@ -32,7 +32,31 @@ void StarParticles::setVisible(float visible) { } void StarParticles::setColor(const QColor &color) { - _color = color; + setColors({ color }); +} + +void StarParticles::setColors(std::vector colors) { + Expects(!colors.empty()); + + const auto was = int(_colors.size()); + _colors = std::move(colors); + const auto now = int(_colors.size()); + + _paintedCaches.clear(); + + if (now != was) { + if (now > 1) { + auto buffered = base::BufferedRandom(_particles.size()); + for (auto &p : _particles) { + p.colorIndex = base::RandomIndex(now, buffered); + } + } else { + for (auto &p : _particles) { + p.colorIndex = 0; + } + } + + } } QImage StarParticles::generateStarCache(int size, QColor color) { @@ -76,12 +100,14 @@ void StarParticles::generate() { _particles.clear(); _particles.reserve(_count); - constexpr auto kRandomPerParticle = 12; + constexpr auto kRandomPerParticle = 13; auto random = bytes::vector(_count * kRandomPerParticle); base::RandomFill(random.data(), random.size()); auto idx = 0; const auto next = [&] { return uchar(random[idx++]) / 255.; }; + const auto colors = int(_colors.size()); + for (auto i = 0; i < _count; ++i) { auto p = Particle(); p.x = next(); @@ -106,23 +132,57 @@ void StarParticles::generate() { p.vy = -std::sin(angle) * speed; } + if (colors > 1) { + p.colorIndex = int(base::SafeRound(next() * (colors - 1))); + } + _particles.push_back(p); } } -void StarParticles::paint(QPainter &p, const QRect &rect, crl::time now) { +void StarParticles::paint( + QPainter &p, + const QRect &rect, + crl::time now, + bool paused) { if (_lastTime == 0) { _lastTime = now; return; } - const auto colorKey = _color.rgba(); - const auto i = _starCache.find(colorKey); - const auto &cache = (i != end(_starCache)) - ? i->second - : _starCache.emplace( - colorKey, - generateStarCache(_starSize, _color)).first->second; + if (paused) { + if (!_pausedAt) { + _pausedAt = now; + } + now = _pausedAt - _pauseOffset; + } else { + if (_pausedAt) { + _pauseOffset += now - _pausedAt; + _pausedAt = 0; + } + now = now - _pauseOffset; + } + + const auto validate = [&](int colorIndex) { + Expects(colorIndex >= 0 && colorIndex < _colors.size()); + + if (_paintedCaches.empty()) { + _paintedCaches.resize(_colors.size()); + } else if (const auto result = _paintedCaches[colorIndex] + ; !result.isNull()) { + return result; + } + const auto &color = _colors[colorIndex]; + const auto colorKey = color.rgba(); + const auto i = _starCache.find(colorKey); + const auto &cache = (i != end(_starCache)) + ? i->second + : _starCache.emplace( + colorKey, + generateStarCache(_starSize, color)).first->second; + _paintedCaches[colorIndex] = cache; + return cache; + }; const auto dt = (now - _lastTime) / 1000.; _lastTime = now; @@ -147,6 +207,7 @@ void StarParticles::paint(QPainter &p, const QRect &rect, crl::time now) { const auto x = rect.x() + particle.x * rect.width(); const auto y = rect.y() + particle.y * rect.height(); + const auto cache = validate(particle.colorIndex); const auto size = cache.width() / cache.devicePixelRatio() * particle.size; diff --git a/Telegram/SourceFiles/ui/effects/ministar_particles.h b/Telegram/SourceFiles/ui/effects/ministar_particles.h index 5b667d3534..3394693b34 100644 --- a/Telegram/SourceFiles/ui/effects/ministar_particles.h +++ b/Telegram/SourceFiles/ui/effects/ministar_particles.h @@ -24,10 +24,12 @@ public: void setSpeed(float speed); void setVisible(float visible); void setColor(const QColor &color); - void paint(QPainter &p, const QRect &rect, crl::time now); + void setColors(std::vector colors); + void paint(QPainter &p, const QRect &rect, crl::time now, bool paused); private: struct Particle { + int colorIndex = 0; float x = 0.; float y = 0.; float vx = 0.; @@ -47,10 +49,14 @@ private: int _starSize = 0; float _speed = 1.f; float _visible = 1.f; - QColor _color = QColor(255, 200, 70); + std::vector _colors = { QColor(255, 200, 70) }; + std::vector _paintedCaches; std::vector _particles; base::flat_map _starCache; crl::time _lastTime = 0; + + crl::time _pausedAt = 0; + crl::time _pauseOffset = 0; }; } // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/premium_bubble.cpp b/Telegram/SourceFiles/ui/effects/premium_bubble.cpp index 1ecfd415a1..4e5a9fe540 100644 --- a/Telegram/SourceFiles/ui/effects/premium_bubble.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_bubble.cpp @@ -411,14 +411,14 @@ void BubbleWidget::setupParticles(not_null parent) { p.setClipPath(bubblePath); _particles->setColor(st::premiumButtonFg->c); - _particles->paint(p, _particlesWidget->rect(), crl::now()); + _particles->paint(p, _particlesWidget->rect(), crl::now(), false); p.setClipping(false); p.setClipPath(fullRect.subtracted(bubblePath)); _particles->setColor(_brushOverride ? st::groupCallMemberInactiveIcon->c : st::creditsBg3->c); - _particles->paint(p, _particlesWidget->rect(), crl::now()); + _particles->paint(p, _particlesWidget->rect(), crl::now(), false); }, _particlesWidget->lifetime()); geometryValue() | rpl::on_next([=](QRect geometry) { diff --git a/Telegram/SourceFiles/ui/text/text_lottie_custom_emoji.cpp b/Telegram/SourceFiles/ui/text/text_lottie_custom_emoji.cpp index e78a264c76..7791bfc2af 100644 --- a/Telegram/SourceFiles/ui/text/text_lottie_custom_emoji.cpp +++ b/Telegram/SourceFiles/ui/text/text_lottie_custom_emoji.cpp @@ -17,8 +17,8 @@ LottieCustomEmoji::LottieCustomEmoji(Lottie::IconDescriptor &&descriptor) } LottieCustomEmoji::LottieCustomEmoji( - Lottie::IconDescriptor &&descriptor, - Fn repaint) + Lottie::IconDescriptor &&descriptor, + Fn repaint) : _entityData(!descriptor.name.isEmpty() ? descriptor.name : !descriptor.path.isEmpty() diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp index 00d8d8e306..f0945dc907 100644 --- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp @@ -168,11 +168,11 @@ void ShowFiltersListMenu( ? &st::mediaPlayerMenuCheck : nullptr; const auto action = Ui::Menu::CreateAction( - state->menu.get(), + state->menu->menu(), text, callback); auto item = base::make_unique_q( - state->menu.get(), + state->menu->menu(), state->menu->st().menu, action, icon, diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index 962eec58e9..e5ec0ff7a4 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -75,7 +75,7 @@ using Core::WindowPosition; base::options::toggle OptionNewWindowsSizeAsFirst({ .id = kOptionNewWindowsSizeAsFirst, - .name = "Adjust size of new chat windows.", + .name = "Adjust size of new chat windows", .description = "Open new windows with a size of the main window.", }); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 6bbc51ca35..ff4e280939 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1442,12 +1442,30 @@ void Filler::addViewAsMessages() { controller->showPeerHistory(peer->id); }; auto to_instant = rpl::map_to(anim::type::instant); + auto make = [=](not_null popupMenu) { + auto owned = base::make_unique_q( + popupMenu->menu(), + popupMenu->menu()->st(), + Ui::Menu::CreateAction( + popupMenu->menu(), + tr::lng_forum_view_as_messages(tr::now), + [=, weak = base::make_weak(popupMenu)] { + if (filterOutChatPreview()) { + } else { + open(); + if (const auto strong = weak.get()) { + strong->hideMenu(false); + } + } + }), + &st::menuIconAsMessages, + &st::menuIconAsMessages); + owned->setPreventClose(true); + return owned; + }; _addAction({ - .text = tr::lng_forum_view_as_messages(tr::now), - .handler = open, - .icon = &st::menuIconAsMessages, - .triggerFilter = filterOutChatPreview, - .hideRequests = parentHideRequests->events() | to_instant + .make = std::move(make), + .hideRequests = parentHideRequests->events() | to_instant, }); } @@ -1726,7 +1744,8 @@ void Filler::addToggleFee() { removeFee); }, feeRemoved ? &st::menuIconEarn : &st::menuIconCancelFee); _addAction({ .isSeparator = true }); - _addAction({ .make = [=](not_null actionParent) { + _addAction({ .make = [=](not_null menuParent) { + const auto actionParent = menuParent->menu(); auto helper = Ui::Text::CustomEmojiHelper(); const auto text = feeRemoved ? tr::lng_context_fee_free( @@ -1745,7 +1764,7 @@ void Filler::addToggleFee() { user->owner().commonStarsPerMessage(parent) )), tr::marked); - const auto action = new QAction(actionParent); + const auto action = Ui::CreateChild(actionParent); action->setDisabled(true); auto result = base::make_unique_q( actionParent, diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 2be8161db0..d39083e81c 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_subsection_tabs.h" #include "media/player/media_player_instance.h" #include "media/view/media_view_open_common.h" +#include "data/components/gift_auctions.h" #include "data/components/recent_peers.h" #include "data/stickers/data_custom_emoji.h" #include "data/data_document_resolver.h" @@ -3565,6 +3566,11 @@ HistoryView::PaintContext SessionController::preparePaintContext( Ui::ChatPaintContextArgs &&args) { const auto visibleAreaTopLocal = content()->mapFromGlobal( args.visibleAreaPositionGlobal).y(); + const auto area = QRect( + 0, + args.visibleAreaTop, + args.visibleAreaWidth, + args.visibleAreaHeight); const auto viewport = QRect( 0, args.visibleAreaTop - visibleAreaTopLocal, @@ -3573,6 +3579,7 @@ HistoryView::PaintContext SessionController::preparePaintContext( return args.theme->preparePaintContext( _chatStyle.get(), viewport, + area, args.clip, isGifPausedAtLeastFor(GifPauseReason::Any)); } @@ -3654,10 +3661,29 @@ void SessionController::dropSubsectionTabs() { void SessionController::showStarGiftAuction(const QString &slug) { _starGiftAuctionLifetime.destroy(); + + const auto requesting = _starGiftAuctionLifetime.make_state< + base::has_weak_ptr + >(); + const auto guard = base::make_weak(requesting); + const auto weak = base::make_weak(this); + session().giftAuctions().resolveSlug(slug, [=](uint64 giftId) { + if (!guard || !weak) { + return; + } + _starGiftAuctionLifetime.destroy(); + if (giftId) { + showStarGiftAuction(giftId); + } + }); +} + +void SessionController::showStarGiftAuction(uint64 giftId) { + _starGiftAuctionLifetime.destroy(); _starGiftAuctionLifetime = Ui::ShowStarGiftAuction( this, nullptr, - slug, + giftId, [] {}, [=] { _starGiftAuctionLifetime.destroy(); }); } diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 7aefbcab1b..2cd57a54cf 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -195,6 +195,7 @@ struct SectionShow { bool forceTopicsList = false; bool reapplyLocalDraft = false; bool dropSameFromStack = false; + bool allowDuplicateInStack = false; Origin origin; }; @@ -697,6 +698,7 @@ public: void dropSubsectionTabs(); void showStarGiftAuction(const QString &slug); + void showStarGiftAuction(uint64 giftId); [[nodiscard]] rpl::lifetime &lifetime() { return _lifetime; diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 68f7461b3a..7071973730 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1048,7 +1048,7 @@ stage('libjxl', """ """) + """ win: cmake . ^ - -A %WIN32X64% ^ + -A %WIN32X64% -T v143 ^ -DCMAKE_INSTALL_PREFIX=%LIBS_DIR%/local ^ -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" ^ -DCMAKE_C_FLAGS="/DJXL_STATIC_DEFINE /DJXL_THREADS_STATIC_DEFINE /DJXL_CMS_STATIC_DEFINE" ^ @@ -1370,7 +1370,7 @@ stage('openal-soft', """ win: git checkout 291c0fdbbd cmake -B build . ^ - -A %WIN32X64% ^ + -A %WIN32X64% -T v143 ^ -D LIBTYPE:STRING=STATIC ^ -D FORCE_STATIC_VCRT=ON ^ -D ALSOFT_UTILS=OFF ^ diff --git a/Telegram/build/version b/Telegram/build/version index 9bbed2240f..ffbaab1961 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 6003010 -AppVersionStrMajor 6.3 -AppVersionStrSmall 6.3.10 -AppVersionStr 6.3.10 +AppVersion 6004002 +AppVersionStrMajor 6.4 +AppVersionStrSmall 6.4.2 +AppVersionStr 6.4.2 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 6.3.10.beta +AppVersionOriginal 6.4.2 diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 4af221dd1c..55bf05c625 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -150,6 +150,9 @@ PRIVATE editor/scene/scene_item_line.cpp editor/scene/scene_item_line.h + ui/boxes/about_cocoon_box.h + ui/boxes/about_cocoon_box.cpp + history/admin_log/history_admin_log_filter_value.h history/history_view_top_toast.cpp history/history_view_top_toast.h @@ -259,6 +262,9 @@ PRIVATE platform/platform_current_geo_location.h platform/platform_text_recognition.h + profile/profile_back_button.cpp + profile/profile_back_button.h + settings/settings_common.cpp settings/settings_common.h settings/cloud_password/settings_cloud_password_common.cpp diff --git a/Telegram/lib_base b/Telegram/lib_base index 444ab33475..cca87bd2fb 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 444ab33475734485dcc183a5fb86875c134fac8b +Subproject commit cca87bd2fb4f83bb3fc45d8b59ed8f1d9429d78b diff --git a/Telegram/lib_webview b/Telegram/lib_webview index 55ea117597..b4a0af279b 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit 55ea11759711d377216eae6c45dad0bc49b67398 +Subproject commit b4a0af279bb55e1caeec3d5f82edae699c3ab251 diff --git a/changelog.txt b/changelog.txt index 423cf59214..dde97ee047 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,18 @@ +6.4.2 (08.01.25) + +- Fix non-closing popup menu items. +- Fix crash in my profile section resize. +- Fix some text glitches because of render optimizations. + +6.4.1 (05.01.25) + +- Fix context menu double activations. +- Fix text rendering in animating bubbles. + +6.4 (03.01.25) + +- AI Summaries for posts in channels. + 6.3.10 beta (22.12.25) - Fix user info showing from large group messages.