From f5c7b206bb03583eb8ccbf10ea71f582a47428cf Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 11 Sep 2019 12:26:13 +0300 Subject: [PATCH] Track message reactions. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/api/api_updates.cpp | 13 ++ .../data/data_message_reactions.cpp | 114 ++++++++++++++++++ .../SourceFiles/data/data_message_reactions.h | 34 ++++++ .../history/history_inner_widget.cpp | 12 ++ Telegram/SourceFiles/history/history_item.cpp | 33 +++++ Telegram/SourceFiles/history/history_item.h | 8 ++ Telegram/SourceFiles/ui/chat/chat.style | 6 + 8 files changed, 222 insertions(+) create mode 100644 Telegram/SourceFiles/data/data_message_reactions.cpp create mode 100644 Telegram/SourceFiles/data/data_message_reactions.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 48953d6e7..f67db3492 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -445,6 +445,8 @@ PRIVATE data/data_media_types.h data/data_messages.cpp data/data_messages.h + data/data_message_reactions.cpp + data/data_message_reactions.h data/data_msg_id.h data/data_notify_settings.cpp data/data_notify_settings.h diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index d41b08728..80e10391c 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1617,6 +1617,19 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } break; + case mtpc_updateMessageReactions: { + const auto &d = update.c_updateMessageReactions(); + const auto peer = peerFromMTP(d.vpeer()); + if (const auto history = session().data().historyLoaded(peer)) { + const auto item = session().data().message( + peer, + d.vmsg_id().v); + if (item) { + item->updateReactions(d.vreactions()); + } + } + } break; + // Messages being read. case mtpc_updateReadHistoryInbox: { auto &d = update.c_updateReadHistoryInbox(); diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp new file mode 100644 index 000000000..1c259c6bd --- /dev/null +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -0,0 +1,114 @@ +/* +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 "data/data_message_reactions.h" + +#include "history/history.h" +#include "history/history_item.h" +#include "main/main_session.h" +#include "apiwrap.h" + +namespace Data { + +std::vector MessageReactions::SuggestList() { + constexpr auto utf = [](const char *utf) { + return QString::fromUtf8(utf); + }; + return { + utf("\xE2\x9D\xA4\xEF\xB8\x8F"), // :heart: + utf("\xF0\x9F\x91\x8D"), // :like: + utf("\xF0\x9F\x98\x82"), // :joy: + utf("\xF0\x9F\x98\xB3"), // :flushed: + utf("\xF0\x9F\x98\x94"), // :pensive: + }; +} + +MessageReactions::MessageReactions(not_null item) +: _item(item) { +} + +void MessageReactions::add(const QString &reaction) { + if (_chosen == reaction) { + return; + } + if (!_chosen.isEmpty()) { + const auto i = _list.find(_chosen); + Assert(i != end(_list)); + --i->second; + if (!i->second) { + _list.erase(i); + } + } + _chosen = reaction; + if (!reaction.isEmpty()) { + ++_list[reaction]; + } + sendRequest(); +} + +void MessageReactions::set( + const QVector &list, + bool ignoreChosen) { + if (_requestId) { + // We'll apply non-stale data from the request response. + return; + } + auto existing = base::flat_set(); + for (const auto &count : list) { + count.match([&](const MTPDreactionCount &data) { + const auto reaction = qs(data.vreaction()); + if (data.is_chosen() && !ignoreChosen) { + _chosen = reaction; + } + _list[reaction] = data.vcount().v; + existing.emplace(reaction); + }); + } + if (_list.size() != existing.size()) { + for (auto i = begin(_list); i != end(_list);) { + if (!existing.contains(i->first)) { + i = _list.erase(i); + } else { + ++i; + } + } + if (!_chosen.isEmpty() && !_list.contains(_chosen)) { + _chosen = QString(); + } + } +} + +const base::flat_map &MessageReactions::list() const { + return _list; +} + +QString MessageReactions::chosen() const { + return _chosen; +} + +void MessageReactions::sendRequest() { + const auto api = &_item->history()->session().api(); + if (_requestId) { + api->request(_requestId).cancel(); + } + const auto flags = _chosen.isEmpty() + ? MTPmessages_SendReaction::Flag(0) + : MTPmessages_SendReaction::Flag::f_reaction; + _requestId = api->request(MTPmessages_SendReaction( + MTP_flags(flags), + _item->history()->peer->input, + MTP_int(_item->id), + MTP_string(_chosen) + )).done([=](const MTPUpdates &result) { + _requestId = 0; + api->applyUpdates(result); + }).fail([=](const MTP::Error &error) { + _requestId = 0; + }).send(); +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h new file mode 100644 index 000000000..e2f392c1b --- /dev/null +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -0,0 +1,34 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Data { + +class MessageReactions final { +public: + static std::vector SuggestList(); + + explicit MessageReactions(not_null item); + + void add(const QString &reaction); + void set(const QVector &list, bool ignoreChosen); + [[nodiscard]] const base::flat_map &list() const; + [[nodiscard]] QString chosen() const; + +private: + void sendRequest(); + + const not_null _item; + + QString _chosen; + base::flat_map _list; + mtpRequestId _requestId = 0; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 23f2a3923..43c351a7e 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -61,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "data/data_session.h" #include "data/data_media_types.h" +#include "data/data_message_reactions.h" #include "data/data_document.h" #include "data/data_channel.h" #include "data/data_poll.h" @@ -1683,6 +1684,17 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { return; } const auto itemId = item->fullId(); + if (item->canReact()) { + auto reactionMenu = std::make_unique( + this, + st::reactionMenu); + for (const auto &text : Data::MessageReactions::SuggestList()) { + reactionMenu->addAction(text, [=] { + item->addReaction(text); + }); + } + _menu->addAction("Reaction", std::move(reactionMenu)); + } if (canSendMessages) { _menu->addAction(tr::lng_context_reply_msg(tr::now), [=] { _widget->replyToMessage(itemId); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 3bc223104..b9dcc70a9 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_scheduled_messages.h" // kScheduledUntilOnlineTimestamp #include "data/data_changes.h" #include "data/data_session.h" +#include "data/data_message_reactions.h" #include "data/data_messages.h" #include "data/data_media_types.h" #include "data/data_folder.h" @@ -741,6 +742,38 @@ bool HistoryItem::suggestDeleteAllReport() const { return !isPost() && !out(); } +bool HistoryItem::canReact() const { + return IsServerMsgId(id) && !out() && !_history->peer->isSelf(); +} + +void HistoryItem::addReaction(const QString &reaction) { + if (!_reactions) { + _reactions = std::make_unique(this); + } + _reactions->add(reaction); +} + +void HistoryItem::updateReactions(const MTPMessageReactions &reactions) { + reactions.match([&](const MTPDmessageReactions &data) { + if (data.vresults().v.isEmpty()) { + _reactions = nullptr; + return; + } else if (!_reactions) { + _reactions = std::make_unique(this); + } + _reactions->set(data.vresults().v, data.is_min()); + }); +} + +const base::flat_map &HistoryItem::reactions() const { + static const auto kEmpty = base::flat_map(); + return _reactions ? _reactions->list() : kEmpty; +} + +QString HistoryItem::chosenReaction() const { + return _reactions ? _reactions->chosen() : QString(); +} + bool HistoryItem::hasDirectLink() const { return isRegular() && _history->peer->isChannel(); } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 8f96c9ad7..2920d28bd 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -43,6 +43,7 @@ struct RippleAnimation; namespace Data { struct MessagePosition; class Media; +class MessageReactions; } // namespace Data namespace Window { @@ -389,6 +390,12 @@ public: [[nodiscard]] bool suggestBanReport() const; [[nodiscard]] bool suggestDeleteAllReport() const; + [[nodiscard]] bool canReact() const; + void addReaction(const QString &reaction); + void updateReactions(const MTPMessageReactions &reactions); + [[nodiscard]] const base::flat_map &reactions() const; + [[nodiscard]] QString chosenReaction() const; + [[nodiscard]] bool hasDirectLink() const; [[nodiscard]] FullMsgId fullId() const; @@ -489,6 +496,7 @@ protected: SavedMediaData _savedLocalEditMediaData; std::unique_ptr _media; + std::unique_ptr _reactions; private: diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 8daef9a37..af0930954 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -48,6 +48,12 @@ historyThemeSize: size(272px, 176px); historyMinimalWidth: 380px; +reactionMenu: PopupMenu(defaultPopupMenu) { + menu: Menu(defaultMenu) { + widthMin: 30px; + } +} + historyScroll: ScrollArea(defaultScrollArea) { bg: historyScrollBg; bgOver: historyScrollBgOver;