mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Poll reactions for visible messages.
This commit is contained in:
parent
aafc24008b
commit
ec16ca7df7
7 changed files with 164 additions and 15 deletions
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_document_media.h"
|
#include "data/data_document_media.h"
|
||||||
|
#include "data/data_changes.h"
|
||||||
#include "base/timer_rpl.h"
|
#include "base/timer_rpl.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
@ -22,18 +23,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Data {
|
namespace Data {
|
||||||
namespace {
|
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
|
} // namespace
|
||||||
|
|
||||||
Reactions::Reactions(not_null<Session*> owner) : _owner(owner) {
|
Reactions::Reactions(not_null<Session*> owner)
|
||||||
|
: _owner(owner)
|
||||||
|
, _repaintTimer([=] { repaintCollected(); }) {
|
||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
base::timer_each(
|
base::timer_each(
|
||||||
kRefreshEach
|
kRefreshFullListEach
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
refresh();
|
refresh();
|
||||||
}, _lifetime);
|
}, _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() {
|
void Reactions::refresh() {
|
||||||
|
@ -279,6 +292,85 @@ void Reactions::send(not_null<HistoryItem*> item, const QString &chosen) {
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Reactions::poll(not_null<HistoryItem*> 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<not_null<PeerData*>, QVector<MTPint>>();
|
||||||
|
_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<MTPint>(ids)
|
||||||
|
)).done([=](const MTPUpdates &result) {
|
||||||
|
_owner->session().api().applyUpdates(result);
|
||||||
|
finalize();
|
||||||
|
}).fail([=] {
|
||||||
|
finalize();
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Reactions::sending(not_null<HistoryItem*> item) const {
|
bool Reactions::sending(not_null<HistoryItem*> item) const {
|
||||||
return _sentRequests.contains(item->fullId());
|
return _sentRequests.contains(item->fullId());
|
||||||
}
|
}
|
||||||
|
@ -305,7 +397,7 @@ void MessageReactions::add(const QString &reaction) {
|
||||||
}
|
}
|
||||||
auto &owner = _item->history()->owner();
|
auto &owner = _item->history()->owner();
|
||||||
owner.reactions().send(_item, _chosen);
|
owner.reactions().send(_item, _chosen);
|
||||||
owner.requestItemResize(_item);
|
owner.notifyItemDataChange(_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageReactions::remove() {
|
void MessageReactions::remove() {
|
||||||
|
@ -315,22 +407,33 @@ void MessageReactions::remove() {
|
||||||
void MessageReactions::set(
|
void MessageReactions::set(
|
||||||
const QVector<MTPReactionCount> &list,
|
const QVector<MTPReactionCount> &list,
|
||||||
bool ignoreChosen) {
|
bool ignoreChosen) {
|
||||||
|
_lastRefreshTime = crl::now();
|
||||||
if (_item->history()->owner().reactions().sending(_item)) {
|
if (_item->history()->owner().reactions().sending(_item)) {
|
||||||
// We'll apply non-stale data from the request response.
|
// We'll apply non-stale data from the request response.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto changed = false;
|
||||||
auto existing = base::flat_set<QString>();
|
auto existing = base::flat_set<QString>();
|
||||||
for (const auto &count : list) {
|
for (const auto &count : list) {
|
||||||
count.match([&](const MTPDreactionCount &data) {
|
count.match([&](const MTPDreactionCount &data) {
|
||||||
const auto reaction = qs(data.vreaction());
|
const auto reaction = qs(data.vreaction());
|
||||||
if (data.is_chosen() && !ignoreChosen) {
|
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);
|
existing.emplace(reaction);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (_list.size() != existing.size()) {
|
if (_list.size() != existing.size()) {
|
||||||
|
changed = true;
|
||||||
for (auto i = begin(_list); i != end(_list);) {
|
for (auto i = begin(_list); i != end(_list);) {
|
||||||
if (!existing.contains(i->first)) {
|
if (!existing.contains(i->first)) {
|
||||||
i = _list.erase(i);
|
i = _list.erase(i);
|
||||||
|
@ -342,7 +445,9 @@ void MessageReactions::set(
|
||||||
_chosen = QString();
|
_chosen = QString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_item->history()->owner().requestItemResize(_item);
|
if (changed) {
|
||||||
|
_item->history()->owner().notifyItemDataChange(_item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const base::flat_map<QString, int> &MessageReactions::list() const {
|
const base::flat_map<QString, int> &MessageReactions::list() const {
|
||||||
|
@ -353,6 +458,10 @@ bool MessageReactions::empty() const {
|
||||||
return _list.empty();
|
return _list.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crl::time MessageReactions::lastRefreshTime() const {
|
||||||
|
return _lastRefreshTime;
|
||||||
|
}
|
||||||
|
|
||||||
QString MessageReactions::chosen() const {
|
QString MessageReactions::chosen() const {
|
||||||
return _chosen;
|
return _chosen;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/timer.h"
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
||||||
class DocumentMedia;
|
class DocumentMedia;
|
||||||
|
@ -59,6 +61,8 @@ public:
|
||||||
void send(not_null<HistoryItem*> item, const QString &chosen);
|
void send(not_null<HistoryItem*> item, const QString &chosen);
|
||||||
[[nodiscard]] bool sending(not_null<HistoryItem*> item) const;
|
[[nodiscard]] bool sending(not_null<HistoryItem*> item) const;
|
||||||
|
|
||||||
|
void poll(not_null<HistoryItem*> item, crl::time now);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ImageSet {
|
struct ImageSet {
|
||||||
QImage bottomInfo;
|
QImage bottomInfo;
|
||||||
|
@ -76,6 +80,9 @@ private:
|
||||||
void resolveImages();
|
void resolveImages();
|
||||||
void downloadTaskFinished();
|
void downloadTaskFinished();
|
||||||
|
|
||||||
|
void repaintCollected();
|
||||||
|
void pollCollected();
|
||||||
|
|
||||||
const not_null<Session*> _owner;
|
const not_null<Session*> _owner;
|
||||||
|
|
||||||
std::vector<Reaction> _active;
|
std::vector<Reaction> _active;
|
||||||
|
@ -91,6 +98,12 @@ private:
|
||||||
|
|
||||||
base::flat_map<FullMsgId, mtpRequestId> _sentRequests;
|
base::flat_map<FullMsgId, mtpRequestId> _sentRequests;
|
||||||
|
|
||||||
|
base::flat_map<not_null<HistoryItem*>, crl::time> _repaintItems;
|
||||||
|
base::Timer _repaintTimer;
|
||||||
|
base::flat_set<not_null<HistoryItem*>> _pollItems;
|
||||||
|
base::flat_set<not_null<HistoryItem*>> _pollingItems;
|
||||||
|
mtpRequestId _pollRequestId = 0;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -105,12 +118,14 @@ public:
|
||||||
[[nodiscard]] const base::flat_map<QString, int> &list() const;
|
[[nodiscard]] const base::flat_map<QString, int> &list() const;
|
||||||
[[nodiscard]] QString chosen() const;
|
[[nodiscard]] QString chosen() const;
|
||||||
[[nodiscard]] bool empty() const;
|
[[nodiscard]] bool empty() const;
|
||||||
|
[[nodiscard]] crl::time lastRefreshTime() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const not_null<HistoryItem*> _item;
|
const not_null<HistoryItem*> _item;
|
||||||
|
|
||||||
QString _chosen;
|
QString _chosen;
|
||||||
base::flat_map<QString, int> _list;
|
base::flat_map<QString, int> _list;
|
||||||
|
crl::time _lastRefreshTime = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -708,6 +708,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
width(),
|
width(),
|
||||||
std::min(st::msgMaxWidth / 2, width() / 2));
|
std::min(st::msgMaxWidth / 2, width() / 2));
|
||||||
|
|
||||||
|
const auto now = crl::now();
|
||||||
const auto historyDisplayedEmpty = _history->isDisplayedEmpty()
|
const auto historyDisplayedEmpty = _history->isDisplayedEmpty()
|
||||||
&& (!_migrated || _migrated->isDisplayedEmpty());
|
&& (!_migrated || _migrated->isDisplayedEmpty());
|
||||||
bool noHistoryDisplayed = historyDisplayedEmpty;
|
bool noHistoryDisplayed = historyDisplayedEmpty;
|
||||||
|
@ -783,6 +784,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
readMentions.insert(item);
|
readMentions.insert(item);
|
||||||
_widget->enqueueMessageHighlight(view);
|
_widget->enqueueMessageHighlight(view);
|
||||||
}
|
}
|
||||||
|
session().data().reactions().poll(item, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
top += height;
|
top += height;
|
||||||
|
@ -826,17 +828,19 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
seltoy - htop);
|
seltoy - htop);
|
||||||
view->draw(p, context);
|
view->draw(p, context);
|
||||||
|
|
||||||
|
const auto item = view->data();
|
||||||
const auto middle = top + height / 2;
|
const auto middle = top + height / 2;
|
||||||
const auto bottom = top + height;
|
const auto bottom = top + height;
|
||||||
if (_visibleAreaBottom >= bottom) {
|
if (_visibleAreaBottom >= bottom) {
|
||||||
const auto item = view->data();
|
|
||||||
if (!item->out() && item->unread()) {
|
if (!item->out() && item->unread()) {
|
||||||
readTill = item;
|
readTill = item;
|
||||||
}
|
}
|
||||||
if (item->isSponsored()) {
|
}
|
||||||
session().data().sponsoredMessages().view(
|
if (item->isSponsored()
|
||||||
item->fullId());
|
&& view->markSponsoredViewed(
|
||||||
}
|
_visibleAreaBottom - top)) {
|
||||||
|
session().data().sponsoredMessages().view(
|
||||||
|
item->fullId());
|
||||||
}
|
}
|
||||||
if (_visibleAreaBottom >= middle
|
if (_visibleAreaBottom >= middle
|
||||||
&& _visibleAreaTop <= middle) {
|
&& _visibleAreaTop <= middle) {
|
||||||
|
@ -848,6 +852,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
_widget->enqueueMessageHighlight(view);
|
_widget->enqueueMessageHighlight(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
session().data().reactions().poll(item, now);
|
||||||
}
|
}
|
||||||
top += height;
|
top += height;
|
||||||
context.translate(0, -height);
|
context.translate(0, -height);
|
||||||
|
|
|
@ -765,6 +765,7 @@ void HistoryItem::toggleReaction(const QString &reaction) {
|
||||||
_reactions->remove();
|
_reactions->remove();
|
||||||
if (_reactions->empty()) {
|
if (_reactions->empty()) {
|
||||||
_reactions = nullptr;
|
_reactions = nullptr;
|
||||||
|
history()->owner().notifyItemDataChange(this);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_reactions->add(reaction);
|
_reactions->add(reaction);
|
||||||
|
@ -775,7 +776,10 @@ void HistoryItem::toggleReaction(const QString &reaction) {
|
||||||
void HistoryItem::updateReactions(const MTPMessageReactions *reactions) {
|
void HistoryItem::updateReactions(const MTPMessageReactions *reactions) {
|
||||||
if (!reactions) {
|
if (!reactions) {
|
||||||
_flags &= ~MessageFlag::CanViewReactions;
|
_flags &= ~MessageFlag::CanViewReactions;
|
||||||
_reactions = nullptr;
|
if (_reactions) {
|
||||||
|
_reactions = nullptr;
|
||||||
|
history()->owner().notifyItemDataChange(this);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
reactions->match([&](const MTPDmessageReactions &data) {
|
reactions->match([&](const MTPDmessageReactions &data) {
|
||||||
|
@ -785,13 +789,15 @@ void HistoryItem::updateReactions(const MTPMessageReactions *reactions) {
|
||||||
_flags &= ~MessageFlag::CanViewReactions;
|
_flags &= ~MessageFlag::CanViewReactions;
|
||||||
}
|
}
|
||||||
if (data.vresults().v.isEmpty()) {
|
if (data.vresults().v.isEmpty()) {
|
||||||
_reactions = nullptr;
|
if (_reactions) {
|
||||||
|
_reactions = nullptr;
|
||||||
|
history()->owner().notifyItemDataChange(this);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
} else if (!_reactions) {
|
} else if (!_reactions) {
|
||||||
_reactions = std::make_unique<Data::MessageReactions>(this);
|
_reactions = std::make_unique<Data::MessageReactions>(this);
|
||||||
}
|
}
|
||||||
_reactions->set(data.vresults().v, data.is_min());
|
_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();
|
return _reactions ? _reactions->chosen() : QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crl::time HistoryItem::lastReactionsRefreshTime() const {
|
||||||
|
return _reactions ? _reactions->lastRefreshTime() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool HistoryItem::hasDirectLink() const {
|
bool HistoryItem::hasDirectLink() const {
|
||||||
return isRegular() && _history->peer->isChannel();
|
return isRegular() && _history->peer->isChannel();
|
||||||
}
|
}
|
||||||
|
|
|
@ -367,6 +367,7 @@ public:
|
||||||
[[nodiscard]] const base::flat_map<QString, int> &reactions() const;
|
[[nodiscard]] const base::flat_map<QString, int> &reactions() const;
|
||||||
[[nodiscard]] bool canViewReactions() const;
|
[[nodiscard]] bool canViewReactions() const;
|
||||||
[[nodiscard]] QString chosenReaction() const;
|
[[nodiscard]] QString chosenReaction() const;
|
||||||
|
[[nodiscard]] crl::time lastReactionsRefreshTime() const;
|
||||||
|
|
||||||
[[nodiscard]] bool hasDirectLink() const;
|
[[nodiscard]] bool hasDirectLink() const;
|
||||||
|
|
||||||
|
|
|
@ -547,6 +547,13 @@ void Element::nextInBlocksRemoved() {
|
||||||
setAttachToNext(false);
|
setAttachToNext(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Element::markSponsoredViewed(int shownFromTop) const {
|
||||||
|
const auto sponsoredTextTop = height()
|
||||||
|
- st::msgPadding.bottom()
|
||||||
|
- st::historyViewButtonHeight;
|
||||||
|
return shownFromTop >= sponsoredTextTop;
|
||||||
|
}
|
||||||
|
|
||||||
void Element::refreshDataId() {
|
void Element::refreshDataId() {
|
||||||
if (const auto media = this->media()) {
|
if (const auto media = this->media()) {
|
||||||
media->refreshParentId(data());
|
media->refreshParentId(data());
|
||||||
|
|
|
@ -408,6 +408,8 @@ public:
|
||||||
return fromLink();
|
return fromLink();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool markSponsoredViewed(int shownFromTop) const;
|
||||||
|
|
||||||
virtual ~Element();
|
virtual ~Element();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
Loading…
Add table
Reference in a new issue