Request top and recent reactions.

This commit is contained in:
John Preston 2022-08-24 11:42:01 +03:00
parent f8c962712b
commit 31db1804c8
11 changed files with 348 additions and 75 deletions

View file

@ -2387,6 +2387,14 @@ void Updates::feedUpdate(const MTPUpdate &update) {
} }
} break; } break;
case mtpc_updateRecentEmojiStatuses: {
// #TODO emoji_status
} break;
case mtpc_updateRecentReactions: {
session().data().reactions().refreshRecentDelayed();
} break;
////// Cloud saved GIFs ////// Cloud saved GIFs
case mtpc_updateSavedGifs: { case mtpc_updateSavedGifs: {
session().data().stickers().setLastSavedGifsUpdate(0); session().data().stickers().setLastSavedGifsUpdate(0);

View file

@ -174,7 +174,7 @@ void SaveAllowedReactions(
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
if (error.type() == qstr("REACTION_INVALID")) { if (error.type() == qstr("REACTION_INVALID")) {
peer->updateFullForced(); peer->updateFullForced();
peer->owner().reactions().refresh(); peer->owner().reactions().refreshDefault();
} }
}).send(); }).send();
} }

View file

@ -393,8 +393,8 @@ void ReactionsSettingsBox(
const auto &reactions = controller->session().data().reactions(); const auto &reactions = controller->session().data().reactions();
const auto state = box->lifetime().make_state<State>(); const auto state = box->lifetime().make_state<State>();
state->selectedEmoji = v::is<QString>(reactions.favorite().data) state->selectedEmoji = v::is<QString>(reactions.favoriteId().data)
? v::get<QString>(reactions.favorite().data) ? v::get<QString>(reactions.favoriteId().data)
: QString(); : QString();
const auto pinnedToTop = box->setPinnedToTopContent( const auto pinnedToTop = box->setPinnedToTopContent(
@ -485,7 +485,7 @@ void ReactionsSettingsBox(
box->addButton(tr::lng_settings_save(), [=] { box->addButton(tr::lng_settings_save(), [=] {
const auto &data = controller->session().data(); const auto &data = controller->session().data();
const auto selected = state->selectedEmoji.current(); const auto selected = state->selectedEmoji.current();
if (data.reactions().favorite() != Data::ReactionId{ selected }) { if (data.reactions().favoriteId() != Data::ReactionId{ selected }) {
data.reactions().setFavorite(Data::ReactionId{ selected }); data.reactions().setFavorite(Data::ReactionId{ selected });
} }
box->closeBox(); box->closeBox();

View file

@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/image/image_location_factory.h" #include "ui/image/image_location_factory.h"
#include "mtproto/mtproto_config.h" #include "mtproto/mtproto_config.h"
#include "base/timer_rpl.h" #include "base/timer_rpl.h"
#include "base/call_delayed.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
@ -34,6 +35,10 @@ namespace {
constexpr auto kRefreshFullListEach = 60 * 60 * crl::time(1000); constexpr auto kRefreshFullListEach = 60 * 60 * crl::time(1000);
constexpr auto kPollEach = 20 * crl::time(1000); constexpr auto kPollEach = 20 * crl::time(1000);
constexpr auto kSizeForDownscale = 64; constexpr auto kSizeForDownscale = 64;
constexpr auto kRecentRequestTimeout = 10 * crl::time(1000);
constexpr auto kRecentReactionsLimit = 40;
constexpr auto kTopRequestDelay = 60 * crl::time(1000);
constexpr auto kTopReactionsLimit = 10;
[[nodiscard]] QString ReactionIdToLog(const ReactionId &id) { [[nodiscard]] QString ReactionIdToLog(const ReactionId &id) {
if (const auto custom = id.custom()) { if (const auto custom = id.custom()) {
@ -42,6 +47,22 @@ constexpr auto kSizeForDownscale = 64;
return id.emoji(); return id.emoji();
} }
[[nodiscard]] std::vector<ReactionId> ListFromMTP(
const MTPDmessages_reactions &data) {
const auto &list = data.vreactions().v;
auto result = std::vector<ReactionId>();
result.reserve(list.size());
for (const auto &reaction : list) {
const auto id = ReactionFromMTP(reaction);
if (id.empty()) {
LOG(("API Error: reactionEmpty in messages.reactions."));
} else {
result.push_back(id);
}
}
return result;
}
} // namespace } // namespace
PossibleItemReactions LookupPossibleReactions(not_null<HistoryItem*> item) { PossibleItemReactions LookupPossibleReactions(not_null<HistoryItem*> item) {
@ -96,7 +117,7 @@ PossibleItemReactions LookupPossibleReactions(not_null<HistoryItem*> item) {
} }
const auto i = ranges::find( const auto i = ranges::find(
result.recent, result.recent,
reactions->favorite(), reactions->favoriteId(),
&Reaction::id); &Reaction::id);
if (i != end(result.recent) && i != begin(result.recent)) { if (i != end(result.recent) && i != begin(result.recent)) {
std::rotate(begin(result.recent), i, i + 1); std::rotate(begin(result.recent), i, i + 1);
@ -106,13 +127,14 @@ PossibleItemReactions LookupPossibleReactions(not_null<HistoryItem*> item) {
Reactions::Reactions(not_null<Session*> owner) Reactions::Reactions(not_null<Session*> owner)
: _owner(owner) : _owner(owner)
, _topRefreshTimer([=] { refreshTop(); })
, _repaintTimer([=] { repaintCollected(); }) { , _repaintTimer([=] { repaintCollected(); }) {
refresh(); refreshDefault();
base::timer_each( base::timer_each(
kRefreshFullListEach kRefreshFullListEach
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
refresh(); refreshDefault();
}, _lifetime); }, _lifetime);
_owner->session().changes().messageUpdates( _owner->session().changes().messageUpdates(
@ -132,56 +154,102 @@ Reactions::Reactions(not_null<Session*> owner)
? ReactionId{ DocumentId(config.reactionDefaultCustom) } ? ReactionId{ DocumentId(config.reactionDefaultCustom) }
: ReactionId{ config.reactionDefaultEmoji }; : ReactionId{ config.reactionDefaultEmoji };
}) | rpl::filter([=](const ReactionId &id) { }) | rpl::filter([=](const ReactionId &id) {
return (_favorite != id) && !_saveFaveRequestId; return !_saveFaveRequestId;
}) | rpl::start_with_next([=](ReactionId &&id) { }) | rpl::start_with_next([=](ReactionId &&id) {
_favorite = std::move(id); applyFavorite(id);
_updated.fire({});
}, _lifetime); }, _lifetime);
} }
Reactions::~Reactions() = default; Reactions::~Reactions() = default;
void Reactions::refresh() { void Reactions::refreshTop() {
request(); requestTop();
}
void Reactions::refreshRecent() {
requestRecent();
}
void Reactions::refreshRecentDelayed() {
if (_recentRequestId || _recentRequestScheduled) {
return;
}
_recentRequestScheduled = true;
base::call_delayed(kRecentRequestTimeout, &_owner->session(), [=] {
if (_recentRequestScheduled) {
requestRecent();
}
});
}
void Reactions::refreshDefault() {
requestDefault();
} }
const std::vector<Reaction> &Reactions::list(Type type) const { const std::vector<Reaction> &Reactions::list(Type type) const {
switch (type) { switch (type) {
case Type::Active: return _active; case Type::Active: return _active;
case Type::Recent: return _recent;
case Type::Top: return _top;
case Type::All: return _available; case Type::All: return _available;
} }
Unexpected("Type in Reactions::list."); Unexpected("Type in Reactions::list.");
} }
ReactionId Reactions::favorite() const { ReactionId Reactions::favoriteId() const {
return _favorite; return _favoriteId;
} }
void Reactions::setFavorite(const ReactionId &emoji) { const Reaction *Reactions::favorite() const {
return _favorite ? &*_favorite : nullptr;
}
void Reactions::setFavorite(const ReactionId &id) {
const auto api = &_owner->session().api(); const auto api = &_owner->session().api();
if (_saveFaveRequestId) { if (_saveFaveRequestId) {
api->request(_saveFaveRequestId).cancel(); api->request(_saveFaveRequestId).cancel();
} }
_saveFaveRequestId = api->request(MTPmessages_SetDefaultReaction( _saveFaveRequestId = api->request(MTPmessages_SetDefaultReaction(
ReactionToMTP(emoji) ReactionToMTP(id)
)).done([=] { )).done([=] {
_saveFaveRequestId = 0; _saveFaveRequestId = 0;
}).fail([=] { }).fail([=] {
_saveFaveRequestId = 0; _saveFaveRequestId = 0;
}).send(); }).send();
if (_favorite != emoji) { applyFavorite(id);
_favorite = emoji; }
_updated.fire({});
void Reactions::applyFavorite(const ReactionId &id) {
if (_favoriteId != id) {
_favoriteId = id;
_favorite = resolveById(_favoriteId);
if (!_favorite && _unresolvedFavoriteId != _favoriteId) {
_unresolvedFavoriteId = _favoriteId;
resolve(_favoriteId);
}
_favoriteUpdated.fire({});
} }
} }
rpl::producer<> Reactions::updates() const { rpl::producer<> Reactions::topUpdates() const {
return _updated.events(); return _topUpdated.events();
}
rpl::producer<> Reactions::recentUpdates() const {
return _recentUpdated.events();
}
rpl::producer<> Reactions::defaultUpdates() const {
return _defaultUpdated.events();
}
rpl::producer<> Reactions::favoriteUpdates() const {
return _favoriteUpdated.events();
} }
void Reactions::preloadImageFor(const ReactionId &id) { void Reactions::preloadImageFor(const ReactionId &id) {
if (_images.contains(id)) { if (_images.contains(id) || id.emoji().isEmpty()) {
return; return;
} }
auto &set = _images.emplace(id).first->second; auto &set = _images.emplace(id).first->second;
@ -195,7 +263,7 @@ void Reactions::preloadImageFor(const ReactionId &id) {
loadImage(set, document, !i->centerIcon); loadImage(set, document, !i->centerIcon);
} else if (!_waitingForList) { } else if (!_waitingForList) {
_waitingForList = true; _waitingForList = true;
refresh(); refreshRecent();
} }
} }
@ -265,16 +333,6 @@ QImage Reactions::resolveImageFor(
Unexpected("ImageSize in Reactions::resolveImageFor."); Unexpected("ImageSize in Reactions::resolveImageFor.");
} }
std::unique_ptr<Ui::Text::CustomEmoji> Reactions::resolveCustomFor(
const ReactionId &emoji,
ImageSize size) {
const auto custom = std::get_if<DocumentId>(&emoji.data);
if (!custom) {
return nullptr;
}
return _owner->customEmojiManager().create(*custom, [] {});
}
void Reactions::resolveImages() { void Reactions::resolveImages() {
for (auto &[id, set] : _images) { for (auto &[id, set] : _images) {
if (!set.bottomInfo.isNull() || set.icon || set.media) { if (!set.bottomInfo.isNull() || set.icon || set.media) {
@ -343,27 +401,83 @@ void Reactions::downloadTaskFinished() {
} }
} }
void Reactions::request() { void Reactions::requestTop() {
auto &api = _owner->session().api(); if (_topRequestId) {
if (_requestId) {
return; return;
} }
_requestId = api.request(MTPmessages_GetAvailableReactions( auto &api = _owner->session().api();
MTP_int(_hash) _topRefreshTimer.cancel();
)).done([=](const MTPmessages_AvailableReactions &result) { _topRequestId = api.request(MTPmessages_GetTopReactions(
_requestId = 0; MTP_int(kTopReactionsLimit),
result.match([&](const MTPDmessages_availableReactions &data) { MTP_long(_topHash)
updateFromData(data); )).done([=](const MTPmessages_Reactions &result) {
}, [&](const MTPDmessages_availableReactionsNotModified &) { _topRequestId = 0;
result.match([&](const MTPDmessages_reactions &data) {
updateTop(data);
}, [](const MTPDmessages_reactionsNotModified&) {
}); });
}).fail([=] { }).fail([=] {
_requestId = 0; _topRequestId = 0;
_hash = 0; _topHash = 0;
}).send(); }).send();
} }
void Reactions::updateFromData(const MTPDmessages_availableReactions &data) { void Reactions::requestRecent() {
_hash = data.vhash().v; if (_recentRequestId) {
return;
}
auto &api = _owner->session().api();
_recentRequestScheduled = false;
_recentRequestId = api.request(MTPmessages_GetRecentReactions(
MTP_int(kRecentReactionsLimit),
MTP_long(_recentHash)
)).done([=](const MTPmessages_Reactions &result) {
_recentRequestId = 0;
result.match([&](const MTPDmessages_reactions &data) {
updateRecent(data);
}, [](const MTPDmessages_reactionsNotModified&) {
});
}).fail([=] {
_recentRequestId = 0;
_recentHash = 0;
}).send();
}
void Reactions::requestDefault() {
if (_defaultRequestId) {
return;
}
auto &api = _owner->session().api();
_defaultRequestId = api.request(MTPmessages_GetAvailableReactions(
MTP_int(_defaultHash)
)).done([=](const MTPmessages_AvailableReactions &result) {
_defaultRequestId = 0;
result.match([&](const MTPDmessages_availableReactions &data) {
updateDefault(data);
}, [&](const MTPDmessages_availableReactionsNotModified &) {
});
}).fail([=] {
_defaultRequestId = 0;
_defaultHash = 0;
}).send();
}
void Reactions::updateTop(const MTPDmessages_reactions &data) {
_topHash = data.vhash().v;
_topIds = ListFromMTP(data);
_top = resolveByIds(_topIds, _unresolvedTop);
_topUpdated.fire({});
}
void Reactions::updateRecent(const MTPDmessages_reactions &data) {
_recentHash = data.vhash().v;
_recentIds = ListFromMTP(data);
_recent = resolveByIds(_recentIds, _unresolvedRecent);
recentUpdated();
}
void Reactions::updateDefault(const MTPDmessages_availableReactions &data) {
_defaultHash = data.vhash().v;
const auto &list = data.vreactions().v; const auto &list = data.vreactions().v;
const auto oldCache = base::take(_iconsCache); const auto oldCache = base::take(_iconsCache);
@ -393,7 +507,99 @@ void Reactions::updateFromData(const MTPDmessages_availableReactions &data) {
_waitingForList = false; _waitingForList = false;
resolveImages(); resolveImages();
} }
_updated.fire({}); defaultUpdated();
}
void Reactions::recentUpdated() {
_topRefreshTimer.callOnce(kTopRequestDelay);
_recentUpdated.fire({});
}
void Reactions::defaultUpdated() {
refreshTop();
refreshRecent();
_defaultUpdated.fire({});
}
not_null<CustomEmojiManager::Listener*> Reactions::resolveListener() {
return static_cast<CustomEmojiManager::Listener*>(this);
}
void Reactions::customEmojiResolveDone(not_null<DocumentData*> document) {
const auto id = ReactionId{ { document->id } };
const auto favorite = (_unresolvedFavoriteId == id);
const auto i = _unresolvedTop.find(id);
const auto top = (i != end(_unresolvedTop));
const auto j = _unresolvedRecent.find(id);
const auto recent = (j != end(_unresolvedRecent));
if (favorite) {
_unresolvedFavoriteId = ReactionId();
_favorite = resolveById(_favoriteId);
}
if (top) {
_unresolvedTop.erase(i);
_top = resolveByIds(_topIds, _unresolvedTop);
}
if (recent) {
_unresolvedRecent.erase(j);
_recent = resolveByIds(_recentIds, _unresolvedRecent);
}
if (favorite) {
_favoriteUpdated.fire({});
}
if (top) {
_topUpdated.fire({});
}
if (recent) {
_recentUpdated.fire({});
}
}
std::optional<Reaction> Reactions::resolveById(const ReactionId &id) {
if (const auto emoji = id.emoji(); !emoji.isEmpty()) {
const auto i = ranges::find(_available, id, &Reaction::id);
if (i != end(_available)) {
return *i;
}
} else if (const auto customId = id.custom()) {
const auto document = _owner->document(customId);
if (document->sticker()) {
return Reaction{
.id = id,
.title = "Custom reaction",
.appearAnimation = document,
.selectAnimation = document,
.centerIcon = document,
.active = true,
};
}
}
return {};
}
std::vector<Reaction> Reactions::resolveByIds(
const std::vector<ReactionId> &ids,
base::flat_set<ReactionId> &unresolved) {
auto result = std::vector<Reaction>();
result.reserve(ids.size());
for (const auto &id : ids) {
if (const auto resolved = resolveById(id)) {
result.push_back(*resolved);
} else if (unresolved.emplace(id).second) {
resolve(id);
}
}
return result;
}
void Reactions::resolve(const ReactionId &id) {
if (const auto emoji = id.emoji(); !emoji.isEmpty()) {
refreshDefault();
} else if (const auto customId = id.custom()) {
_owner->customEmojiManager().resolve(
customId,
resolveListener());
}
} }
std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) { std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
@ -409,7 +615,7 @@ std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
? std::make_optional(Reaction{ ? std::make_optional(Reaction{
.id = ReactionId{ emoji }, .id = ReactionId{ emoji },
.title = qs(data.vtitle()), .title = qs(data.vtitle()),
.staticIcon = _owner->processDocument(data.vstatic_icon()), //.staticIcon = _owner->processDocument(data.vstatic_icon()),
.appearAnimation = _owner->processDocument( .appearAnimation = _owner->processDocument(
data.vappear_animation()), data.vappear_animation()),
.selectAnimation = selectAnimation, .selectAnimation = selectAnimation,

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h" #include "base/timer.h"
#include "data/data_message_reaction_id.h" #include "data/data_message_reaction_id.h"
#include "data/stickers/data_custom_emoji.h"
namespace Ui::Text { namespace Ui::Text {
class CustomEmoji; class CustomEmoji;
@ -26,7 +27,7 @@ class Session;
struct Reaction { struct Reaction {
ReactionId id; ReactionId id;
QString title; QString title;
not_null<DocumentData*> staticIcon; //not_null<DocumentData*> staticIcon;
not_null<DocumentData*> appearAnimation; not_null<DocumentData*> appearAnimation;
not_null<DocumentData*> selectAnimation; not_null<DocumentData*> selectAnimation;
//not_null<DocumentData*> activateAnimation; //not_null<DocumentData*> activateAnimation;
@ -46,22 +47,31 @@ struct PossibleItemReactions {
[[nodiscard]] PossibleItemReactions LookupPossibleReactions( [[nodiscard]] PossibleItemReactions LookupPossibleReactions(
not_null<HistoryItem*> item); not_null<HistoryItem*> item);
class Reactions final { class Reactions final : private CustomEmojiManager::Listener {
public: public:
explicit Reactions(not_null<Session*> owner); explicit Reactions(not_null<Session*> owner);
~Reactions(); ~Reactions();
void refresh(); void refreshTop();
void refreshRecent();
void refreshRecentDelayed();
void refreshDefault();
enum class Type { enum class Type {
Active, Active,
Recent,
Top,
All, All,
}; };
[[nodiscard]] const std::vector<Reaction> &list(Type type) const; [[nodiscard]] const std::vector<Reaction> &list(Type type) const;
[[nodiscard]] ReactionId favorite() const; [[nodiscard]] ReactionId favoriteId() const;
void setFavorite(const ReactionId &emoji); [[nodiscard]] const Reaction *favorite() const;
void setFavorite(const ReactionId &id);
[[nodiscard]] rpl::producer<> updates() const; [[nodiscard]] rpl::producer<> topUpdates() const;
[[nodiscard]] rpl::producer<> recentUpdates() const;
[[nodiscard]] rpl::producer<> defaultUpdates() const;
[[nodiscard]] rpl::producer<> favoriteUpdates() const;
enum class ImageSize { enum class ImageSize {
BottomInfo, BottomInfo,
@ -72,9 +82,6 @@ public:
[[nodiscard]] QImage resolveImageFor( [[nodiscard]] QImage resolveImageFor(
const ReactionId &emoji, const ReactionId &emoji,
ImageSize size); ImageSize size);
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> resolveCustomFor(
const ReactionId &emoji,
ImageSize size);
void send(not_null<HistoryItem*> item, const ReactionId &chosen); void send(not_null<HistoryItem*> item, const ReactionId &chosen);
[[nodiscard]] bool sending(not_null<HistoryItem*> item) const; [[nodiscard]] bool sending(not_null<HistoryItem*> item) const;
@ -97,8 +104,26 @@ private:
bool fromAppearAnimation = false; bool fromAppearAnimation = false;
}; };
void request(); [[nodiscard]] not_null<CustomEmojiManager::Listener*> resolveListener();
void updateFromData(const MTPDmessages_availableReactions &data); void customEmojiResolveDone(not_null<DocumentData*> document) override;
void requestTop();
void requestRecent();
void requestDefault();
void updateTop(const MTPDmessages_reactions &data);
void updateRecent(const MTPDmessages_reactions &data);
void updateDefault(const MTPDmessages_availableReactions &data);
void recentUpdated();
void defaultUpdated();
[[nodiscard]] std::optional<Reaction> resolveById(const ReactionId &id);
[[nodiscard]] std::vector<Reaction> resolveByIds(
const std::vector<ReactionId> &ids,
base::flat_set<ReactionId> &unresolved);
void resolve(const ReactionId &id);
void applyFavorite(const ReactionId &id);
[[nodiscard]] std::optional<Reaction> parse( [[nodiscard]] std::optional<Reaction> parse(
const MTPAvailableReaction &entry); const MTPAvailableReaction &entry);
@ -118,14 +143,34 @@ private:
std::vector<Reaction> _active; std::vector<Reaction> _active;
std::vector<Reaction> _available; std::vector<Reaction> _available;
ReactionId _favorite; std::vector<Reaction> _recent;
std::vector<ReactionId> _recentIds;
base::flat_set<ReactionId> _unresolvedRecent;
std::vector<Reaction> _top;
std::vector<ReactionId> _topIds;
base::flat_set<ReactionId> _unresolvedTop;
ReactionId _favoriteId;
ReactionId _unresolvedFavoriteId;
std::optional<Reaction> _favorite;
base::flat_map< base::flat_map<
not_null<DocumentData*>, not_null<DocumentData*>,
std::shared_ptr<DocumentMedia>> _iconsCache; std::shared_ptr<DocumentMedia>> _iconsCache;
rpl::event_stream<> _updated; rpl::event_stream<> _topUpdated;
rpl::event_stream<> _recentUpdated;
rpl::event_stream<> _defaultUpdated;
rpl::event_stream<> _favoriteUpdated;
mtpRequestId _requestId = 0; base::Timer _topRefreshTimer;
int32 _hash = 0; mtpRequestId _topRequestId = 0;
bool _topRequestScheduled = false;
uint64 _topHash = 0;
mtpRequestId _recentRequestId = 0;
bool _recentRequestScheduled = false;
uint64 _recentHash = 0;
mtpRequestId _defaultRequestId = 0;
int32 _defaultHash = 0;
base::flat_map<ReactionId, ImageSet> _images; base::flat_map<ReactionId, ImageSet> _images;
rpl::lifetime _imagesLoadLifetime; rpl::lifetime _imagesLoadLifetime;

View file

@ -1941,7 +1941,7 @@ void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) {
void HistoryInner::toggleFavoriteReaction(not_null<Element*> view) const { void HistoryInner::toggleFavoriteReaction(not_null<Element*> view) const {
const auto item = view->data(); const auto item = view->data();
const auto favorite = session().data().reactions().favorite(); const auto favorite = session().data().reactions().favoriteId();
if (!ranges::contains( if (!ranges::contains(
Data::LookupPossibleReactions(item).recent, Data::LookupPossibleReactions(item).recent,
favorite, favorite,
@ -1972,7 +1972,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
&& _reactionsManager->showContextMenu( && _reactionsManager->showContextMenu(
this, this,
e, e,
session().data().reactions().favorite())) { session().data().reactions().favoriteId())) {
return; return;
} }
auto selectedState = getSelectionState(); auto selectedState = getSelectionState();

View file

@ -1187,7 +1187,7 @@ void ShowWhoReactedMenu(
const auto reactions = &controller->session().data().reactions(); const auto reactions = &controller->session().data().reactions();
const auto &list = reactions->list( const auto &list = reactions->list(
Data::Reactions::Type::Active); Data::Reactions::Type::Active);
const auto activeNonQuick = (id != reactions->favorite()) const auto activeNonQuick = (id != reactions->favoriteId())
&& ranges::contains(list, id, &Data::Reaction::id); && ranges::contains(list, id, &Data::Reaction::id);
const auto filler = lifetime.make_state<Ui::WhoReactedListMenu>( const auto filler = lifetime.make_state<Ui::WhoReactedListMenu>(
participantChosen, participantChosen,

View file

@ -2122,7 +2122,7 @@ void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) {
void ListWidget::toggleFavoriteReaction(not_null<Element*> view) const { void ListWidget::toggleFavoriteReaction(not_null<Element*> view) const {
const auto item = view->data(); const auto item = view->data();
const auto favorite = session().data().reactions().favorite(); const auto favorite = session().data().reactions().favoriteId();
if (!ranges::contains( if (!ranges::contains(
Data::LookupPossibleReactions(item).recent, Data::LookupPossibleReactions(item).recent,
favorite, favorite,
@ -2199,7 +2199,7 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
&& _reactionsManager->showContextMenu( && _reactionsManager->showContextMenu(
this, this,
e, e,
session().data().reactions().favorite())) { session().data().reactions().favoriteId())) {
return; return;
} }
const auto overItem = _overItemExact const auto overItem = _overItemExact

View file

@ -37,6 +37,7 @@ constexpr auto kButtonExpandDelay = crl::time(25);
constexpr auto kButtonHideDelay = crl::time(300); constexpr auto kButtonHideDelay = crl::time(300);
constexpr auto kButtonExpandedHideDelay = crl::time(0); constexpr auto kButtonExpandedHideDelay = crl::time(0);
constexpr auto kMaxReactionsScrollAtOnce = 2; constexpr auto kMaxReactionsScrollAtOnce = 2;
constexpr auto kRefreshListDelay = crl::time(100);
[[nodiscard]] QPoint LocalPosition(not_null<QWheelEvent*> e) { [[nodiscard]] QPoint LocalPosition(not_null<QWheelEvent*> e) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
@ -840,6 +841,7 @@ void SetupManagerList(
Main::Session *session = nullptr; Main::Session *session = nullptr;
rpl::lifetime sessionLifetime; rpl::lifetime sessionLifetime;
rpl::lifetime peerLifetime; rpl::lifetime peerLifetime;
base::Timer timer;
}; };
const auto state = manager->lifetime().make_state<State>(); const auto state = manager->lifetime().make_state<State>();
@ -857,10 +859,12 @@ void SetupManagerList(
const auto peerChanged = (state->peer != peer); const auto peerChanged = (state->peer != peer);
const auto sessionChanged = (state->session != session); const auto sessionChanged = (state->session != session);
const auto push = [=] { const auto push = [=] {
state->timer.cancel();
if (const auto item = state->item) { if (const auto item = state->item) {
manager->applyList(Data::LookupPossibleReactions(item)); manager->applyList(Data::LookupPossibleReactions(item));
} }
}; };
state->timer.setCallback(push);
if (sessionChanged) { if (sessionChanged) {
state->sessionLifetime.destroy(); state->sessionLifetime.destroy();
state->session = session; state->session = session;
@ -875,6 +879,7 @@ void SetupManagerList(
) | rpl::start_with_next([=](const Data::MessageUpdate &update) { ) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
if (update.item == state->item) { if (update.item == state->item) {
state->item = nullptr; state->item = nullptr;
state->timer.cancel();
} }
}, state->sessionLifetime); }, state->sessionLifetime);
@ -883,8 +888,17 @@ void SetupManagerList(
return (item == state->item); return (item == state->item);
}) | rpl::start_with_next(push, state->sessionLifetime); }) | rpl::start_with_next(push, state->sessionLifetime);
session->data().reactions().updates( const auto &reactions = session->data().reactions();
) | rpl::start_with_next(push, state->sessionLifetime); rpl::merge(
reactions.topUpdates(),
reactions.recentUpdates(),
reactions.defaultUpdates(),
reactions.favoriteUpdates()
) | rpl::start_with_next([=] {
if (!state->timer.isActive()) {
state->timer.callOnce(kRefreshListDelay);
}
}, state->sessionLifetime);
} }
if (peerChanged) { if (peerChanged) {
state->peer = peer; state->peer = peer;

View file

@ -449,7 +449,7 @@ rpl::producer<int> FullReactionsCountValue(
not_null<Main::Session*> session) { not_null<Main::Session*> session) {
const auto reactions = &session->data().reactions(); const auto reactions = &session->data().reactions();
return rpl::single(rpl::empty) | rpl::then( return rpl::single(rpl::empty) | rpl::then(
reactions->updates() reactions->defaultUpdates()
) | rpl::map([=] { ) | rpl::map([=] {
return int(reactions->list(Data::Reactions::Type::Active).size()); return int(reactions->list(Data::Reactions::Type::Active).size());
}) | rpl::distinct_until_changed(); }) | rpl::distinct_until_changed();

View file

@ -906,10 +906,10 @@ void SetupMessages(
const auto &reactions = controller->session().data().reactions(); const auto &reactions = controller->session().data().reactions();
auto idValue = rpl::single( auto idValue = rpl::single(
reactions.favorite() reactions.favoriteId()
) | rpl::then( ) | rpl::then(
reactions.updates() | rpl::map([=] { reactions.favoriteUpdates() | rpl::map([=] {
return controller->session().data().reactions().favorite(); return controller->session().data().reactions().favoriteId();
}) })
) | rpl::filter([](const Data::ReactionId &id) { ) | rpl::filter([](const Data::ReactionId &id) {
return !id.empty(); return !id.empty();