Request correct saved/default reaction tags.

This commit is contained in:
John Preston 2024-01-04 11:47:59 +04:00
parent 9b43d204e2
commit 9aacff8b54
5 changed files with 227 additions and 14 deletions

View file

@ -2508,6 +2508,10 @@ void Updates::feedUpdate(const MTPUpdate &update) {
session().data().reactions().refreshRecentDelayed();
} break;
case mtpc_updateSavedReactionTags: {
session().data().reactions().refreshMyTagsDelayed();
} break;
////// Cloud saved GIFs
case mtpc_updateSavedGifs: {
session().data().stickers().setLastSavedGifsUpdate(0);

View file

@ -38,6 +38,7 @@ constexpr auto kPollEach = 20 * crl::time(1000);
constexpr auto kSizeForDownscale = 64;
constexpr auto kRecentRequestTimeout = 10 * crl::time(1000);
constexpr auto kRecentReactionsLimit = 40;
constexpr auto kMyTagsRequestTimeout = crl::time(1000);
constexpr auto kTopRequestDelay = 60 * crl::time(1000);
constexpr auto kTopReactionsLimit = 14;
@ -64,6 +65,27 @@ constexpr auto kTopReactionsLimit = 14;
return result;
}
[[nodiscard]] std::vector<MyTagInfo> ListFromMTP(
const MTPDmessages_savedReactionTags &data) {
const auto &list = data.vtags().v;
auto result = std::vector<MyTagInfo>();
result.reserve(list.size());
for (const auto &reaction : list) {
const auto &data = reaction.data();
const auto id = ReactionFromMTP(data.vreaction());
if (id.empty()) {
LOG(("API Error: reactionEmpty in messages.reactions."));
} else {
result.push_back({
.id = id,
.title = qs(data.vtitle().value_or_empty()),
.count = data.vcount().v,
});
}
}
return result;
}
[[nodiscard]] Reaction CustomReaction(not_null<DocumentData*> document) {
return Reaction{
.id = { { document->id } },
@ -121,6 +143,8 @@ PossibleItemReactionsRef LookupPossibleReactions(
const auto &full = reactions->list(Reactions::Type::Active);
const auto &top = reactions->list(Reactions::Type::Top);
const auto &recent = reactions->list(Reactions::Type::Recent);
const auto &myTags = reactions->list(Reactions::Type::MyTags);
const auto &tags = reactions->list(Reactions::Type::Tags);
const auto &all = item->reactions();
const auto limit = UniqueReactionsLimit(peer);
const auto premiumPossible = session->premiumPossible();
@ -143,7 +167,19 @@ PossibleItemReactionsRef LookupPossibleReactions(
}
};
reactions->clearTemporary();
if (limited) {
if (item->reactionsAreTags()) {
auto &&all = ranges::views::concat(myTags, tags);
result.recent.reserve(myTags.size() + tags.size());
for (const auto &reaction : all) {
if (premiumPossible
|| ranges::contains(tags, reaction.id, &Reaction::id)) {
if (added.emplace(reaction.id).second) {
result.recent.push_back(&reaction);
}
}
}
result.customAllowed = premiumPossible;
} else if (limited) {
result.recent.reserve(all.size());
add([&](const Reaction &reaction) {
return ranges::contains(all, reaction.id, &MessageReaction::id);
@ -193,12 +229,14 @@ PossibleItemReactionsRef LookupPossibleReactions(
result.customAllowed = (allowed.type == AllowedReactionsType::All)
&& premiumPossible;
}
const auto i = ranges::find(
result.recent,
reactions->favoriteId(),
&Reaction::id);
if (i != end(result.recent) && i != begin(result.recent)) {
std::rotate(begin(result.recent), i, i + 1);
if (!item->reactionsAreTags()) {
const auto i = ranges::find(
result.recent,
reactions->favoriteId(),
&Reaction::id);
if (i != end(result.recent) && i != begin(result.recent)) {
std::rotate(begin(result.recent), i, i + 1);
}
}
return result;
}
@ -280,16 +318,42 @@ void Reactions::refreshDefault() {
requestDefault();
}
void Reactions::refreshMyTags() {
requestMyTags();
}
void Reactions::refreshMyTagsDelayed() {
if (_myTagsRequestId || _myTagsRequestScheduled) {
return;
}
_myTagsRequestScheduled = true;
base::call_delayed(kMyTagsRequestTimeout, &_owner->session(), [=] {
if (_myTagsRequestScheduled) {
requestMyTags();
}
});
}
void Reactions::refreshTags() {
requestTags();
}
const std::vector<Reaction> &Reactions::list(Type type) const {
switch (type) {
case Type::Active: return _active;
case Type::Recent: return _recent;
case Type::Top: return _top;
case Type::All: return _available;
case Type::MyTags: return _myTags;
case Type::Tags: return _tags;
}
Unexpected("Type in Reactions::list.");
}
const std::vector<MyTagInfo> &Reactions::myTagsInfo() const {
return _myTagsInfo;
}
ReactionId Reactions::favoriteId() const {
return _favoriteId;
}
@ -375,6 +439,14 @@ rpl::producer<> Reactions::favoriteUpdates() const {
return _favoriteUpdated.events();
}
rpl::producer<> Reactions::myTagsUpdates() const {
return _myTagsUpdated.events();
}
rpl::producer<> Reactions::tagsUpdates() const {
return _tagsUpdated.events();
}
void Reactions::preloadImageFor(const ReactionId &id) {
if (_images.contains(id) || id.emoji().isEmpty()) {
return;
@ -617,6 +689,46 @@ void Reactions::requestGeneric() {
}).send();
}
void Reactions::requestMyTags() {
if (_myTagsRequestId) {
return;
}
auto &api = _owner->session().api();
_myTagsRequestScheduled = false;
_myTagsRequestId = api.request(MTPmessages_GetSavedReactionTags(
MTP_long(_myTagsHash)
)).done([=](const MTPmessages_SavedReactionTags &result) {
_myTagsRequestId = 0;
result.match([&](const MTPDmessages_savedReactionTags &data) {
updateMyTags(data);
}, [](const MTPDmessages_savedReactionTagsNotModified&) {
});
}).fail([=] {
_myTagsRequestId = 0;
_myTagsHash = 0;
}).send();
}
void Reactions::requestTags() {
if (_tagsRequestId) {
return;
}
auto &api = _owner->session().api();
_tagsRequestId = api.request(MTPmessages_GetDefaultTagReactions(
MTP_long(_tagsHash)
)).done([=](const MTPmessages_Reactions &result) {
_tagsRequestId = 0;
result.match([&](const MTPDmessages_reactions &data) {
updateTags(data);
}, [](const MTPDmessages_reactionsNotModified&) {
});
}).fail([=] {
_tagsRequestId = 0;
_tagsHash = 0;
}).send();
}
void Reactions::updateTop(const MTPDmessages_reactions &data) {
_topHash = data.vhash().v;
_topIds = ListFromMTP(data);
@ -685,6 +797,23 @@ void Reactions::updateGeneric(const MTPDmessages_stickerSet &data) {
}
}
void Reactions::updateMyTags(const MTPDmessages_savedReactionTags &data) {
_myTagsHash = data.vhash().v;
_myTagsInfo = ListFromMTP(data);
_myTagsIds = _myTagsInfo | ranges::views::transform(
&MyTagInfo::id
) | ranges::to_vector;
_myTags = resolveByIds(_myTagsIds, _unresolvedMyTags);
_myTagsUpdated.fire({});
}
void Reactions::updateTags(const MTPDmessages_reactions &data) {
_tagsHash = data.vhash().v;
_tagsIds = ListFromMTP(data);
_tags = resolveByIds(_tagsIds, _unresolvedTags);
_tagsUpdated.fire({});
}
void Reactions::recentUpdated() {
_topRefreshTimer.callOnce(kTopRequestDelay);
_recentUpdated.fire({});
@ -696,9 +825,25 @@ void Reactions::defaultUpdated() {
if (_genericAnimations.empty()) {
requestGeneric();
}
refreshMyTags();
refreshTags();
_defaultUpdated.fire({});
}
void Reactions::myTagsUpdated() {
if (_genericAnimations.empty()) {
requestGeneric();
}
_myTagsUpdated.fire({});
}
void Reactions::tagsUpdated() {
if (_genericAnimations.empty()) {
requestGeneric();
}
_tagsUpdated.fire({});
}
not_null<CustomEmojiManager::Listener*> Reactions::resolveListener() {
return static_cast<CustomEmojiManager::Listener*>(this);
}
@ -710,6 +855,10 @@ void Reactions::customEmojiResolveDone(not_null<DocumentData*> document) {
const auto top = (i != end(_unresolvedTop));
const auto j = _unresolvedRecent.find(id);
const auto recent = (j != end(_unresolvedRecent));
const auto k = _unresolvedMyTags.find(id);
const auto myTag = (k != end(_unresolvedMyTags));
const auto l = _unresolvedTags.find(id);
const auto tag = (l != end(_unresolvedTags));
if (favorite) {
_unresolvedFavoriteId = ReactionId();
_favorite = resolveById(_favoriteId);
@ -722,6 +871,14 @@ void Reactions::customEmojiResolveDone(not_null<DocumentData*> document) {
_unresolvedRecent.erase(j);
_recent = resolveByIds(_recentIds, _unresolvedRecent);
}
if (myTag) {
_unresolvedMyTags.erase(k);
_myTags = resolveByIds(_myTagsIds, _unresolvedMyTags);
}
if (tag) {
_unresolvedTags.erase(l);
_tags = resolveByIds(_tagsIds, _unresolvedTags);
}
if (favorite) {
_favoriteUpdated.fire({});
}
@ -731,6 +888,12 @@ void Reactions::customEmojiResolveDone(not_null<DocumentData*> document) {
if (recent) {
_recentUpdated.fire({});
}
if (myTag) {
_myTagsUpdated.fire({});
}
if (tag) {
_tagsUpdated.fire({});
}
}
std::optional<Reaction> Reactions::resolveById(const ReactionId &id) {

View file

@ -56,6 +56,12 @@ struct PossibleItemReactions {
[[nodiscard]] PossibleItemReactionsRef LookupPossibleReactions(
not_null<HistoryItem*> item);
struct MyTagInfo {
ReactionId id;
QString title;
int count = 0;
};
class Reactions final : private CustomEmojiManager::Listener {
public:
explicit Reactions(not_null<Session*> owner);
@ -70,14 +76,20 @@ public:
void refreshRecent();
void refreshRecentDelayed();
void refreshDefault();
void refreshMyTags();
void refreshMyTagsDelayed();
void refreshTags();
enum class Type {
Active,
Recent,
Top,
All,
MyTags,
Tags,
};
[[nodiscard]] const std::vector<Reaction> &list(Type type) const;
[[nodiscard]] const std::vector<MyTagInfo> &myTagsInfo() const;
[[nodiscard]] ReactionId favoriteId() const;
[[nodiscard]] const Reaction *favorite() const;
void setFavorite(const ReactionId &id);
@ -88,6 +100,8 @@ public:
[[nodiscard]] rpl::producer<> recentUpdates() const;
[[nodiscard]] rpl::producer<> defaultUpdates() const;
[[nodiscard]] rpl::producer<> favoriteUpdates() const;
[[nodiscard]] rpl::producer<> myTagsUpdates() const;
[[nodiscard]] rpl::producer<> tagsUpdates() const;
enum class ImageSize {
BottomInfo,
@ -130,14 +144,20 @@ private:
void requestRecent();
void requestDefault();
void requestGeneric();
void requestMyTags();
void requestTags();
void updateTop(const MTPDmessages_reactions &data);
void updateRecent(const MTPDmessages_reactions &data);
void updateDefault(const MTPDmessages_availableReactions &data);
void updateGeneric(const MTPDmessages_stickerSet &data);
void updateMyTags(const MTPDmessages_savedReactionTags &data);
void updateTags(const MTPDmessages_reactions &data);
void recentUpdated();
void defaultUpdated();
void myTagsUpdated();
void tagsUpdated();
[[nodiscard]] std::optional<Reaction> resolveById(const ReactionId &id);
[[nodiscard]] std::vector<Reaction> resolveByIds(
@ -167,6 +187,13 @@ private:
std::vector<Reaction> _recent;
std::vector<ReactionId> _recentIds;
base::flat_set<ReactionId> _unresolvedRecent;
std::vector<Reaction> _myTags;
std::vector<ReactionId> _myTagsIds;
std::vector<MyTagInfo> _myTagsInfo;
base::flat_set<ReactionId> _unresolvedMyTags;
std::vector<Reaction> _tags;
std::vector<ReactionId> _tagsIds;
base::flat_set<ReactionId> _unresolvedTags;
std::vector<Reaction> _top;
std::vector<ReactionId> _topIds;
base::flat_set<ReactionId> _unresolvedTop;
@ -184,6 +211,8 @@ private:
rpl::event_stream<> _recentUpdated;
rpl::event_stream<> _defaultUpdated;
rpl::event_stream<> _favoriteUpdated;
rpl::event_stream<> _myTagsUpdated;
rpl::event_stream<> _tagsUpdated;
// We need &i->second stay valid while inserting new items.
// So we use std::map instead of base::flat_map here.
@ -203,6 +232,13 @@ private:
mtpRequestId _genericRequestId = 0;
mtpRequestId _myTagsRequestId = 0;
bool _myTagsRequestScheduled = false;
uint64 _myTagsHash = 0;
mtpRequestId _tagsRequestId = 0;
uint64 _tagsHash = 0;
base::flat_map<ReactionId, ImageSet> _images;
rpl::lifetime _imagesLoadLifetime;
bool _waitingForList = false;

View file

@ -895,7 +895,9 @@ void SetupManagerList(
reactions.topUpdates(),
reactions.recentUpdates(),
reactions.defaultUpdates(),
reactions.favoriteUpdates()
reactions.favoriteUpdates(),
reactions.myTagsUpdates(),
reactions.tagsUpdates()
) | rpl::start_with_next([=] {
if (!state->timer.isActive()) {
state->timer.callOnce(kRefreshListDelay);

View file

@ -529,14 +529,22 @@ bool ShowReactPremiumError(
|| ranges::contains(item->chosenReactions(), id)
|| item->history()->peer->isBroadcast()) {
return false;
}
const auto &list = controller->session().data().reactions().list(
Data::Reactions::Type::Active);
const auto i = ranges::find(list, id, &Data::Reaction::id);
if (i == end(list) || !i->premium) {
if (!id.custom()) {
} else if (item->reactionsAreTags()) {
const auto &list = controller->session().data().reactions().list(
Data::Reactions::Type::Tags);
const auto i = ranges::find(list, id, &Data::Reaction::id);
if (i != end(list)) {
return false;
}
} else {
const auto &list = controller->session().data().reactions().list(
Data::Reactions::Type::Active);
const auto i = ranges::find(list, id, &Data::Reaction::id);
if (i == end(list) || !i->premium) {
if (!id.custom()) {
return false;
}
}
}
ShowPremiumPreviewBox(controller, PremiumPreview::InfiniteReactions);
return true;