From ec16ca7df78c533972ccb8a3f779e411e6316f8c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 28 Dec 2021 21:20:32 +0300 Subject: [PATCH] Poll reactions for visible messages. --- .../data/data_message_reactions.cpp | 123 +++++++++++++++++- .../SourceFiles/data/data_message_reactions.h | 15 +++ .../history/history_inner_widget.cpp | 15 ++- Telegram/SourceFiles/history/history_item.cpp | 16 ++- Telegram/SourceFiles/history/history_item.h | 1 + .../history/view/history_view_element.cpp | 7 + .../history/view/history_view_element.h | 2 + 7 files changed, 164 insertions(+), 15 deletions(-) diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index b8f0d796a..18c534043 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_document.h" #include "data/data_document_media.h" +#include "data/data_changes.h" #include "base/timer_rpl.h" #include "apiwrap.h" #include "styles/style_chat.h" @@ -22,18 +23,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { namespace { -constexpr auto kRefreshEach = 60 * 60 * crl::time(1000); +constexpr auto kRefreshFullListEach = 60 * 60 * crl::time(1000); +constexpr auto kPollEach = 20 * crl::time(1000); } // namespace -Reactions::Reactions(not_null owner) : _owner(owner) { +Reactions::Reactions(not_null owner) +: _owner(owner) +, _repaintTimer([=] { repaintCollected(); }) { refresh(); base::timer_each( - kRefreshEach + kRefreshFullListEach ) | rpl::start_with_next([=] { refresh(); }, _lifetime); + + _owner->session().changes().messageUpdates( + MessageUpdate::Flag::Destroyed + ) | rpl::start_with_next([=](const MessageUpdate &update) { + const auto item = update.item; + _pollingItems.remove(item); + _pollItems.remove(item); + _repaintItems.remove(item); + }, _lifetime); } void Reactions::refresh() { @@ -279,6 +292,85 @@ void Reactions::send(not_null item, const QString &chosen) { }).send(); } +void Reactions::poll(not_null item, crl::time now) { + // Group them by one second. + const auto last = item->lastReactionsRefreshTime(); + const auto grouped = ((last + 999) / 1000) * 1000; + if (!grouped || item->history()->peer->isUser()) { + // First reaction always edits message. + return; + } else if (const auto left = grouped + kPollEach - now; left > 0) { + if (!_repaintItems.contains(item)) { + _repaintItems.emplace(item, grouped + kPollEach); + if (!_repaintTimer.isActive() + || _repaintTimer.remainingTime() > left) { + _repaintTimer.callOnce(left); + } + } + } else if (!_pollingItems.contains(item)) { + if (_pollItems.empty() && !_pollRequestId) { + crl::on_main(&_owner->session(), [=] { + pollCollected(); + }); + } + _pollItems.emplace(item); + } +} + +void Reactions::repaintCollected() { + const auto now = crl::now(); + auto closest = 0; + for (auto i = begin(_repaintItems); i != end(_repaintItems);) { + if (i->second <= now) { + _owner->requestItemRepaint(i->first); + i = _repaintItems.erase(i); + } else { + if (!closest || i->second < closest) { + closest = i->second; + } + ++i; + } + } + if (closest) { + _repaintTimer.callOnce(closest - now); + } +} + +void Reactions::pollCollected() { + auto toRequest = base::flat_map, QVector>(); + _pollingItems = std::move(_pollItems); + for (const auto &item : _pollingItems) { + toRequest[item->history()->peer].push_back(MTP_int(item->id)); + } + auto &api = _owner->session().api(); + for (const auto &[peer, ids] : toRequest) { + const auto finalize = [=] { + const auto now = crl::now(); + for (const auto &item : base::take(_pollingItems)) { + const auto last = item->lastReactionsRefreshTime(); + if (last && last + kPollEach <= now) { + item->updateReactions(nullptr); + } + } + _pollRequestId = 0; + if (!_pollItems.empty()) { + crl::on_main(&_owner->session(), [=] { + pollCollected(); + }); + } + }; + _pollRequestId = api.request(MTPmessages_GetMessagesReactions( + peer->input, + MTP_vector(ids) + )).done([=](const MTPUpdates &result) { + _owner->session().api().applyUpdates(result); + finalize(); + }).fail([=] { + finalize(); + }).send(); + } +} + bool Reactions::sending(not_null item) const { return _sentRequests.contains(item->fullId()); } @@ -305,7 +397,7 @@ void MessageReactions::add(const QString &reaction) { } auto &owner = _item->history()->owner(); owner.reactions().send(_item, _chosen); - owner.requestItemResize(_item); + owner.notifyItemDataChange(_item); } void MessageReactions::remove() { @@ -315,22 +407,33 @@ void MessageReactions::remove() { void MessageReactions::set( const QVector &list, bool ignoreChosen) { + _lastRefreshTime = crl::now(); if (_item->history()->owner().reactions().sending(_item)) { // We'll apply non-stale data from the request response. return; } + auto changed = false; 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; + if (_chosen != reaction) { + _chosen = reaction; + changed = true; + } + } + const auto nowCount = data.vcount().v; + auto &wasCount = _list[reaction]; + if (wasCount != nowCount) { + wasCount = nowCount; + changed = true; } - _list[reaction] = data.vcount().v; existing.emplace(reaction); }); } if (_list.size() != existing.size()) { + changed = true; for (auto i = begin(_list); i != end(_list);) { if (!existing.contains(i->first)) { i = _list.erase(i); @@ -342,7 +445,9 @@ void MessageReactions::set( _chosen = QString(); } } - _item->history()->owner().requestItemResize(_item); + if (changed) { + _item->history()->owner().notifyItemDataChange(_item); + } } const base::flat_map &MessageReactions::list() const { @@ -353,6 +458,10 @@ bool MessageReactions::empty() const { return _list.empty(); } +crl::time MessageReactions::lastRefreshTime() const { + return _lastRefreshTime; +} + QString MessageReactions::chosen() const { return _chosen; } diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index 53fde0cc9..22eff5545 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/timer.h" + namespace Data { class DocumentMedia; @@ -59,6 +61,8 @@ public: void send(not_null item, const QString &chosen); [[nodiscard]] bool sending(not_null item) const; + void poll(not_null item, crl::time now); + private: struct ImageSet { QImage bottomInfo; @@ -76,6 +80,9 @@ private: void resolveImages(); void downloadTaskFinished(); + void repaintCollected(); + void pollCollected(); + const not_null _owner; std::vector _active; @@ -91,6 +98,12 @@ private: base::flat_map _sentRequests; + base::flat_map, crl::time> _repaintItems; + base::Timer _repaintTimer; + base::flat_set> _pollItems; + base::flat_set> _pollingItems; + mtpRequestId _pollRequestId = 0; + rpl::lifetime _lifetime; }; @@ -105,12 +118,14 @@ public: [[nodiscard]] const base::flat_map &list() const; [[nodiscard]] QString chosen() const; [[nodiscard]] bool empty() const; + [[nodiscard]] crl::time lastRefreshTime() const; private: const not_null _item; QString _chosen; base::flat_map _list; + crl::time _lastRefreshTime = 0; }; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 40391406c..397607d18 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -708,6 +708,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { width(), std::min(st::msgMaxWidth / 2, width() / 2)); + const auto now = crl::now(); const auto historyDisplayedEmpty = _history->isDisplayedEmpty() && (!_migrated || _migrated->isDisplayedEmpty()); bool noHistoryDisplayed = historyDisplayedEmpty; @@ -783,6 +784,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { readMentions.insert(item); _widget->enqueueMessageHighlight(view); } + session().data().reactions().poll(item, now); } top += height; @@ -826,17 +828,19 @@ void HistoryInner::paintEvent(QPaintEvent *e) { seltoy - htop); view->draw(p, context); + const auto item = view->data(); const auto middle = top + height / 2; const auto bottom = top + height; if (_visibleAreaBottom >= bottom) { - const auto item = view->data(); if (!item->out() && item->unread()) { readTill = item; } - if (item->isSponsored()) { - session().data().sponsoredMessages().view( - item->fullId()); - } + } + if (item->isSponsored() + && view->markSponsoredViewed( + _visibleAreaBottom - top)) { + session().data().sponsoredMessages().view( + item->fullId()); } if (_visibleAreaBottom >= middle && _visibleAreaTop <= middle) { @@ -848,6 +852,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { _widget->enqueueMessageHighlight(view); } } + session().data().reactions().poll(item, now); } top += height; context.translate(0, -height); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index ea491fd66..4f8214c34 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -765,6 +765,7 @@ void HistoryItem::toggleReaction(const QString &reaction) { _reactions->remove(); if (_reactions->empty()) { _reactions = nullptr; + history()->owner().notifyItemDataChange(this); } } else { _reactions->add(reaction); @@ -775,7 +776,10 @@ void HistoryItem::toggleReaction(const QString &reaction) { void HistoryItem::updateReactions(const MTPMessageReactions *reactions) { if (!reactions) { _flags &= ~MessageFlag::CanViewReactions; - _reactions = nullptr; + if (_reactions) { + _reactions = nullptr; + history()->owner().notifyItemDataChange(this); + } return; } reactions->match([&](const MTPDmessageReactions &data) { @@ -785,13 +789,15 @@ void HistoryItem::updateReactions(const MTPMessageReactions *reactions) { _flags &= ~MessageFlag::CanViewReactions; } if (data.vresults().v.isEmpty()) { - _reactions = nullptr; + if (_reactions) { + _reactions = nullptr; + history()->owner().notifyItemDataChange(this); + } return; } else if (!_reactions) { _reactions = std::make_unique(this); } _reactions->set(data.vresults().v, data.is_min()); - history()->owner().notifyItemDataChange(this); }); } @@ -810,6 +816,10 @@ QString HistoryItem::chosenReaction() const { return _reactions ? _reactions->chosen() : QString(); } +crl::time HistoryItem::lastReactionsRefreshTime() const { + return _reactions ? _reactions->lastRefreshTime() : 0; +} + 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 25d6aa20c..c7ece4b95 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -367,6 +367,7 @@ public: [[nodiscard]] const base::flat_map &reactions() const; [[nodiscard]] bool canViewReactions() const; [[nodiscard]] QString chosenReaction() const; + [[nodiscard]] crl::time lastReactionsRefreshTime() const; [[nodiscard]] bool hasDirectLink() const; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 93e9f12ad..f6d002867 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -547,6 +547,13 @@ void Element::nextInBlocksRemoved() { setAttachToNext(false); } +bool Element::markSponsoredViewed(int shownFromTop) const { + const auto sponsoredTextTop = height() + - st::msgPadding.bottom() + - st::historyViewButtonHeight; + return shownFromTop >= sponsoredTextTop; +} + void Element::refreshDataId() { if (const auto media = this->media()) { media->refreshParentId(data()); diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index b902cf736..3135acde6 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -408,6 +408,8 @@ public: return fromLink(); } + [[nodiscard]] bool markSponsoredViewed(int shownFromTop) const; + virtual ~Element(); protected: