From 60cc23288405b28c9ee76214378b5b8d7b0ed3cd Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 16 Aug 2022 14:28:17 +0300 Subject: [PATCH] Update API scheme on layer 145. --- Telegram/Resources/tl/api.tl | 16 ++- Telegram/SourceFiles/api/api_who_reacted.cpp | 44 ++++--- Telegram/SourceFiles/api/api_who_reacted.h | 6 +- .../boxes/peers/edit_peer_reactions.cpp | 7 +- .../SourceFiles/boxes/premium_preview_box.cpp | 17 ++- .../SourceFiles/boxes/premium_preview_box.h | 11 +- .../boxes/reactions_settings_box.cpp | 21 +-- .../chat_helpers/tabbed_selector.cpp | 5 +- .../data/data_message_reactions.cpp | 122 +++++++++++------- .../SourceFiles/data/data_message_reactions.h | 60 ++++++--- .../history/history_inner_widget.cpp | 28 ++-- Telegram/SourceFiles/history/history_item.cpp | 29 +++-- Telegram/SourceFiles/history/history_item.h | 18 +-- .../history/view/history_view_bottom_info.cpp | 48 ++++--- .../history/view/history_view_bottom_info.h | 28 ++-- .../view/history_view_context_menu.cpp | 14 +- .../history/view/history_view_context_menu.h | 6 +- .../history/view/history_view_element.cpp | 15 +-- .../history/view/history_view_element.h | 14 +- .../history/view/history_view_list_widget.cpp | 28 ++-- .../history/view/history_view_message.cpp | 16 ++- .../history/view/history_view_message.h | 8 +- .../view/history_view_react_animation.cpp | 3 +- .../view/history_view_react_button.cpp | 67 +++++----- .../history/view/history_view_react_button.h | 26 ++-- .../history/view/history_view_reactions.cpp | 65 ++++++---- .../history/view/history_view_reactions.h | 33 ++--- .../view/reactions/message_reactions_list.cpp | 83 ++++++------ .../view/reactions/message_reactions_list.h | 6 +- .../reactions/message_reactions_selector.cpp | 25 ++-- .../reactions/message_reactions_selector.h | 10 +- .../SourceFiles/mtproto/mtproto_config.cpp | 42 +++++- Telegram/SourceFiles/mtproto/mtproto_config.h | 4 + .../SourceFiles/settings/settings_chat.cpp | 18 +-- .../window/notifications_manager.cpp | 86 ++++++++---- .../window/notifications_manager.h | 5 +- .../window/notifications_manager_default.cpp | 12 +- .../window/notifications_manager_default.h | 6 +- .../SourceFiles/window/section_widget.cpp | 15 ++- Telegram/SourceFiles/window/section_widget.h | 6 +- 40 files changed, 646 insertions(+), 427 deletions(-) diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index 13b655aab..12dea6ad6 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -420,7 +420,7 @@ upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true this_port_only:flags.5?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption; -config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config; +config#232566ac flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int reactions_default:flags.15?Reaction = Config; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; @@ -1330,7 +1330,7 @@ messages.peerSettings#6880b94d settings:PeerSettings chats:Vector users:Ve auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut; -reactionCount#6fb250d1 flags:# chosen:flags.0?true reaction:string count:int = ReactionCount; +reactionCount#2454adf0 flags:# chosen:flags.0?true reaction:Reaction count:int = ReactionCount; messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector recent_reactions:flags.1?Vector = MessageReactions; @@ -1344,7 +1344,7 @@ messages.availableReactions#768e3aad hash:int reactions:Vector = account.EmojiStatuses; +reactionEmpty#79f5d419 = Reaction; +reactionEmoji#1b2286b8 emoticon:string = Reaction; +reactionCustomEmoji#8935fc73 document_id:long = Reaction; + emailVerifyPurposeLoginSetup#4345be73 phone_number:string phone_code_hash:string = EmailVerifyPurpose; emailVerifyPurposeLoginChange#527d22eb = EmailVerifyPurpose; emailVerifyPurposePassport#bbf51685 = EmailVerifyPurpose; @@ -1708,12 +1712,12 @@ messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPe messages.hideAllChatJoinRequests#e085f4ea flags:# approved:flags.0?true peer:InputPeer link:flags.1?string = Updates; messages.toggleNoForwards#b11eafa2 peer:InputPeer enabled:Bool = Updates; messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool; -messages.sendReaction#25690ce4 flags:# big:flags.1?true peer:InputPeer msg_id:int reaction:flags.0?string = Updates; +messages.sendReaction#5afe99b5 flags:# big:flags.1?true peer:InputPeer msg_id:int reaction:flags.0?Reaction = Updates; messages.getMessagesReactions#8bba90e6 peer:InputPeer id:Vector = Updates; -messages.getMessageReactionsList#e0ee6b77 flags:# peer:InputPeer id:int reaction:flags.0?string offset:flags.1?string limit:int = messages.MessageReactionsList; +messages.getMessageReactionsList#461b3f48 flags:# peer:InputPeer id:int reaction:flags.0?Reaction offset:flags.1?string limit:int = messages.MessageReactionsList; messages.setChatAvailableReactions#14050ea6 peer:InputPeer available_reactions:Vector = Updates; messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions; -messages.setDefaultReaction#d960c4d4 reaction:string = Bool; +messages.setDefaultReaction#4f47a016 reaction:Reaction = Bool; messages.translateText#24ce6dee flags:# peer:flags.0?InputPeer msg_id:flags.0?int text:flags.1?string from_lang:flags.2?string to_lang:string = messages.TranslatedText; messages.getUnreadReactions#e85bae1a peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; messages.readReactions#82e251d7 peer:InputPeer = messages.AffectedHistory; diff --git a/Telegram/SourceFiles/api/api_who_reacted.cpp b/Telegram/SourceFiles/api/api_who_reacted.cpp index 1af33f570..b606c0d3e 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.cpp +++ b/Telegram/SourceFiles/api/api_who_reacted.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_session.h" #include "data/data_media_types.h" +#include "data/data_message_reactions.h" #include "main/main_app_config.h" #include "main/main_session.h" #include "main/main_account.h" @@ -31,6 +32,8 @@ namespace { constexpr auto kContextReactionsLimit = 50; +using Data::ReactionId; + struct Peers { std::vector list; bool unknown = false; @@ -41,7 +44,7 @@ inline bool operator==(const Peers &a, const Peers &b) noexcept { struct PeerWithReaction { PeerId peer = 0; - QString reaction; + ReactionId reaction; }; inline bool operator==( const PeerWithReaction &a, @@ -84,7 +87,7 @@ struct Context { base::flat_map, CachedRead> cachedRead; base::flat_map< not_null, - base::flat_map> cachedReacted; + base::flat_map> cachedReacted; base::flat_map, rpl::lifetime> subscriptions; [[nodiscard]] CachedRead &cacheRead(not_null item) { @@ -97,7 +100,7 @@ struct Context { [[nodiscard]] CachedReacted &cacheReacted( not_null item, - const QString &reaction) { + const ReactionId &reaction) { auto &map = cachedReacted[item]; const auto i = map.find(reaction); if (i != end(map)) { @@ -249,7 +252,7 @@ struct State { Peers &&peers) { auto result = PeersWithReactions{ .list = peers.list | ranges::views::transform([](PeerId peer) { - return PeerWithReaction{.peer = peer }; + return PeerWithReaction{ .peer = peer }; }) | ranges::to_vector, .unknown = peers.unknown, }; @@ -259,7 +262,7 @@ struct State { [[nodiscard]] rpl::producer WhoReactedIds( not_null item, - const QString &reaction, + const ReactionId &reaction, not_null context) { auto weak = QPointer(context.get()); const auto session = &item->history()->session(); @@ -273,12 +276,12 @@ struct State { using Flag = MTPmessages_GetMessageReactionsList::Flag; entry.requestId = session->api().request( MTPmessages_GetMessageReactionsList( - MTP_flags(reaction.isEmpty() + MTP_flags(reaction.empty() ? Flag(0) : Flag::f_reaction), item->history()->peer->input, MTP_int(item->id), - MTP_string(reaction), + ReactionToMTP(reaction), MTPstring(), // offset MTP_int(kContextReactionsLimit) ) @@ -299,7 +302,8 @@ struct State { vote.match([&](const auto &data) { parsed.list.push_back(PeerWithReaction{ .peer = peerFromMTP(data.vpeer_id()), - .reaction = qs(data.vreaction()), + .reaction = Data::ReactionFromMTP( + data.vreaction()), }); }); } @@ -322,7 +326,7 @@ struct State { not_null context) -> rpl::producer { return rpl::combine( - WhoReactedIds(item, QString(), context), + WhoReactedIds(item, {}, context), WhoReadIds(item, context) ) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) { if (reacted.unknown || read.unknown) { @@ -347,7 +351,7 @@ bool UpdateUserpics( struct ResolvedPeer { PeerData *peer = nullptr; - QString reaction; + ReactionId reaction; }; const auto peers = ranges::views::all( ids @@ -383,7 +387,7 @@ bool UpdateUserpics( } now.push_back(Userpic{ .peer = peer, - .reaction = resolved.reaction, + .reaction = resolved.reaction.emoji(), // #TODO reactions }); auto &userpic = now.back(); userpic.uniqueKey = peer->userpicUniqueKey(userpic.view); @@ -446,7 +450,7 @@ void RegenerateParticipants(not_null state, int small, int large) { rpl::producer WhoReacted( not_null item, - const QString &reaction, + const ReactionId &reaction, not_null context, const style::WhoRead &st, std::shared_ptr whoReadIds) { @@ -455,7 +459,7 @@ rpl::producer WhoReacted( return [=](auto consumer) { auto lifetime = rpl::lifetime(); - const auto resolveWhoRead = reaction.isEmpty() + const auto resolveWhoRead = reaction.empty() && WhoReadExists(item); const auto state = lifetime.make_state(); @@ -463,7 +467,7 @@ rpl::producer WhoReacted( consumer.put_next_copy(state->current); }; - const auto resolveWhoReacted = !reaction.isEmpty() + const auto resolveWhoReacted = !reaction.empty() || item->canViewReactions(); auto idsWithReactions = (resolveWhoRead && resolveWhoReacted) ? WhoReadOrReactedIds(item, context) @@ -475,7 +479,7 @@ rpl::producer WhoReacted( : Ui::WhoReadType::Reacted; if (resolveWhoReacted) { const auto &list = item->reactions(); - state->current.fullReactionsCount = reaction.isEmpty() + state->current.fullReactionsCount = reaction.empty() ? ranges::accumulate( list, 0, @@ -486,11 +490,11 @@ rpl::producer WhoReacted( : 0; // #TODO reactions - state->current.singleReaction = !reaction.isEmpty() + state->current.singleReaction = (!reaction.empty() ? reaction : (list.size() == 1) ? list.front().first - : QString(); + : ReactionId()).emoji(); } std::move( idsWithReactions @@ -510,7 +514,7 @@ rpl::producer WhoReacted( if (whoReadIds) { const auto reacted = peers.list.size() - ranges::count( peers.list, - QString(), + ReactionId(), &PeerWithReaction::reaction); whoReadIds->list = (peers.read.size() > reacted) ? std::move(peers.read) @@ -592,12 +596,12 @@ rpl::producer WhoReacted( not_null context, const style::WhoRead &st, std::shared_ptr whoReadIds) { - return WhoReacted(item, QString(), context, st, std::move(whoReadIds)); + return WhoReacted(item, {}, context, st, std::move(whoReadIds)); } rpl::producer WhoReacted( not_null item, - const QString &reaction, + const Data::ReactionId &reaction, not_null context, const style::WhoRead &st) { return WhoReacted(item, reaction, context, st, nullptr); diff --git a/Telegram/SourceFiles/api/api_who_reacted.h b/Telegram/SourceFiles/api/api_who_reacted.h index 7a2325b88..0a1159690 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.h +++ b/Telegram/SourceFiles/api/api_who_reacted.h @@ -18,6 +18,10 @@ struct WhoReadContent; enum class WhoReadType; } // namespace Ui +namespace Data { +struct ReactionId; +} // namespace Data + namespace Api { [[nodiscard]] bool WhoReadExists(not_null item); @@ -36,7 +40,7 @@ struct WhoReadList { std::shared_ptr whoReadIds = nullptr); [[nodiscard]] rpl::producer WhoReacted( not_null item, - const QString &reaction, + const Data::ReactionId &reaction, not_null context, // Cache results for this lifetime. const style::WhoRead &st); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp index 769874301..75083f3b0 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp @@ -91,9 +91,12 @@ void EditAllowedReactionsBox( tr::lng_manage_peer_reactions_available()); const auto active = [&](const Data::Reaction &entry) { - return selected.contains(entry.emoji); + return selected.contains(entry.id.emoji()); }; const auto add = [&](const Data::Reaction &entry) { + if (entry.id.emoji().isEmpty()) { + return; + } const auto button = Settings::AddButton( container, rpl::single(entry.title), @@ -114,7 +117,7 @@ void EditAllowedReactionsBox( }) | rpl::to_empty, rpl::never<>(), &button->lifetime()); - state->toggles.emplace(entry.emoji, button); + state->toggles.emplace(entry.id.emoji(), button); button->toggleOn(rpl::single( active(entry) ) | rpl::then( diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 493a47bdd..a9fe3978b 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -58,10 +58,12 @@ constexpr auto kStarOpacityOff = 0.1; constexpr auto kStarOpacityOn = 1.; constexpr auto kStarPeriod = 3 * crl::time(1000); +using Data::ReactionId; + struct Descriptor { PremiumPreview section = PremiumPreview::Stickers; DocumentData *requestedSticker = nullptr; - base::flat_map disabled; + base::flat_map disabled; bool fromSettings = false; Fn hiddenCallback; Fn)> shownCallback; @@ -968,7 +970,7 @@ void ReactionPreview::paintEffect(QPainter &p) { [[nodiscard]] not_null ReactionsPreview( not_null parent, not_null controller, - const base::flat_map &disabled, + const base::flat_map &disabled, Fn readyCallback) { struct State { std::vector> entries; @@ -1015,7 +1017,7 @@ void ReactionPreview::paintEffect(QPainter &p) { if (!reaction.centerIcon || !reaction.aroundAnimation) { continue; } - const auto i = disabled.find(reaction.emoji); + const auto i = disabled.find(reaction.id); const auto disable = (i != end(disabled)) ? i->second : ReactionDisableType::None; @@ -1623,7 +1625,14 @@ void ShowStickerPreviewBox( void ShowPremiumPreviewBox( not_null controller, PremiumPreview section, - const base::flat_map &disabled, + Fn)> shown) { + ShowPremiumPreviewBox(controller, section, {}, std::move(shown)); +} + +void ShowPremiumPreviewBox( + not_null controller, + PremiumPreview section, + const base::flat_map &disabled, Fn)> shown) { Show(controller, Descriptor{ .section = section, diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.h b/Telegram/SourceFiles/boxes/premium_preview_box.h index ed057f3f9..ec74b091a 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.h +++ b/Telegram/SourceFiles/boxes/premium_preview_box.h @@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class DocumentData; +namespace Data { +struct ReactionId; +} // namespace Data + namespace Ui { class BoxContent; class GenericBox; @@ -56,7 +60,12 @@ enum class ReactionDisableType { void ShowPremiumPreviewBox( not_null controller, PremiumPreview section, - const base::flat_map &disabled = {}, + Fn)> shown = nullptr); + +void ShowPremiumPreviewBox( + not_null controller, + PremiumPreview section, + const base::flat_map &disabled, Fn)> shown = nullptr); void ShowPremiumPreviewToBuy( diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp index c25703d36..236c8b465 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp @@ -222,9 +222,10 @@ void AddMessage( emojiValue = std::move(emojiValue), iconSize = st::settingsReactionMessageSize ](const QString &emoji) { + const auto id = Data::ReactionId{ emoji }; const auto &reactions = controller->session().data().reactions(); for (const auto &r : reactions.list(Data::Reactions::Type::Active)) { - if (emoji != r.emoji) { + if (r.id != id) { continue; } const auto index = state->icons.flag ? 1 : 0; @@ -392,7 +393,9 @@ void ReactionsSettingsBox( const auto &reactions = controller->session().data().reactions(); const auto state = box->lifetime().make_state(); - state->selectedEmoji = reactions.favorite(); + state->selectedEmoji = v::is(reactions.favorite().data) + ? v::get(reactions.favorite().data) + : QString(); const auto pinnedToTop = box->setPinnedToTopContent( object_ptr(box)); @@ -451,7 +454,7 @@ void ReactionsSettingsBox( rpl::never<>(), &button->lifetime()); - button->setClickedCallback([=, emoji = r.emoji] { + button->setClickedCallback([=, id = r.id] { if (premium && !controller->session().premium()) { ShowPremiumPreviewBox( controller, @@ -459,9 +462,11 @@ void ReactionsSettingsBox( return; } checkButton(button); - state->selectedEmoji = emoji; + state->selectedEmoji = v::is(id.data) + ? v::get(id.data) + : QString(); }); - if (r.emoji == state->selectedEmoji.current()) { + if (r.id == Data::ReactionId{ state->selectedEmoji.current() }) { firstCheckedButton = button; } } @@ -479,9 +484,9 @@ void ReactionsSettingsBox( box->setWidth(st::boxWideWidth); box->addButton(tr::lng_settings_save(), [=] { const auto &data = controller->session().data(); - const auto selectedEmoji = state->selectedEmoji.current(); - if (data.reactions().favorite() != selectedEmoji) { - data.reactions().setFavorite(selectedEmoji); + const auto selected = state->selectedEmoji.current(); + if (data.reactions().favorite() != Data::ReactionId{ selected }) { + data.reactions().setFavorite(Data::ReactionId{ selected }); } box->closeBox(); }); diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index f9e3d6242..6cb9f190d 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -862,10 +862,7 @@ void TabbedSelector::setCurrentPeer(PeerData *peer) { void TabbedSelector::showPromoForPremiumEmoji() { premiumEmojiChosen( ) | rpl::start_with_next([=] { - ShowPremiumPreviewBox( - _controller, - PremiumPreview::AnimatedEmoji, - {}); + ShowPremiumPreviewBox(_controller, PremiumPreview::AnimatedEmoji); }, lifetime()); } diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index a429e8fa8..af549638f 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lottie/lottie_icon.h" #include "storage/localimageloader.h" #include "ui/image/image_location_factory.h" +#include "mtproto/mtproto_config.h" #include "base/timer_rpl.h" #include "apiwrap.h" #include "styles/style_chat.h" @@ -32,8 +33,35 @@ constexpr auto kRefreshFullListEach = 60 * 60 * crl::time(1000); constexpr auto kPollEach = 20 * crl::time(1000); constexpr auto kSizeForDownscale = 64; +[[nodiscard]] QString ReactionIdToLog(const ReactionId &id) { + if (const auto custom = id.custom()) { + return "custom:" + QString::number(custom); + } + return id.emoji(); +} + } // namespace +ReactionId ReactionFromMTP(const MTPReaction &reaction) { + return reaction.match([](MTPDreactionEmpty) { + return ReactionId{ QString() }; + }, [](const MTPDreactionEmoji &data) { + return ReactionId{ qs(data.vemoticon()) }; + }, [](const MTPDreactionCustomEmoji &data) { + return ReactionId{ DocumentId(data.vdocument_id().v) }; + }); +} + +MTPReaction ReactionToMTP(ReactionId id) { + if (const auto custom = id.custom()) { + return MTP_reactionCustomEmoji(MTP_long(custom)); + } + const auto emoji = id.emoji(); + return emoji.isEmpty() + ? MTP_reactionEmpty() + : MTP_reactionEmoji(MTP_string(emoji)); +} + Reactions::Reactions(not_null owner) : _owner(owner) , _repaintTimer([=] { repaintCollected(); }) { @@ -54,16 +82,18 @@ Reactions::Reactions(not_null owner) _repaintItems.remove(item); }, _lifetime); - const auto appConfig = &_owner->session().account().appConfig(); - appConfig->value( - ) | rpl::start_with_next([=] { - const auto favorite = appConfig->get( - u"reactions_default"_q, - QString::fromUtf8("\xf0\x9f\x91\x8d")); - if (_favorite != favorite && !_saveFaveRequestId) { - _favorite = favorite; - _updated.fire({}); - } + rpl::single(rpl::empty) | rpl::then( + _owner->session().mtp().config().updates() + ) | rpl::map([=] { + const auto &config = _owner->session().mtp().configValues(); + return config.reactionDefaultCustom + ? ReactionId{ DocumentId(config.reactionDefaultCustom) } + : ReactionId{ config.reactionDefaultEmoji }; + }) | rpl::filter([=](const ReactionId &id) { + return (_favorite != id) && !_saveFaveRequestId; + }) | rpl::start_with_next([=](ReactionId &&id) { + _favorite = std::move(id); + _updated.fire({}); }, _lifetime); } @@ -81,17 +111,17 @@ const std::vector &Reactions::list(Type type) const { Unexpected("Type in Reactions::list."); } -QString Reactions::favorite() const { +ReactionId Reactions::favorite() const { return _favorite; } -void Reactions::setFavorite(const QString &emoji) { +void Reactions::setFavorite(const ReactionId &emoji) { const auto api = &_owner->session().api(); if (_saveFaveRequestId) { api->request(_saveFaveRequestId).cancel(); } _saveFaveRequestId = api->request(MTPmessages_SetDefaultReaction( - MTP_string(emoji) + ReactionToMTP(emoji) )).done([=] { _saveFaveRequestId = 0; }).fail([=] { @@ -108,12 +138,12 @@ rpl::producer<> Reactions::updates() const { return _updated.events(); } -void Reactions::preloadImageFor(const QString &emoji) { - if (_images.contains(emoji)) { +void Reactions::preloadImageFor(const ReactionId &id) { + if (_images.contains(id)) { return; } - auto &set = _images.emplace(emoji).first->second; - const auto i = ranges::find(_available, emoji, &Reaction::emoji); + auto &set = _images.emplace(id).first->second; + const auto i = ranges::find(_available, id, &Reaction::id); const auto document = (i == end(_available)) ? nullptr : i->centerIcon @@ -127,8 +157,8 @@ void Reactions::preloadImageFor(const QString &emoji) { } } -void Reactions::preloadAnimationsFor(const QString &emoji) { - const auto i = ranges::find(_available, emoji, &Reaction::emoji); +void Reactions::preloadAnimationsFor(const ReactionId &id) { + const auto i = ranges::find(_available, id, &Reaction::id); if (i == end(_available)) { return; } @@ -146,7 +176,7 @@ void Reactions::preloadAnimationsFor(const QString &emoji) { } QImage Reactions::resolveImageFor( - const QString &emoji, + const ReactionId &emoji, ImageSize size) { const auto i = _images.find(emoji); if (i == end(_images)) { @@ -194,11 +224,11 @@ QImage Reactions::resolveImageFor( } void Reactions::resolveImages() { - for (auto &[emoji, set] : _images) { + for (auto &[id, set] : _images) { if (!set.bottomInfo.isNull() || set.icon || set.media) { continue; } - const auto i = ranges::find(_available, emoji, &Reaction::emoji); + const auto i = ranges::find(_available, id, &Reaction::id); const auto document = (i == end(_available)) ? nullptr : i->centerIcon @@ -207,8 +237,8 @@ void Reactions::resolveImages() { if (document) { loadImage(set, document, !i->centerIcon); } else { - LOG(("API Error: Reaction for emoji '%1' not found!" - ).arg(emoji)); + LOG(("API Error: Reaction '%1' not found!" + ).arg(ReactionIdToLog(id))); } } } @@ -338,7 +368,7 @@ std::optional Reactions::parse(const MTPAvailableReaction &entry) { data.vselect_animation()); return known ? std::make_optional(Reaction{ - .emoji = emoji, + .id = ReactionId{ emoji }, .title = qs(data.vtitle()), .staticIcon = _owner->processDocument(data.vstatic_icon()), .appearAnimation = _owner->processDocument( @@ -362,7 +392,7 @@ std::optional Reactions::parse(const MTPAvailableReaction &entry) { }); } -void Reactions::send(not_null item, const QString &chosen) { +void Reactions::send(not_null item, const ReactionId &chosen) { const auto id = item->fullId(); auto &api = _owner->session().api(); auto i = _sentRequests.find(id); @@ -371,14 +401,14 @@ void Reactions::send(not_null item, const QString &chosen) { } else { i = _sentRequests.emplace(id).first; } - const auto flags = chosen.isEmpty() + const auto flags = chosen.empty() ? MTPmessages_SendReaction::Flag(0) : MTPmessages_SendReaction::Flag::f_reaction; i->second = api.request(MTPmessages_SendReaction( MTP_flags(flags), item->history()->peer->input, MTP_int(id.msg), - MTP_string(chosen) + ReactionToMTP(chosen) )).done([=](const MTPUpdates &result) { _sentRequests.remove(id); _owner->session().api().applyUpdates(result); @@ -509,13 +539,13 @@ MessageReactions::MessageReactions(not_null item) : _item(item) { } -void MessageReactions::add(const QString &reaction) { +void MessageReactions::add(const ReactionId &reaction) { if (_chosen == reaction) { return; } const auto history = _item->history(); const auto self = history->session().user(); - if (!_chosen.isEmpty()) { + if (!_chosen.empty()) { const auto i = _list.find(_chosen); Assert(i != end(_list)); --i->second; @@ -534,7 +564,7 @@ void MessageReactions::add(const QString &reaction) { } } _chosen = reaction; - if (!reaction.isEmpty()) { + if (!reaction.empty()) { if (_item->canViewReactions()) { auto &list = _recent[reaction]; list.insert(begin(list), RecentReaction{ self }); @@ -547,7 +577,7 @@ void MessageReactions::add(const QString &reaction) { } void MessageReactions::remove() { - add(QString()); + add(ReactionId()); } bool MessageReactions::checkIfChanged( @@ -558,10 +588,10 @@ bool MessageReactions::checkIfChanged( // We'll apply non-stale data from the request response. return false; } - auto existing = base::flat_set(); + auto existing = base::flat_set(); for (const auto &count : list) { const auto changed = count.match([&](const MTPDreactionCount &data) { - const auto reaction = qs(data.vreaction()); + const auto reaction = ReactionFromMTP(data.vreaction()); const auto nowCount = data.vcount().v; const auto i = _list.find(reaction); const auto wasCount = (i != end(_list)) ? i->second : 0; @@ -580,10 +610,10 @@ bool MessageReactions::checkIfChanged( return true; } } - auto parsed = base::flat_map>(); + auto parsed = base::flat_map>(); for (const auto &reaction : recent) { reaction.match([&](const MTPDmessagePeerReaction &data) { - const auto emoji = qs(data.vreaction()); + const auto emoji = ReactionFromMTP(data.vreaction()); if (_list.contains(emoji)) { parsed[emoji].push_back(RecentReaction{ .peer = owner.peer(peerFromMTP(data.vpeer_id())), @@ -614,16 +644,16 @@ bool MessageReactions::change( return false; } auto changed = false; - auto existing = base::flat_set(); + auto existing = base::flat_set(); for (const auto &count : list) { count.match([&](const MTPDreactionCount &data) { - const auto reaction = qs(data.vreaction()); + const auto reaction = ReactionFromMTP(data.vreaction()); if (!ignoreChosen) { if (data.is_chosen() && _chosen != reaction) { _chosen = reaction; changed = true; } else if (!data.is_chosen() && _chosen == reaction) { - _chosen = QString(); + _chosen = ReactionId(); changed = true; } } @@ -645,14 +675,14 @@ bool MessageReactions::change( ++i; } } - if (!_chosen.isEmpty() && !_list.contains(_chosen)) { - _chosen = QString(); + if (!_chosen.empty() && !_list.contains(_chosen)) { + _chosen = ReactionId(); } } - auto parsed = base::flat_map>(); + auto parsed = base::flat_map>(); for (const auto &reaction : recent) { reaction.match([&](const MTPDmessagePeerReaction &data) { - const auto emoji = qs(data.vreaction()); + const auto emoji = ReactionFromMTP(data.vreaction()); if (_list.contains(emoji)) { parsed[emoji].push_back(RecentReaction{ .peer = owner.peer(peerFromMTP(data.vpeer_id())), @@ -669,12 +699,12 @@ bool MessageReactions::change( return changed; } -const base::flat_map &MessageReactions::list() const { +const base::flat_map &MessageReactions::list() const { return _list; } auto MessageReactions::recent() const --> const base::flat_map> & { +-> const base::flat_map> & { return _recent; } @@ -699,7 +729,7 @@ void MessageReactions::markRead() { } } -QString MessageReactions::chosen() const { +ReactionId MessageReactions::chosen() const { return _chosen; } diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index f1d553d82..369b6a4ec 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -18,8 +18,36 @@ namespace Data { class DocumentMedia; class Session; +struct ReactionId { + std::variant data; + + [[nodiscard]] bool empty() const { + const auto emoji = std::get_if(&data); + return emoji && emoji->isEmpty(); + } + [[nodiscard]] QString emoji() const { + const auto emoji = std::get_if(&data); + return emoji ? *emoji : QString(); + } + [[nodiscard]] DocumentId custom() const { + const auto custom = std::get_if(&data); + return custom ? *custom : DocumentId(); + } +}; +Q_DECLARE_METATYPE(ReactionId); + +inline bool operator<(const ReactionId &a, const ReactionId &b) { + return a.data < b.data; +} +inline bool operator==(const ReactionId &a, const ReactionId &b) { + return a.data == b.data; +} + +[[nodiscard]] ReactionId ReactionFromMTP(const MTPReaction &reaction); +[[nodiscard]] MTPReaction ReactionToMTP(ReactionId id); + struct Reaction { - QString emoji; + ReactionId id; QString title; not_null staticIcon; not_null appearAnimation; @@ -44,8 +72,8 @@ public: All, }; [[nodiscard]] const std::vector &list(Type type) const; - [[nodiscard]] QString favorite() const; - void setFavorite(const QString &emoji); + [[nodiscard]] ReactionId favorite() const; + void setFavorite(const ReactionId &emoji); [[nodiscard]] static base::flat_set ParseAllowed( const MTPVector *list); @@ -56,13 +84,13 @@ public: BottomInfo, InlineList, }; - void preloadImageFor(const QString &emoji); - void preloadAnimationsFor(const QString &emoji); + void preloadImageFor(const ReactionId &emoji); + void preloadAnimationsFor(const ReactionId &emoji); [[nodiscard]] QImage resolveImageFor( - const QString &emoji, + const ReactionId &emoji, ImageSize size); - void send(not_null item, const QString &chosen); + void send(not_null item, const ReactionId &chosen); [[nodiscard]] bool sending(not_null item) const; void poll(not_null item, crl::time now); @@ -104,7 +132,7 @@ private: std::vector _active; std::vector _available; - QString _favorite; + ReactionId _favorite; base::flat_map< not_null, std::shared_ptr> _iconsCache; @@ -113,7 +141,7 @@ private: mtpRequestId _requestId = 0; int32 _hash = 0; - base::flat_map _images; + base::flat_map _images; rpl::lifetime _imagesLoadLifetime; bool _waitingForList = false; @@ -149,7 +177,7 @@ class MessageReactions final { public: explicit MessageReactions(not_null item); - void add(const QString &reaction); + void add(const ReactionId &reaction); void remove(); bool change( const QVector &list, @@ -158,10 +186,10 @@ public: [[nodiscard]] bool checkIfChanged( const QVector &list, const QVector &recent) const; - [[nodiscard]] const base::flat_map &list() const; + [[nodiscard]] const base::flat_map &list() const; [[nodiscard]] auto recent() const - -> const base::flat_map> &; - [[nodiscard]] QString chosen() const; + -> const base::flat_map> &; + [[nodiscard]] ReactionId chosen() const; [[nodiscard]] bool empty() const; [[nodiscard]] bool hasUnread() const; @@ -170,9 +198,9 @@ public: private: const not_null _item; - QString _chosen; - base::flat_map _list; - base::flat_map> _recent; + ReactionId _chosen; + base::flat_map _list; + base::flat_map> _recent; }; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 7dd367148..57af04041 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -401,16 +401,16 @@ HistoryInner::HistoryInner( || Window::ShowReactPremiumError( _controller, item, - reaction.emoji)) { + reaction.id)) { return; } - item->toggleReaction(reaction.emoji); - if (item->chosenReaction() != reaction.emoji) { + item->toggleReaction(reaction.id); + if (item->chosenReaction() != reaction.id) { return; } else if (const auto view = item->mainView()) { if (const auto top = itemTop(view); top >= 0) { view->animateReaction({ - .emoji = reaction.emoji, + .id = reaction.id, .flyIcon = reaction.icon, .flyFrom = reaction.geometry.translated(0, -top), }); @@ -1926,7 +1926,9 @@ void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) { void HistoryInner::toggleFavoriteReaction(not_null view) const { const auto favorite = session().data().reactions().favorite(); const auto allowed = _reactionsManager->allowedSublist(); - if (allowed && !allowed->contains(favorite)) { + if (allowed + && (favorite.emoji().isEmpty() + || !allowed->contains(favorite.emoji()))) { return; } const auto item = view->data(); @@ -1934,7 +1936,7 @@ void HistoryInner::toggleFavoriteReaction(not_null view) const { return; } else if (item->chosenReaction() != favorite) { if (const auto top = itemTop(view); top >= 0) { - view->animateReaction({ .emoji = favorite }); + view->animateReaction({ .id = favorite }); } } item->toggleReaction(favorite); @@ -1951,7 +1953,8 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto link = ClickHandler::getActive(); if (link - && !link->property(kSendReactionEmojiProperty).toString().isEmpty() + && !link->property( + kSendReactionEmojiProperty).value().empty() && _reactionsManager->showContextMenu( this, e, @@ -1995,17 +1998,18 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto hasWhoReactedItem = _dragStateItem && Api::WhoReactedExists(_dragStateItem); - const auto clickedEmoji = link - ? link->property(kReactionsCountEmojiProperty).toString() - : QString(); + const auto clickedReaction = link + ? link->property( + kReactionsCountEmojiProperty).value() + : Data::ReactionId(); _whoReactedMenuLifetime.destroy(); - if (hasWhoReactedItem && !clickedEmoji.isEmpty()) { + if (hasWhoReactedItem && !clickedReaction.empty()) { HistoryView::ShowWhoReactedMenu( &_menu, e->globalPos(), this, _dragStateItem, - clickedEmoji, + clickedReaction, _controller, _whoReactedMenuLifetime); e->accept(); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 0979d422d..0576d1a5e 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -883,7 +883,7 @@ bool HistoryItem::canReact() const { return true; } -void HistoryItem::addReaction(const QString &reaction) { +void HistoryItem::addReaction(const Data::ReactionId &reaction) { if (!_reactions) { _reactions = std::make_unique(this); } @@ -891,7 +891,7 @@ void HistoryItem::addReaction(const QString &reaction) { history()->owner().notifyItemDataChange(this); } -void HistoryItem::toggleReaction(const QString &reaction) { +void HistoryItem::toggleReaction(const Data::ReactionId &reaction) { if (!_reactions) { _reactions = std::make_unique(this); const auto canViewReactions = !isDiscussionPost() @@ -977,15 +977,17 @@ void HistoryItem::updateReactionsUnknown() { _reactionsLastRefreshed = 1; } -const base::flat_map &HistoryItem::reactions() const { - static const auto kEmpty = base::flat_map(); +const base::flat_map &HistoryItem::reactions() const { + static const auto kEmpty = base::flat_map(); return _reactions ? _reactions->list() : kEmpty; } auto HistoryItem::recentReactions() const --> const base::flat_map> & { +-> const base::flat_map< + Data::ReactionId, + std::vector> & { static const auto kEmpty = base::flat_map< - QString, + Data::ReactionId, std::vector>(); return _reactions ? _reactions->recent() : kEmpty; } @@ -996,25 +998,26 @@ bool HistoryItem::canViewReactions() const { && !_reactions->list().empty(); } -QString HistoryItem::chosenReaction() const { - return _reactions ? _reactions->chosen() : QString(); +Data::ReactionId HistoryItem::chosenReaction() const { + return _reactions ? _reactions->chosen() : Data::ReactionId(); } -QString HistoryItem::lookupUnreadReaction(not_null from) const { +Data::ReactionId HistoryItem::lookupUnreadReaction( + not_null from) const { if (!_reactions) { - return QString(); + return {}; } const auto recent = _reactions->recent(); - for (const auto &[emoji, list] : _reactions->recent()) { + for (const auto &[id, list] : _reactions->recent()) { const auto i = ranges::find( list, from, &Data::RecentReaction::peer); if (i != end(list) && i->unread) { - return emoji; + return id; } } - return QString(); + return {}; } crl::time HistoryItem::lastReactionsRefreshTime() const { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 89ece7de5..c7a8372f9 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -42,6 +42,7 @@ struct RippleAnimation; namespace Data { struct MessagePosition; struct RecentReaction; +struct ReactionId; class Media; class MessageReactions; } // namespace Data @@ -372,18 +373,19 @@ public: [[nodiscard]] bool suggestDeleteAllReport() const; [[nodiscard]] bool canReact() const; - void addReaction(const QString &reaction); - void toggleReaction(const QString &reaction); + void addReaction(const Data::ReactionId &reaction); + void toggleReaction(const Data::ReactionId &reaction); void updateReactions(const MTPMessageReactions *reactions); void updateReactionsUnknown(); - [[nodiscard]] const base::flat_map &reactions() const; + [[nodiscard]] auto reactions() const + -> const base::flat_map &; [[nodiscard]] auto recentReactions() const - -> const base::flat_map< - QString, - std::vector> &; + -> const base::flat_map< + Data::ReactionId, + std::vector> &; [[nodiscard]] bool canViewReactions() const; - [[nodiscard]] QString chosenReaction() const; - [[nodiscard]] QString lookupUnreadReaction( + [[nodiscard]] Data::ReactionId chosenReaction() const; + [[nodiscard]] Data::ReactionId lookupUnreadReaction( not_null from) const; [[nodiscard]] crl::time lastReactionsRefreshTime() const; diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index 49057809e..9ce17b3a9 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -29,6 +29,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { +ReactionAnimationArgs ReactionAnimationArgs::translated( + QPoint point) const { + return { + .id = id, + .flyIcon = flyIcon, + .flyFrom = flyFrom.translated(point), + }; +} + BottomInfo::BottomInfo( not_null<::Data::Reactions*> reactionsOwner, Data &&data) @@ -150,7 +159,7 @@ ClickHandlerPtr BottomInfo::revokeReactionLink( auto y = top; auto widthLeft = available; for (const auto &reaction : _reactions) { - const auto chosen = (reaction.emoji == _data.chosenReaction); + const auto chosen = (reaction.id == _data.chosenReaction); const auto add = (reaction.countTextWidth > 0) ? st::reactionInfoDigitSkip : st::reactionInfoBetween; @@ -192,7 +201,7 @@ ClickHandlerPtr BottomInfo::revokeReactionLink( auto &owner = controller->session().data(); if (const auto item = owner.message(itemId)) { const auto chosen = item->chosenReaction(); - if (!chosen.isEmpty()) { + if (!chosen.empty()) { item->toggleReaction(chosen); } } @@ -348,7 +357,7 @@ void BottomInfo::paintReactions( } if (reaction.image.isNull()) { reaction.image = _reactionsOwner->resolveImageFor( - reaction.emoji, + reaction.id, ::Data::Reactions::ImageSize::BottomInfo); } const auto image = QRect( @@ -476,15 +485,18 @@ void BottomInfo::layoutReactionsText() { ) | ranges::view::transform([](const auto &pair) { return std::make_pair(pair.first, pair.second); }) | ranges::to_vector; - ranges::sort(sorted, std::greater<>(), &std::pair::second); + ranges::sort( + sorted, + std::greater<>(), + &std::pair::second); auto reactions = std::vector(); reactions.reserve(sorted.size()); - for (const auto &[emoji, count] : sorted) { - const auto i = ranges::find(_reactions, emoji, &Reaction::emoji); + for (const auto &[id, count] : sorted) { + const auto i = ranges::find(_reactions, id, &Reaction::id); reactions.push_back((i != end(_reactions)) ? std::move(*i) - : prepareReactionWithEmoji(emoji)); + : prepareReactionWithId(id)); setReactionCount(reactions.back(), count); } _reactions = std::move(reactions); @@ -514,10 +526,10 @@ QSize BottomInfo::countOptimalSize() { return QSize(width, st::msgDateFont->height); } -BottomInfo::Reaction BottomInfo::prepareReactionWithEmoji( - const QString &emoji) { - auto result = Reaction{ .emoji = emoji }; - _reactionsOwner->preloadImageFor(emoji); +BottomInfo::Reaction BottomInfo::prepareReactionWithId( + const ReactionId &id) { + auto result = Reaction{ .id = id }; + _reactionsOwner->preloadImageFor(id); return result; } @@ -537,7 +549,7 @@ void BottomInfo::setReactionCount(Reaction &reaction, int count) { void BottomInfo::animateReaction( ReactionAnimationArgs &&args, Fn repaint) { - const auto i = ranges::find(_reactions, args.emoji, &Reaction::emoji); + const auto i = ranges::find(_reactions, args.id, &Reaction::id); if (i == end(_reactions)) { return; } @@ -549,23 +561,23 @@ void BottomInfo::animateReaction( } auto BottomInfo::takeReactionAnimations() --> base::flat_map> { +-> base::flat_map> { auto result = base::flat_map< - QString, + ReactionId, std::unique_ptr>(); for (auto &reaction : _reactions) { if (reaction.animation) { - result.emplace(reaction.emoji, std::move(reaction.animation)); + result.emplace(reaction.id, std::move(reaction.animation)); } } return result; } void BottomInfo::continueReactionAnimations(base::flat_map< - QString, + ReactionId, std::unique_ptr> animations) { - for (auto &[emoji, animation] : animations) { - const auto i = ranges::find(_reactions, emoji, &Reaction::emoji); + for (auto &[id, animation] : animations) { + const auto i = ranges::find(_reactions, id, &Reaction::id); if (i != end(_reactions)) { i->animation = std::move(animation); } diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.h b/Telegram/SourceFiles/history/view/history_view_bottom_info.h index ab88def68..b5a839eec 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.h +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "history/view/history_view_object.h" +#include "data/data_message_reactions.h" #include "ui/text/text.h" #include "base/flags.h" @@ -15,23 +16,27 @@ namespace Ui { struct ChatPaintContext; } // namespace Ui -namespace Data { -class Reactions; -} // namespace Data - namespace HistoryView { namespace Reactions { class Animation; } // namespace Reactions +struct ReactionAnimationArgs { + ::Data::ReactionId id; + std::shared_ptr flyIcon; + QRect flyFrom; + + [[nodiscard]] ReactionAnimationArgs translated(QPoint point) const; +}; + using PaintContext = Ui::ChatPaintContext; class Message; struct TextState; -struct ReactionAnimationArgs; class BottomInfo final : public Object { public: + using ReactionId = ::Data::ReactionId; struct Data { enum class Flag : uchar { Edited = 0x01, @@ -49,8 +54,8 @@ public: QDateTime date; QString author; - base::flat_map reactions; - QString chosenReaction; + base::flat_map reactions; + ReactionId chosenReaction; std::optional views; std::optional replies; Flags flags; @@ -79,16 +84,16 @@ public: ReactionAnimationArgs &&args, Fn repaint); [[nodiscard]] auto takeReactionAnimations() - -> base::flat_map>; + -> base::flat_map>; void continueReactionAnimations(base::flat_map< - QString, + ReactionId, std::unique_ptr> animations); private: struct Reaction { mutable std::unique_ptr animation; mutable QImage image; - QString emoji; + ReactionId id; QString countText; int count = 0; int countTextWidth = 0; @@ -114,7 +119,8 @@ private: QSize countCurrentSize(int newWidth) override; void setReactionCount(Reaction &reaction, int count); - [[nodiscard]] Reaction prepareReactionWithEmoji(const QString &emoji); + [[nodiscard]] Reaction prepareReactionWithId( + const ReactionId &id); [[nodiscard]] ClickHandlerPtr revokeReactionLink( not_null item, QPoint position) const; diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index b2da03677..7adc9dbd4 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -1153,7 +1153,7 @@ void AddWhoReactedAction( controller->window().show(ReactionsListBox( controller, item, - QString(), + {}, whoReadIds)); } }; @@ -1172,7 +1172,7 @@ void ShowWhoReactedMenu( QPoint position, not_null context, not_null item, - const QString &emoji, + const Data::ReactionId &id, not_null controller, rpl::lifetime &lifetime) { const auto participantChosen = [=](uint64 id) { @@ -1183,20 +1183,20 @@ void ShowWhoReactedMenu( controller->window().show(ReactionsListBox( controller, item, - emoji)); + id)); } }; const auto reactions = &controller->session().data().reactions(); const auto &list = reactions->list( Data::Reactions::Type::Active); - const auto activeNonQuick = (emoji != reactions->favorite()) - && ranges::contains(list, emoji, &Data::Reaction::emoji); + const auto activeNonQuick = (id != reactions->favorite()) + && ranges::contains(list, id, &Data::Reaction::id); const auto filler = lifetime.make_state( participantChosen, showAllChosen); Api::WhoReacted( item, - emoji, + id, context, st::defaultWhoRead ) | rpl::filter([=](const Ui::WhoReadContent &content) { @@ -1206,7 +1206,7 @@ void ShowWhoReactedMenu( const auto refill = [=] { if (activeNonQuick) { (*menu)->addAction(tr::lng_context_set_as_quick(tr::now), [=] { - reactions->setFavorite(emoji); + reactions->setFavorite(id); }, &st::menuIconFave); (*menu)->addSeparator(); } diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.h b/Telegram/SourceFiles/history/view/history_view_context_menu.h index d7d8ac773..dc842b2b4 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.h +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unique_qptr.h" +namespace Data { +struct ReactionId; +} // namespace Data + namespace Main { class Session; } // namespace Main @@ -75,7 +79,7 @@ void ShowWhoReactedMenu( QPoint position, not_null context, not_null item, - const QString &emoji, + const Data::ReactionId &id, not_null controller, rpl::lifetime &lifetime); diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 96f5726cb..0d81768b0 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -353,15 +353,6 @@ void DateBadge::paint( ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide); } -ReactionAnimationArgs ReactionAnimationArgs::translated( - QPoint point) const { - return { - .emoji = emoji, - .flyIcon = flyIcon, - .flyFrom = flyFrom.translated(point), - }; -} - Element::Element( not_null delegate, not_null data, @@ -1115,15 +1106,15 @@ void Element::animateReaction(ReactionAnimationArgs &&args) { void Element::animateUnreadReactions() { const auto &recent = data()->recentReactions(); - for (const auto &[emoji, list] : recent) { + for (const auto &[id, list] : recent) { if (ranges::contains(list, true, &Data::RecentReaction::unread)) { - animateReaction({ .emoji = emoji }); + animateReaction({ .id = id }); } } } auto Element::takeReactionAnimations() --> base::flat_map> { +-> base::flat_map> { return {}; } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 6bd3ae0a6..6aea91de2 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -20,6 +20,7 @@ struct HistoryMessageReply; namespace Data { struct Reaction; +struct ReactionId; } // namespace Data namespace Window { @@ -41,6 +42,7 @@ namespace HistoryView { enum class PointState : char; enum class InfoDisplayType : char; +struct ReactionAnimationArgs; struct StateRequest; struct TextState; class Media; @@ -238,14 +240,6 @@ struct DateBadge : public RuntimeComponent { }; -struct ReactionAnimationArgs { - QString emoji; - std::shared_ptr flyIcon; - QRect flyFrom; - - [[nodiscard]] ReactionAnimationArgs translated(QPoint point) const; -}; - class Element : public Object , public RuntimeComposer @@ -442,7 +436,9 @@ public: virtual void animateReaction(ReactionAnimationArgs &&args); void animateUnreadReactions(); [[nodiscard]] virtual auto takeReactionAnimations() - -> base::flat_map>; + -> base::flat_map< + Data::ReactionId, + std::unique_ptr>; virtual ~Element(); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index bbfd55110..a62ef5428 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -361,16 +361,16 @@ ListWidget::ListWidget( || Window::ShowReactPremiumError( _controller, item, - reaction.emoji)) { + reaction.id)) { return; } - item->toggleReaction(reaction.emoji); - if (item->chosenReaction() != reaction.emoji) { + item->toggleReaction(reaction.id); + if (item->chosenReaction() != reaction.id) { return; } else if (const auto view = viewForItem(item)) { if (const auto top = itemTop(view); top >= 0) { view->animateReaction({ - .emoji = reaction.emoji, + .id = reaction.id, .flyIcon = reaction.icon, .flyFrom = reaction.geometry.translated(0, -top), }); @@ -2116,7 +2116,9 @@ void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) { void ListWidget::toggleFavoriteReaction(not_null view) const { const auto favorite = session().data().reactions().favorite(); const auto allowed = _reactionsManager->allowedSublist(); - if (allowed && !allowed->contains(favorite)) { + if (allowed + && (favorite.emoji().isEmpty() + || !allowed->contains(favorite.emoji()))) { return; } const auto item = view->data(); @@ -2124,7 +2126,7 @@ void ListWidget::toggleFavoriteReaction(not_null view) const { return; } else if (item->chosenReaction() != favorite) { if (const auto top = itemTop(view); top >= 0) { - view->animateReaction({ .emoji = favorite }); + view->animateReaction({ .id = favorite }); } } item->toggleReaction(favorite); @@ -2187,7 +2189,8 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto link = ClickHandler::getActive(); if (link - && !link->property(kSendReactionEmojiProperty).toString().isEmpty() + && !link->property( + kSendReactionEmojiProperty).value().empty() && _reactionsManager->showContextMenu( this, e, @@ -2201,17 +2204,18 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { : nullptr; const auto hasWhoReactedItem = overItem && Api::WhoReactedExists(overItem); - const auto clickedEmoji = link - ? link->property(kReactionsCountEmojiProperty).toString() - : QString(); + const auto clickedReaction = link + ? link->property( + kReactionsCountEmojiProperty).value() + : Data::ReactionId(); _whoReactedMenuLifetime.destroy(); - if (hasWhoReactedItem && !clickedEmoji.isEmpty()) { + if (hasWhoReactedItem && !clickedReaction.empty()) { HistoryView::ShowWhoReactedMenu( &_menu, e->globalPos(), this, overItem, - clickedEmoji, + clickedReaction, _controller, _whoReactedMenuLifetime); e->accept(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index b3d036ead..206bb0106 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -260,10 +260,12 @@ Message::Message( refreshReactions(); auto animations = replacing ? replacing->takeReactionAnimations() - : base::flat_map>(); + : base::flat_map< + Data::ReactionId, + std::unique_ptr>(); if (!animations.empty()) { const auto repainter = [=] { repaint(); }; - for (const auto &[emoji, animation] : animations) { + for (const auto &[id, animation] : animations) { animation->setRepaintCallback(repainter); } if (_reactions) { @@ -433,7 +435,7 @@ void Message::animateReaction(ReactionAnimationArgs &&args) { } auto Message::takeReactionAnimations() --> base::flat_map> { +-> base::flat_map> { return _reactions ? _reactions->takeAnimations() : _bottomInfo.takeReactionAnimations(); @@ -2182,15 +2184,15 @@ void Message::refreshReactions() { using namespace Reactions; auto reactionsData = InlineListDataFromMessage(this); if (!_reactions) { - const auto handlerFactory = [=](QString emoji) { + const auto handlerFactory = [=](ReactionId id) { const auto weak = base::make_weak(this); return std::make_shared([=] { if (const auto strong = weak.get()) { - strong->data()->toggleReaction(emoji); + strong->data()->toggleReaction(id); if (const auto now = weak.get()) { - if (now->data()->chosenReaction() == emoji) { + if (now->data()->chosenReaction() == id) { now->animateReaction({ - .emoji = emoji, + .id = id, }); } } diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index bef81fc92..11617b817 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -16,6 +16,10 @@ class HistoryMessage; struct HistoryMessageEdited; struct HistoryMessageForwarded; +namespace Data { +struct ReactionId; +} // namespace Data + namespace HistoryView { class ViewButton; @@ -136,7 +140,9 @@ public: void animateReaction(ReactionAnimationArgs &&args) override; auto takeReactionAnimations() - -> base::flat_map> override; + -> base::flat_map< + Data::ReactionId, + std::unique_ptr> override; QRect innerGeometry() const override; diff --git a/Telegram/SourceFiles/history/view/history_view_react_animation.cpp b/Telegram/SourceFiles/history/view/history_view_react_animation.cpp index f332fba6f..f773250dc 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_animation.cpp +++ b/Telegram/SourceFiles/history/view/history_view_react_animation.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_react_animation.h" #include "history/view/history_view_element.h" +#include "history/view/history_view_bottom_info.h" #include "lottie/lottie_icon.h" #include "data/data_message_reactions.h" #include "data/data_document.h" @@ -30,7 +31,7 @@ Animation::Animation( , _repaint(std::move(repaint)) , _flyFrom(args.flyFrom) { const auto &list = owner->list(::Data::Reactions::Type::All); - const auto i = ranges::find(list, args.emoji, &::Data::Reaction::emoji); + const auto i = ranges::find(list, args.id, &::Data::Reaction::id); if (i == end(list) || !i->centerIcon) { return; } diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.cpp b/Telegram/SourceFiles/history/view/history_view_react_button.cpp index 259a3dbe0..45104206a 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_react_button.cpp @@ -407,9 +407,9 @@ Manager::Manager( applyListFilters(); }, _lifetime); - _createChooseCallback = [=](QString emoji) { + _createChooseCallback = [=](ReactionId id) { return [=] { - if (auto chosen = lookupChosen(emoji)) { + if (auto chosen = lookupChosen(id)) { updateButton({}); _chosen.fire(std::move(chosen)); } @@ -417,13 +417,13 @@ Manager::Manager( }; } -Manager::Chosen Manager::lookupChosen(const QString &emoji) const { +Manager::Chosen Manager::lookupChosen(const ReactionId &id) const { auto result = Chosen{ .context = _buttonContext, - .emoji = emoji, + .id = id, }; const auto button = _button.get(); - const auto i = ranges::find(_icons, emoji, &ReactionIcons::emoji); + const auto i = ranges::find(_icons, id, &ReactionIcons::id); if (i == end(_icons) || !button) { return result; } @@ -471,14 +471,15 @@ void Manager::applyListFilters() { auto showPremiumLock = (ReactionIcons*)nullptr; auto favoriteIndex = -1; for (auto &icon : _list) { - const auto &emoji = icon.emoji; + const auto &id = icon.id; const auto add = applyUniqueLimit - ? _buttonAlreadyList.contains(emoji) - : (!_filter || _filter->contains(emoji)); + ? _buttonAlreadyList.contains(id) + : (!_filter + || (!id.emoji().isEmpty() && _filter->contains(id.emoji()))); if (add) { if (icon.premium && !_allowSendingPremium - && !_buttonAlreadyList.contains(emoji)) { + && !_buttonAlreadyList.contains(id)) { if (_premiumPossible) { showPremiumLock = &icon; } else { @@ -486,7 +487,7 @@ void Manager::applyListFilters() { } } else { icon.premiumLock = false; - if (emoji == _favorite) { + if (id == _favorite) { favoriteIndex = int(icons.size()); } icons.push_back(&icon); @@ -576,13 +577,13 @@ void Manager::showButtonDelayed() { void Manager::applyList( const std::vector &list, - const QString &favorite, + const ReactionId &favorite, bool premiumPossible) { const auto possibleChanged = (_premiumPossible != premiumPossible); _premiumPossible = premiumPossible; const auto proj = [](const auto &obj) { return std::tie( - obj.emoji, + obj.id, obj.appearAnimation, obj.selectAnimation, obj.premium); @@ -603,7 +604,7 @@ void Manager::applyList( _list.clear(); for (const auto &reaction : list) { _list.push_back({ - .emoji = reaction.emoji, + .id = reaction.id, .appearAnimation = reaction.appearAnimation, .selectAnimation = reaction.selectAnimation, .premium = reaction.premium, @@ -639,12 +640,12 @@ void Manager::updateUniqueLimit(not_null item) { } const auto &all = item->reactions(); const auto my = item->chosenReaction(); - auto list = base::flat_set(); + auto list = base::flat_set(); list.reserve(all.size()); auto myIsUnique = false; - for (const auto &[emoji, count] : all) { - list.emplace(emoji); - if (count == 1 && emoji == my) { + for (const auto &[id, count] : all) { + list.emplace(id); + if (count == 1 && id == my) { myIsUnique = true; } } @@ -780,7 +781,7 @@ void Manager::loadIcons() { if (all && !_icons.empty()) { auto &data = _icons.front()->appearAnimation->owner().reactions(); for (const auto &icon : _icons) { - data.preloadAnimationsFor(icon->emoji); + data.preloadAnimationsFor(icon->id); } } } @@ -886,15 +887,17 @@ void Manager::setSelectedIcon(int index) const { ClickHandlerPtr Manager::resolveButtonLink( const ReactionIcons &reaction) const { - const auto emoji = reaction.emoji; - const auto i = _reactionsLinks.find(emoji); + const auto id = reaction.id; + const auto i = _reactionsLinks.find(id); if (i != end(_reactionsLinks)) { return i->second; } auto handler = std::make_shared( - crl::guard(this, _createChooseCallback(emoji))); - handler->setProperty(kSendReactionEmojiProperty, emoji); - return _reactionsLinks.emplace(emoji, std::move(handler)).first->second; + crl::guard(this, _createChooseCallback(id))); + handler->setProperty( + kSendReactionEmojiProperty, + QVariant::fromValue(id)); + return _reactionsLinks.emplace(id, std::move(handler)).first->second; } TextState Manager::buttonTextState(QPoint position) const { @@ -1625,23 +1628,23 @@ void Manager::recordCurrentReactionEffect(FullMsgId itemId, QPoint origin) { bool Manager::showContextMenu( QWidget *parent, QContextMenuEvent *e, - const QString &favorite) { + const ReactionId &favorite) { if (_icons.empty() || _selectedIcon < 0) { return false; } - const auto lookupSelectedEmoji = [&] { + const auto lookupSelectedId = [&] { const auto i = ranges::find(_icons, true, &ReactionIcons::selected); - return (i != end(_icons)) ? (*i)->emoji : QString(); + return (i != end(_icons)) ? (*i)->id : ReactionId(); }; - if (!favorite.isEmpty() && lookupSelectedEmoji() == favorite) { + if (!favorite.empty() && lookupSelectedId() == favorite) { return true; } _menu = base::make_unique_q( parent, st::popupMenuWithIcons); const auto callback = [=] { - if (const auto emoji = lookupSelectedEmoji(); !emoji.isEmpty()) { - _faveRequests.fire_copy(emoji); + if (const auto id = lookupSelectedId(); !id.empty()) { + _faveRequests.fire_copy(id); } }; _menu->addAction( @@ -1652,7 +1655,7 @@ bool Manager::showContextMenu( return true; } -rpl::producer Manager::faveRequests() const { +auto Manager::faveRequests() const -> rpl::producer { return _faveRequests.events(); } @@ -1677,8 +1680,8 @@ void SetupManagerList( }, manager->lifetime()); manager->faveRequests( - ) | rpl::start_with_next([=](const QString &emoji) { - reactions->setFavorite(emoji); + ) | rpl::start_with_next([=](const Data::ReactionId &id) { + reactions->setFavorite(id); manager->updateButton({}); }, manager->lifetime()); diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.h b/Telegram/SourceFiles/history/view/history_view_react_button.h index b3f728193..95ae70dee 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.h +++ b/Telegram/SourceFiles/history/view/history_view_react_button.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "ui/widgets/scroll_area.h" +#include "data/data_message_reactions.h" #include "ui/chat/chat_style.h" namespace Ui { @@ -143,11 +144,12 @@ public: IconFactory iconFactory); ~Manager(); + using ReactionId = ::Data::ReactionId; using AllowedSublist = std::optional>; void applyList( const std::vector &list, - const QString &favorite, + const ReactionId &favorite, bool premiumPossible); void updateAllowedSublist(AllowedSublist filter); void updateAllowSendingPremium(bool allow); @@ -163,12 +165,12 @@ public: struct Chosen { FullMsgId context; - QString emoji; + ReactionId id; std::shared_ptr icon; QRect geometry; explicit operator bool() const { - return context && !emoji.isNull(); + return context && !id.empty(); } }; [[nodiscard]] rpl::producer chosen() const { @@ -185,8 +187,8 @@ public: bool showContextMenu( QWidget *parent, QContextMenuEvent *e, - const QString &favorite); - [[nodiscard]] rpl::producer faveRequests() const; + const ReactionId &favorite); + [[nodiscard]] rpl::producer faveRequests() const; [[nodiscard]] rpl::lifetime &lifetime() { return _lifetime; @@ -198,7 +200,7 @@ private: std::shared_ptr icon; }; struct ReactionIcons { - QString emoji; + ReactionId id; not_null appearAnimation; not_null selectAnimation; std::shared_ptr appear; @@ -221,7 +223,7 @@ private: void showButtonDelayed(); void stealWheelEvents(not_null target); - [[nodiscard]] Chosen lookupChosen(const QString &emoji) const; + [[nodiscard]] Chosen lookupChosen(const ReactionId &id) const; [[nodiscard]] bool overCurrentButton(QPoint position) const; void removeStaleButtons(); @@ -314,7 +316,7 @@ private: const IconFactory _iconFactory; rpl::event_stream _chosen; std::vector _list; - QString _favorite; + ReactionId _favorite; AllowedSublist _filter; QSize _outer; QRect _inner; @@ -358,10 +360,10 @@ private: std::unique_ptr