| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378 |
- /*
- 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 "api/api_global_privacy.h"
- #include "chat_helpers/stickers_lottie.h"
- #include "core/application.h"
- #include "history/history.h"
- #include "history/history_item.h"
- #include "history/history_item_components.h"
- #include "main/main_session.h"
- #include "main/main_app_config.h"
- #include "main/session/send_as_peers.h"
- #include "data/components/credits.h"
- #include "data/data_channel.h"
- #include "data/data_user.h"
- #include "data/data_session.h"
- #include "data/data_histories.h"
- #include "data/data_changes.h"
- #include "data/data_document.h"
- #include "data/data_document_media.h"
- #include "data/data_file_origin.h"
- #include "data/data_peer_values.h"
- #include "data/data_saved_sublist.h"
- #include "data/stickers/data_custom_emoji.h"
- #include "storage/localimageloader.h"
- #include "ui/image/image_location_factory.h"
- #include "ui/animated_icon.h"
- #include "mtproto/mtproto_config.h"
- #include "base/timer_rpl.h"
- #include "base/call_delayed.h"
- #include "base/unixtime.h"
- #include "apiwrap.h"
- #include "styles/style_chat.h"
- #include "base/random.h"
- namespace Data {
- namespace {
- constexpr auto kRefreshFullListEach = 60 * 60 * crl::time(1000);
- 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;
- constexpr auto kPaidAccumulatePeriod = 5 * crl::time(1000) + 500;
- [[nodiscard]] QString ReactionIdToLog(const ReactionId &id) {
- if (const auto custom = id.custom()) {
- return "custom:" + QString::number(custom);
- }
- 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;
- }
- [[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 } },
- .title = "Custom reaction",
- .appearAnimation = document,
- .selectAnimation = document,
- .centerIcon = document,
- .active = true,
- };
- }
- [[nodiscard]] int SentReactionsLimit(not_null<HistoryItem*> item) {
- const auto session = &item->history()->session();
- const auto config = &session->appConfig();
- return session->premium()
- ? config->get<int>("reactions_user_max_premium", 3)
- : config->get<int>("reactions_user_max_default", 1);
- }
- [[nodiscard]] bool IsMyRecent(
- const MTPDmessagePeerReaction &data,
- const ReactionId &id,
- not_null<PeerData*> peer,
- const base::flat_map<
- ReactionId,
- std::vector<RecentReaction>> &recent,
- bool min) {
- if (peer->isSelf()) {
- return true;
- } else if (!min) {
- return data.is_my();
- }
- const auto j = recent.find(id);
- if (j == end(recent)) {
- return false;
- }
- const auto k = ranges::find(
- j->second,
- peer,
- &RecentReaction::peer);
- return (k != end(j->second)) && k->my;
- }
- [[nodiscard]] bool IsMyTop(
- const MTPDmessageReactor &data,
- PeerData *peer,
- const std::vector<MessageReactionsTopPaid> &top,
- bool min) {
- if (peer && peer->isSelf()) {
- return true;
- } else if (!min) {
- return data.is_my();
- }
- const auto i = ranges::find(top, peer, &MessageReactionsTopPaid::peer);
- return (i != end(top)) && i->my;
- }
- [[nodiscard]] std::optional<PeerId> MaybeShownPeer(
- uint32 privacySet,
- PeerId shownPeer) {
- return privacySet ? shownPeer : std::optional<PeerId>();
- }
- [[nodiscard]] MTPPaidReactionPrivacy PaidReactionShownPeerToTL(
- not_null<Main::Session*> session,
- std::optional<PeerId> shownPeer) {
- return !shownPeer
- ? MTPPaidReactionPrivacy()
- : !*shownPeer
- ? MTP_paidReactionPrivacyAnonymous()
- : (*shownPeer == session->userPeerId())
- ? MTP_paidReactionPrivacyDefault()
- : MTP_paidReactionPrivacyPeer(
- session->data().peer(*shownPeer)->input);
- }
- } // namespace
- PossibleItemReactionsRef LookupPossibleReactions(
- not_null<HistoryItem*> item,
- bool paidInFront) {
- if (!item->canReact()) {
- return {};
- }
- auto result = PossibleItemReactionsRef();
- auto peer = item->history()->peer;
- if (item->isDiscussionPost()) {
- if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
- if (forwarded->savedFromPeer) {
- peer = forwarded->savedFromPeer;
- }
- }
- }
- const auto session = &peer->session();
- if (const auto channel = peer->asChannel()) {
- if ((!channel->amCreator())
- && (channel->adminRights() & ChatAdminRight::Anonymous)
- && (session->sendAsPeers().resolveChosen(channel) == channel)) {
- return {};
- }
- }
- const auto reactions = &session->data().reactions();
- 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 &allowed = PeerAllowedReactions(peer);
- const auto limit = UniqueReactionsLimit(peer);
- const auto premiumPossible = session->premiumPossible();
- const auto limited = (all.size() >= limit) && [&] {
- const auto my = item->chosenReactions();
- if (my.empty()) {
- return true;
- }
- return true; // #TODO reactions
- }();
- auto added = base::flat_set<ReactionId>();
- const auto add = [&](auto predicate) {
- auto &&all = ranges::views::concat(top, recent, full);
- for (const auto &reaction : all) {
- if (predicate(reaction)) {
- if (added.emplace(reaction.id).second) {
- result.recent.push_back(&reaction);
- }
- }
- }
- };
- reactions->clearTemporary();
- 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;
- result.tags = true;
- } else if (limited) {
- result.recent.reserve((allowed.paidEnabled ? 1 : 0) + all.size());
- add([&](const Reaction &reaction) {
- return ranges::contains(all, reaction.id, &MessageReaction::id);
- });
- for (const auto &reaction : all) {
- const auto id = reaction.id;
- if (added.emplace(id).second) {
- if (const auto temp = reactions->lookupTemporary(id)) {
- result.recent.push_back(temp);
- }
- }
- }
- if (allowed.paidEnabled
- && !added.contains(Data::ReactionId::Paid())) {
- result.recent.push_back(reactions->lookupPaid());
- }
- } else {
- result.recent.reserve((allowed.paidEnabled ? 1 : 0)
- + ((allowed.type == AllowedReactionsType::Some)
- ? allowed.some.size()
- : full.size()));
- if (allowed.paidEnabled) {
- result.recent.push_back(reactions->lookupPaid());
- }
- add([&](const Reaction &reaction) {
- const auto id = reaction.id;
- if (id.custom() && !premiumPossible) {
- return false;
- } else if ((allowed.type == AllowedReactionsType::Some)
- && !ranges::contains(allowed.some, id)) {
- return false;
- } else if (id.custom()
- && allowed.type == AllowedReactionsType::Default) {
- return false;
- }
- return true;
- });
- if (allowed.type == AllowedReactionsType::Some) {
- for (const auto &id : allowed.some) {
- if (!added.contains(id)) {
- if (const auto temp = reactions->lookupTemporary(id)) {
- result.recent.push_back(temp);
- }
- }
- }
- }
- result.customAllowed = (allowed.type == AllowedReactionsType::All)
- && premiumPossible;
- const auto favoriteId = reactions->favoriteId();
- if (favoriteId.custom()
- && result.customAllowed
- && !ranges::contains(result.recent, favoriteId, &Reaction::id)) {
- if (const auto temp = reactions->lookupTemporary(favoriteId)) {
- result.recent.insert(begin(result.recent), temp);
- }
- }
- }
- if (!item->reactionsAreTags()) {
- const auto toFront = [&](Data::ReactionId id) {
- const auto i = ranges::find(result.recent, id, &Reaction::id);
- if (i != end(result.recent) && i != begin(result.recent)) {
- std::rotate(begin(result.recent), i, i + 1);
- }
- };
- toFront(reactions->favoriteId());
- if (paidInFront) {
- toFront(Data::ReactionId::Paid());
- }
- }
- return result;
- }
- PossibleItemReactions::PossibleItemReactions(
- const PossibleItemReactionsRef &other)
- : recent(other.recent | ranges::views::transform([](const auto &value) {
- return *value;
- }) | ranges::to_vector)
- , stickers(other.stickers | ranges::views::transform([](const auto &value) {
- return *value;
- }) | ranges::to_vector)
- , customAllowed(other.customAllowed)
- , tags(other.tags){
- }
- Reactions::Reactions(not_null<Session*> owner)
- : _owner(owner)
- , _topRefreshTimer([=] { refreshTop(); })
- , _repaintTimer([=] { repaintCollected(); })
- , _sendPaidTimer([=] { sendPaid(); }) {
- refreshDefault();
- _myTags.emplace(nullptr);
- base::timer_each(
- kRefreshFullListEach
- ) | rpl::start_with_next([=] {
- refreshDefault();
- requestEffects();
- }, _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);
- _sendPaidItems.remove(item);
- if (const auto i = _sendingPaid.find(item)
- ; i != end(_sendingPaid)) {
- _sendingPaid.erase(i);
- _owner->session().credits().invalidate();
- crl::on_main(&_owner->session(), [=] {
- sendPaid();
- });
- }
- }, _lifetime);
- crl::on_main(&owner->session(), [=] {
- // applyFavorite accesses not yet constructed parts of session.
- rpl::single(rpl::empty) | rpl::then(
- _owner->session().mtp().config().updates()
- ) | rpl::map([=] {
- const auto &config = _owner->session().mtp().configValues();
- return config.reactionDefaultCustom
- ? ReactionId{ DocumentId(config.reactionDefaultCustom) }
- : ReactionId{ config.reactionDefaultEmoji };
- }) | rpl::filter([=](const ReactionId &id) {
- return !_saveFaveRequestId;
- }) | rpl::start_with_next([=](ReactionId &&id) {
- applyFavorite(id);
- }, _lifetime);
- });
- }
- Reactions::~Reactions() = default;
- Main::Session &Reactions::session() const {
- return _owner->session();
- }
- void Reactions::refreshTop() {
- 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();
- }
- void Reactions::refreshMyTags(SavedSublist *sublist) {
- requestMyTags(sublist);
- }
- void Reactions::refreshMyTagsDelayed() {
- auto &my = _myTags[nullptr];
- if (my.requestId || my.requestScheduled) {
- return;
- }
- my.requestScheduled = true;
- base::call_delayed(kMyTagsRequestTimeout, &_owner->session(), [=] {
- if (_myTags[nullptr].requestScheduled) {
- requestMyTags();
- }
- });
- }
- void Reactions::refreshTags() {
- requestTags();
- }
- void Reactions::refreshEffects() {
- if (_effects.empty()) {
- requestEffects();
- }
- }
- 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.find((SavedSublist*)nullptr)->second.tags;
- case Type::Tags: return _tags;
- case Type::Effects: return _effects;
- }
- Unexpected("Type in Reactions::list.");
- }
- const std::vector<MyTagInfo> &Reactions::myTagsInfo() const {
- return _myTags.find((SavedSublist*)nullptr)->second.info;
- }
- const QString &Reactions::myTagTitle(const ReactionId &id) const {
- const auto i = _myTags.find((SavedSublist*)nullptr);
- if (i != end(_myTags)) {
- const auto j = ranges::find(i->second.info, id, &MyTagInfo::id);
- if (j != end(i->second.info)) {
- return j->title;
- }
- }
- static const auto kEmpty = QString();
- return kEmpty;
- }
- ReactionId Reactions::favoriteId() const {
- return _favoriteId;
- }
- const Reaction *Reactions::favorite() const {
- return _favorite ? &*_favorite : nullptr;
- }
- void Reactions::setFavorite(const ReactionId &id) {
- const auto api = &_owner->session().api();
- if (_saveFaveRequestId) {
- api->request(_saveFaveRequestId).cancel();
- }
- _saveFaveRequestId = api->request(MTPmessages_SetDefaultReaction(
- ReactionToMTP(id)
- )).done([=] {
- _saveFaveRequestId = 0;
- }).fail([=] {
- _saveFaveRequestId = 0;
- }).send();
- applyFavorite(id);
- }
- void Reactions::incrementMyTag(const ReactionId &id, SavedSublist *sublist) {
- if (sublist) {
- incrementMyTag(id, nullptr);
- }
- auto &my = _myTags[sublist];
- auto i = ranges::find(my.info, id, &MyTagInfo::id);
- if (i == end(my.info)) {
- my.info.push_back({ .id = id, .count = 0 });
- i = end(my.info) - 1;
- }
- ++i->count;
- while (i != begin(my.info)) {
- auto j = i - 1;
- if (j->count >= i->count) {
- break;
- }
- std::swap(*i, *j);
- i = j;
- }
- scheduleMyTagsUpdate(sublist);
- }
- void Reactions::decrementMyTag(const ReactionId &id, SavedSublist *sublist) {
- if (sublist) {
- decrementMyTag(id, nullptr);
- }
- auto &my = _myTags[sublist];
- auto i = ranges::find(my.info, id, &MyTagInfo::id);
- if (i != end(my.info) && i->count > 0) {
- --i->count;
- while (i + 1 != end(my.info)) {
- auto j = i + 1;
- if (j->count <= i->count) {
- break;
- }
- std::swap(*i, *j);
- i = j;
- }
- }
- scheduleMyTagsUpdate(sublist);
- }
- void Reactions::renameTag(const ReactionId &id, const QString &name) {
- auto changed = false;
- for (auto &[sublist, my] : _myTags) {
- auto i = ranges::find(my.info, id, &MyTagInfo::id);
- if (i == end(my.info) || i->title == name) {
- continue;
- }
- i->title = name;
- changed = true;
- scheduleMyTagsUpdate(sublist);
- }
- if (!changed) {
- return;
- }
- _myTagRenamed.fire_copy(id);
- using Flag = MTPmessages_UpdateSavedReactionTag::Flag;
- _owner->session().api().request(MTPmessages_UpdateSavedReactionTag(
- MTP_flags(name.isEmpty() ? Flag(0) : Flag::f_title),
- ReactionToMTP(id),
- MTP_string(name)
- )).send();
- }
- void Reactions::scheduleMyTagsUpdate(SavedSublist *sublist) {
- auto &my = _myTags[sublist];
- my.updateScheduled = true;
- crl::on_main(&session(), [=] {
- auto &my = _myTags[sublist];
- if (!my.updateScheduled) {
- return;
- }
- my.updateScheduled = false;
- my.tags = resolveByInfos(my.info, _unresolvedMyTags, sublist);
- _myTagsUpdated.fire_copy(sublist);
- });
- }
- DocumentData *Reactions::chooseGenericAnimation(
- not_null<DocumentData*> custom) const {
- const auto sticker = custom->sticker();
- const auto i = sticker
- ? ranges::find(
- _available,
- ::Data::ReactionId{ { sticker->alt } },
- &::Data::Reaction::id)
- : end(_available);
- if (i != end(_available) && i->aroundAnimation) {
- const auto view = i->aroundAnimation->createMediaView();
- view->checkStickerLarge();
- if (view->loaded()) {
- return i->aroundAnimation;
- }
- }
- return randomLoadedFrom(_genericAnimations);
- }
- void Reactions::fillPaidReactionAnimations() const {
- const auto generate = [&](int index) {
- const auto session = &_owner->session();
- const auto name = u"star_reaction_effect%1"_q.arg(index + 1);
- return ChatHelpers::GenerateLocalTgsSticker(session, name);
- };
- const auto kCount = 3;
- for (auto i = 0; i != kCount; ++i) {
- const auto document = generate(i);
- _paidReactionAnimations.push_back(document);
- _paidReactionCache.emplace(
- document,
- document->createMediaView());
- }
- _paidReactionCache.front().second->checkStickerLarge();
- }
- DocumentData *Reactions::choosePaidReactionAnimation() const {
- if (_paidReactionAnimations.empty()) {
- fillPaidReactionAnimations();
- }
- return randomLoadedFrom(_paidReactionAnimations);
- }
- DocumentData *Reactions::randomLoadedFrom(
- std::vector<not_null<DocumentData*>> list) const {
- if (list.empty()) {
- return nullptr;
- }
- ranges::shuffle(list);
- const auto first = list.front();
- const auto view = first->createMediaView();
- view->checkStickerLarge();
- if (view->loaded()) {
- return first;
- }
- const auto k = ranges::find_if(list, [&](not_null<DocumentData*> value) {
- return value->createMediaView()->loaded();
- });
- return (k != end(list)) ? (*k) : first;
- }
- 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::topUpdates() const {
- 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();
- }
- rpl::producer<> Reactions::myTagsUpdates() const {
- return _myTagsUpdated.events(
- ) | rpl::filter(
- !rpl::mappers::_1
- ) | rpl::to_empty;
- }
- rpl::producer<> Reactions::tagsUpdates() const {
- return _tagsUpdated.events();
- }
- rpl::producer<ReactionId> Reactions::myTagRenamed() const {
- return _myTagRenamed.events();
- }
- rpl::producer<> Reactions::effectsUpdates() const {
- return _effectsUpdated.events();
- }
- void Reactions::preloadReactionImageFor(const ReactionId &emoji) {
- if (emoji.paid() || !emoji.emoji().isEmpty()) {
- preloadImageFor(emoji);
- }
- }
- void Reactions::preloadEffectImageFor(EffectId id) {
- if (id != kFakeEffectId) {
- preloadImageFor({ DocumentId(id) });
- }
- }
- void Reactions::preloadImageFor(const ReactionId &id) {
- if (_images.contains(id)) {
- return;
- }
- auto &set = _images.emplace(id).first->second;
- set.effect = (id.custom() != 0);
- if (id.paid()) {
- loadImage(set, lookupPaid()->centerIcon, true);
- return;
- }
- auto &list = set.effect ? _effects : _available;
- const auto i = ranges::find(list, id, &Reaction::id);
- const auto document = (i == end(list))
- ? nullptr
- : i->centerIcon
- ? i->centerIcon
- : i->selectAnimation.get();
- if (document || (set.effect && i != end(list))) {
- if (!set.effect || i->centerIcon) {
- loadImage(set, document, !i->centerIcon);
- } else {
- generateImage(set, i->title);
- }
- if (set.effect) {
- preloadEffect(*i);
- }
- } else if (set.effect && !_waitingForEffects) {
- _waitingForEffects = true;
- refreshEffects();
- } else if (!set.effect && !_waitingForReactions) {
- _waitingForReactions = true;
- refreshDefault();
- }
- }
- void Reactions::preloadEffect(const Reaction &effect) {
- if (effect.aroundAnimation) {
- effect.aroundAnimation->createMediaView()->checkStickerLarge();
- } else {
- const auto premium = effect.selectAnimation;
- premium->loadVideoThumbnail(premium->stickerSetOrigin());
- }
- }
- void Reactions::preloadAnimationsFor(const ReactionId &id) {
- const auto preload = [&](DocumentData *document) {
- const auto view = document
- ? document->activeMediaView()
- : nullptr;
- if (view) {
- view->checkStickerLarge();
- }
- };
- if (id.paid()) {
- const auto fake = lookupPaid();
- preload(fake->centerIcon);
- preload(fake->aroundAnimation);
- return;
- }
- const auto custom = id.custom();
- const auto document = custom ? _owner->document(custom).get() : nullptr;
- const auto customSticker = document ? document->sticker() : nullptr;
- const auto findId = custom
- ? ReactionId{ { customSticker ? customSticker->alt : QString() } }
- : id;
- const auto i = ranges::find(_available, findId, &Reaction::id);
- if (i == end(_available)) {
- return;
- }
- if (!custom) {
- preload(i->centerIcon);
- }
- preload(i->aroundAnimation);
- }
- QImage Reactions::resolveReactionImageFor(const ReactionId &emoji) {
- Expects(!emoji.custom());
- return resolveImageFor(emoji);
- }
- QImage Reactions::resolveEffectImageFor(EffectId id) {
- return (id == kFakeEffectId)
- ? QImage()
- : resolveImageFor({ DocumentId(id) });
- }
- QImage Reactions::resolveImageFor(const ReactionId &id) {
- auto i = _images.find(id);
- if (i == end(_images)) {
- preloadImageFor(id);
- i = _images.find(id);
- Assert(i != end(_images));
- }
- auto &set = i->second;
- set.effect = (id.custom() != 0);
- const auto resolve = [&](QImage &image, int size) {
- const auto factor = style::DevicePixelRatio();
- const auto frameSize = set.fromSelectAnimation
- ? (size / 2)
- : size;
- // Must not be colored to text.
- image = set.icon->frame(QColor()).scaled(
- frameSize * factor,
- frameSize * factor,
- Qt::IgnoreAspectRatio,
- Qt::SmoothTransformation);
- if (set.fromSelectAnimation) {
- auto result = QImage(
- size * factor,
- size * factor,
- QImage::Format_ARGB32_Premultiplied);
- result.fill(Qt::transparent);
- auto p = QPainter(&result);
- p.drawImage(
- (size - frameSize) * factor / 2,
- (size - frameSize) * factor / 2,
- image);
- p.end();
- std::swap(result, image);
- }
- image.setDevicePixelRatio(factor);
- };
- if (set.image.isNull() && set.icon) {
- resolve(
- set.image,
- set.effect ? st::effectInfoImage : st::reactionInlineImage);
- crl::async([icon = std::move(set.icon)]{});
- }
- return set.image;
- }
- void Reactions::resolveReactionImages() {
- for (auto &[id, set] : _images) {
- if (set.effect || !set.image.isNull() || set.icon || set.media) {
- continue;
- }
- const auto i = ranges::find(_available, id, &Reaction::id);
- const auto document = (i == end(_available))
- ? nullptr
- : i->centerIcon
- ? i->centerIcon
- : i->selectAnimation.get();
- if (document) {
- loadImage(set, document, !i->centerIcon);
- } else {
- LOG(("API Error: Reaction '%1' not found!"
- ).arg(ReactionIdToLog(id)));
- }
- }
- }
- void Reactions::resolveEffectImages() {
- for (auto &[id, set] : _images) {
- if (!set.effect || !set.image.isNull() || set.icon || set.media) {
- continue;
- }
- const auto i = ranges::find(_effects, id, &Reaction::id);
- const auto document = (i == end(_effects))
- ? nullptr
- : i->centerIcon
- ? i->centerIcon
- : nullptr;
- if (document) {
- loadImage(set, document, false);
- } else if (i != end(_effects)) {
- generateImage(set, i->title);
- } else {
- LOG(("API Error: Effect '%1' not found!"
- ).arg(ReactionIdToLog(id)));
- }
- if (i != end(_effects)) {
- preloadEffect(*i);
- }
- }
- }
- void Reactions::loadImage(
- ImageSet &set,
- not_null<DocumentData*> document,
- bool fromSelectAnimation) {
- if (!set.image.isNull() || set.icon) {
- return;
- } else if (!set.media) {
- if (!set.effect) {
- set.fromSelectAnimation = fromSelectAnimation;
- }
- set.media = document->createMediaView();
- set.media->checkStickerLarge();
- }
- if (set.media->loaded()) {
- setAnimatedIcon(set);
- } else if (!_imagesLoadLifetime) {
- document->session().downloaderTaskFinished(
- ) | rpl::start_with_next([=] {
- downloadTaskFinished();
- }, _imagesLoadLifetime);
- }
- }
- void Reactions::generateImage(ImageSet &set, const QString &emoji) {
- Expects(set.effect);
- const auto e = Ui::Emoji::Find(emoji);
- Assert(e != nullptr);
- const auto large = Ui::Emoji::GetSizeLarge();
- const auto factor = style::DevicePixelRatio();
- auto image = QImage(large, large, QImage::Format_ARGB32_Premultiplied);
- image.setDevicePixelRatio(factor);
- image.fill(Qt::transparent);
- {
- QPainter p(&image);
- Ui::Emoji::Draw(p, e, large, 0, 0);
- }
- const auto size = st::effectInfoImage;
- set.image = image.scaled(size * factor, size * factor);
- set.image.setDevicePixelRatio(factor);
- }
- void Reactions::setAnimatedIcon(ImageSet &set) {
- const auto size = style::ConvertScale(kSizeForDownscale);
- set.icon = Ui::MakeAnimatedIcon({
- .generator = DocumentIconFrameGenerator(set.media),
- .sizeOverride = QSize(size, size),
- .colorized = set.media->owner()->emojiUsesTextColor(),
- });
- set.media = nullptr;
- }
- void Reactions::downloadTaskFinished() {
- auto hasOne = false;
- for (auto &[emoji, set] : _images) {
- if (!set.media) {
- continue;
- } else if (set.media->loaded()) {
- setAnimatedIcon(set);
- } else {
- hasOne = true;
- }
- }
- if (!hasOne) {
- _imagesLoadLifetime.destroy();
- }
- }
- void Reactions::requestTop() {
- if (_topRequestId) {
- return;
- }
- auto &api = _owner->session().api();
- _topRefreshTimer.cancel();
- _topRequestId = api.request(MTPmessages_GetTopReactions(
- MTP_int(kTopReactionsLimit),
- MTP_long(_topHash)
- )).done([=](const MTPmessages_Reactions &result) {
- _topRequestId = 0;
- result.match([&](const MTPDmessages_reactions &data) {
- updateTop(data);
- }, [](const MTPDmessages_reactionsNotModified&) {
- });
- }).fail([=] {
- _topRequestId = 0;
- _topHash = 0;
- }).send();
- }
- void Reactions::requestRecent() {
- 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::requestGeneric() {
- if (_genericRequestId) {
- return;
- }
- auto &api = _owner->session().api();
- _genericRequestId = api.request(MTPmessages_GetStickerSet(
- MTP_inputStickerSetEmojiGenericAnimations(),
- MTP_int(0) // hash
- )).done([=](const MTPmessages_StickerSet &result) {
- _genericRequestId = 0;
- result.match([&](const MTPDmessages_stickerSet &data) {
- updateGeneric(data);
- }, [](const MTPDmessages_stickerSetNotModified &) {
- LOG(("API Error: Unexpected messages.stickerSetNotModified."));
- });
- }).fail([=] {
- _genericRequestId = 0;
- }).send();
- }
- void Reactions::requestMyTags(SavedSublist *sublist) {
- auto &my = _myTags[sublist];
- if (my.requestId) {
- return;
- }
- auto &api = _owner->session().api();
- my.requestScheduled = false;
- using Flag = MTPmessages_GetSavedReactionTags::Flag;
- my.requestId = api.request(MTPmessages_GetSavedReactionTags(
- MTP_flags(sublist ? Flag::f_peer : Flag()),
- (sublist ? sublist->peer()->input : MTP_inputPeerEmpty()),
- MTP_long(my.hash)
- )).done([=](const MTPmessages_SavedReactionTags &result) {
- auto &my = _myTags[sublist];
- my.requestId = 0;
- result.match([&](const MTPDmessages_savedReactionTags &data) {
- updateMyTags(sublist, data);
- }, [](const MTPDmessages_savedReactionTagsNotModified&) {
- });
- }).fail([=] {
- auto &my = _myTags[sublist];
- my.requestId = 0;
- my.hash = 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::requestEffects() {
- if (_effectsRequestId) {
- return;
- }
- auto &api = _owner->session().api();
- _effectsRequestId = api.request(MTPmessages_GetAvailableEffects(
- MTP_int(_effectsHash)
- )).done([=](const MTPmessages_AvailableEffects &result) {
- _effectsRequestId = 0;
- result.match([&](const MTPDmessages_availableEffects &data) {
- updateEffects(data);
- }, [&](const MTPDmessages_availableEffectsNotModified &) {
- });
- }).fail([=] {
- _effectsRequestId = 0;
- _effectsHash = 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 oldCache = base::take(_iconsCache);
- const auto toCache = [&](DocumentData *document) {
- if (document) {
- _iconsCache.emplace(document, document->createMediaView());
- }
- };
- _active.clear();
- _available.clear();
- _active.reserve(list.size());
- _available.reserve(list.size());
- _iconsCache.reserve(list.size() * 4);
- for (const auto &reaction : list) {
- if (const auto parsed = parse(reaction)) {
- _available.push_back(*parsed);
- if (parsed->active) {
- _active.push_back(*parsed);
- toCache(parsed->appearAnimation);
- toCache(parsed->selectAnimation);
- toCache(parsed->centerIcon);
- toCache(parsed->aroundAnimation);
- }
- }
- }
- if (_waitingForReactions) {
- _waitingForReactions = false;
- resolveReactionImages();
- }
- defaultUpdated();
- }
- void Reactions::updateGeneric(const MTPDmessages_stickerSet &data) {
- const auto oldCache = base::take(_genericCache);
- const auto toCache = [&](not_null<DocumentData*> document) {
- if (document->sticker()) {
- _genericAnimations.push_back(document);
- _genericCache.emplace(document, document->createMediaView());
- }
- };
- const auto &list = data.vdocuments().v;
- _genericAnimations.clear();
- _genericAnimations.reserve(list.size());
- _genericCache.reserve(list.size());
- for (const auto &sticker : data.vdocuments().v) {
- toCache(_owner->processDocument(sticker));
- }
- if (!_genericCache.empty()) {
- _genericCache.front().second->checkStickerLarge();
- }
- }
- void Reactions::updateMyTags(
- SavedSublist *sublist,
- const MTPDmessages_savedReactionTags &data) {
- auto &my = _myTags[sublist];
- my.hash = data.vhash().v;
- auto list = ListFromMTP(data);
- auto renamed = base::flat_set<ReactionId>();
- if (!sublist) {
- for (const auto &info : list) {
- const auto j = ranges::find(my.info, info.id, &MyTagInfo::id);
- const auto was = (j != end(my.info)) ? j->title : QString();
- if (info.title != was) {
- renamed.emplace(info.id);
- }
- }
- }
- my.info = std::move(list);
- my.tags = resolveByInfos(my.info, _unresolvedMyTags, sublist);
- _myTagsUpdated.fire_copy(sublist);
- for (const auto &id : renamed) {
- _myTagRenamed.fire_copy(id);
- }
- }
- void Reactions::updateTags(const MTPDmessages_reactions &data) {
- _tagsHash = data.vhash().v;
- _tagsIds = ListFromMTP(data);
- _tags = resolveByIds(_tagsIds, _unresolvedTags);
- _tagsUpdated.fire({});
- }
- void Reactions::updateEffects(const MTPDmessages_availableEffects &data) {
- _effectsHash = data.vhash().v;
- const auto &list = data.veffects().v;
- const auto toCache = [&](DocumentData *document) {
- if (document) {
- _iconsCache.emplace(document, document->createMediaView());
- }
- };
- for (const auto &document : data.vdocuments().v) {
- toCache(_owner->processDocument(document));
- }
- _effects.clear();
- _effects.reserve(list.size());
- for (const auto &effect : list) {
- if (const auto parsed = parse(effect)) {
- _effects.push_back(*parsed);
- }
- }
- if (_waitingForEffects) {
- _waitingForEffects = false;
- resolveEffectImages();
- }
- effectsUpdated();
- }
- void Reactions::recentUpdated() {
- _topRefreshTimer.callOnce(kTopRequestDelay);
- _recentUpdated.fire({});
- }
- void Reactions::defaultUpdated() {
- refreshTop();
- refreshRecent();
- if (_genericAnimations.empty()) {
- requestGeneric();
- }
- refreshMyTags();
- refreshTags();
- refreshEffects();
- _defaultUpdated.fire({});
- }
- void Reactions::myTagsUpdated() {
- if (_genericAnimations.empty()) {
- requestGeneric();
- }
- _myTagsUpdated.fire({});
- }
- void Reactions::tagsUpdated() {
- if (_genericAnimations.empty()) {
- requestGeneric();
- }
- _tagsUpdated.fire({});
- }
- void Reactions::effectsUpdated() {
- _effectsUpdated.fire({});
- }
- not_null<CustomEmojiManager::Listener*> Reactions::resolveListener() {
- return static_cast<CustomEmojiManager::Listener*>(this);
- }
- void Reactions::customEmojiResolveDone(not_null<DocumentData*> document) {
- if (!document->sticker()) {
- return;
- }
- 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));
- const auto k = _unresolvedMyTags.find(id);
- const auto myTagSublists = (k != end(_unresolvedMyTags))
- ? base::take(k->second)
- : base::flat_set<SavedSublist*>();
- const auto l = _unresolvedTags.find(id);
- const auto tag = (l != end(_unresolvedTags));
- 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 (!myTagSublists.empty()) {
- _unresolvedMyTags.erase(k);
- for (const auto &sublist : myTagSublists) {
- auto &my = _myTags[sublist];
- my.tags = resolveByInfos(my.info, _unresolvedMyTags, sublist);
- }
- }
- if (tag) {
- _unresolvedTags.erase(l);
- _tags = resolveByIds(_tagsIds, _unresolvedTags);
- }
- if (favorite) {
- _favoriteUpdated.fire({});
- }
- if (top) {
- _topUpdated.fire({});
- }
- if (recent) {
- _recentUpdated.fire({});
- }
- for (const auto &sublist : myTagSublists) {
- _myTagsUpdated.fire_copy(sublist);
- }
- if (tag) {
- _tagsUpdated.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 CustomReaction(document);
- }
- }
- 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;
- }
- std::optional<Reaction> Reactions::resolveByInfo(
- const MyTagInfo &info,
- SavedSublist *sublist) {
- const auto withInfo = [&](Reaction reaction) {
- reaction.count = info.count;
- reaction.title = sublist ? myTagTitle(reaction.id) : info.title;
- return reaction;
- };
- if (const auto emoji = info.id.emoji(); !emoji.isEmpty()) {
- const auto i = ranges::find(_available, info.id, &Reaction::id);
- if (i != end(_available)) {
- return withInfo(*i);
- }
- } else if (const auto customId = info.id.custom()) {
- const auto document = _owner->document(customId);
- if (document->sticker()) {
- return withInfo(CustomReaction(document));
- }
- }
- return {};
- }
- std::vector<Reaction> Reactions::resolveByInfos(
- const std::vector<MyTagInfo> &infos,
- base::flat_map<
- ReactionId,
- base::flat_set<SavedSublist*>> &unresolved,
- SavedSublist *sublist) {
- auto result = std::vector<Reaction>();
- result.reserve(infos.size());
- for (const auto &tag : infos) {
- if (auto resolved = resolveByInfo(tag, sublist)) {
- result.push_back(*resolved);
- } else if (const auto i = unresolved.find(tag.id)
- ; i != end(unresolved)) {
- i->second.emplace(sublist);
- } else {
- unresolved[tag.id].emplace(sublist);
- resolve(tag.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) {
- const auto &data = entry.data();
- const auto emoji = qs(data.vreaction());
- const auto known = (Ui::Emoji::Find(emoji) != nullptr);
- if (!known) {
- LOG(("API Error: Unknown emoji in reactions: %1").arg(emoji));
- return std::nullopt;
- }
- return std::make_optional(Reaction{
- .id = ReactionId{ emoji },
- .title = qs(data.vtitle()),
- //.staticIcon = _owner->processDocument(data.vstatic_icon()),
- .appearAnimation = _owner->processDocument(
- data.vappear_animation()),
- .selectAnimation = _owner->processDocument(
- data.vselect_animation()),
- //.activateAnimation = _owner->processDocument(
- // data.vactivate_animation()),
- //.activateEffects = _owner->processDocument(
- // data.veffect_animation()),
- .centerIcon = (data.vcenter_icon()
- ? _owner->processDocument(*data.vcenter_icon()).get()
- : nullptr),
- .aroundAnimation = (data.varound_animation()
- ? _owner->processDocument(*data.varound_animation()).get()
- : nullptr),
- .active = !data.is_inactive(),
- });
- }
- std::optional<Reaction> Reactions::parse(const MTPAvailableEffect &entry) {
- const auto &data = entry.data();
- const auto emoji = qs(data.vemoticon());
- const auto known = (Ui::Emoji::Find(emoji) != nullptr);
- if (!known) {
- LOG(("API Error: Unknown emoji in effects: %1").arg(emoji));
- return std::nullopt;
- }
- const auto id = DocumentId(data.vid().v);
- const auto stickerId = data.veffect_sticker_id().v;
- const auto document = _owner->document(stickerId);
- if (!document->sticker()) {
- LOG(("API Error: Bad sticker in effects: %1").arg(stickerId));
- return std::nullopt;
- }
- const auto aroundId = data.veffect_animation_id().value_or_empty();
- const auto around = aroundId
- ? _owner->document(aroundId).get()
- : nullptr;
- if (around && !around->sticker()) {
- LOG(("API Error: Bad sticker in effects around: %1").arg(aroundId));
- return std::nullopt;
- }
- const auto iconId = data.vstatic_icon_id().value_or_empty();
- const auto icon = iconId ? _owner->document(iconId).get() : nullptr;
- if (icon && !icon->sticker()) {
- LOG(("API Error: Bad sticker in effects icon: %1").arg(iconId));
- return std::nullopt;
- }
- return std::make_optional(Reaction{
- .id = ReactionId{ id },
- .title = emoji,
- .appearAnimation = document,
- .selectAnimation = document,
- .centerIcon = icon,
- .aroundAnimation = around,
- .active = true,
- .effect = true,
- .premium = data.is_premium_required(),
- });
- }
- void Reactions::send(not_null<HistoryItem*> item, bool addToRecent) {
- const auto id = item->fullId();
- auto &api = _owner->session().api();
- auto i = _sentRequests.find(id);
- if (i != end(_sentRequests)) {
- api.request(i->second).cancel();
- } else {
- i = _sentRequests.emplace(id).first;
- }
- const auto chosen = item->chosenReactions();
- using Flag = MTPmessages_SendReaction::Flag;
- const auto flags = (chosen.empty() ? Flag(0) : Flag::f_reaction)
- | (addToRecent ? Flag::f_add_to_recent : Flag(0));
- i->second = api.request(MTPmessages_SendReaction(
- MTP_flags(flags),
- item->history()->peer->input,
- MTP_int(id.msg),
- MTP_vector<MTPReaction>(chosen | ranges::views::filter([](
- const ReactionId &id) {
- return !id.paid();
- }) | ranges::views::transform(
- ReactionToMTP
- ) | ranges::to<QVector<MTPReaction>>())
- )).done([=](const MTPUpdates &result) {
- _sentRequests.remove(id);
- _owner->session().api().applyUpdates(result);
- }).fail([=](const MTP::Error &error) {
- _sentRequests.remove(id);
- }).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::updateAllInHistory(not_null<PeerData*> peer, bool enabled) {
- if (const auto history = _owner->historyLoaded(peer)) {
- history->reactionsEnabledChanged(enabled);
- }
- }
- void Reactions::clearTemporary() {
- _temporary.clear();
- }
- Reaction *Reactions::lookupTemporary(const ReactionId &id) {
- if (id.paid()) {
- return lookupPaid();
- } else if (const auto emoji = id.emoji(); !emoji.isEmpty()) {
- const auto i = ranges::find(_available, id, &Reaction::id);
- return (i != end(_available)) ? &*i : nullptr;
- } else if (const auto customId = id.custom()) {
- if (const auto i = _temporary.find(customId); i != end(_temporary)) {
- return &i->second;
- }
- const auto document = _owner->document(customId);
- if (document->sticker()) {
- return &_temporary.emplace(
- customId,
- CustomReaction(document)).first->second;
- }
- _owner->customEmojiManager().resolve(
- customId,
- resolveListener());
- return nullptr;
- }
- return nullptr;
- }
- not_null<Reaction*> Reactions::lookupPaid() {
- if (!_paid) {
- const auto generate = [&](const QString &name) {
- const auto session = &_owner->session();
- return ChatHelpers::GenerateLocalTgsSticker(session, name);
- };
- const auto appear = generate(u"star_reaction_appear"_q);
- const auto center = generate(u"star_reaction_center"_q);
- const auto select = generate(u"star_reaction_select"_q);
- _paid.emplace(Reaction{
- .id = ReactionId::Paid(),
- .title = u"Telegram Star"_q,
- .appearAnimation = appear,
- .selectAnimation = select,
- .centerIcon = center,
- .active = true,
- });
- _iconsCache.emplace(appear, appear->createMediaView());
- _iconsCache.emplace(center, center->createMediaView());
- _iconsCache.emplace(select, select->createMediaView());
- fillPaidReactionAnimations();
- }
- return &*_paid;
- }
- not_null<DocumentData*> Reactions::paidToastAnimation() {
- if (!_paidToastAnimation) {
- _paidToastAnimation = ChatHelpers::GenerateLocalTgsSticker(
- &_owner->session(),
- u"star_reaction_toast"_q);
- }
- return _paidToastAnimation;
- }
- rpl::producer<std::vector<Reaction>> Reactions::myTagsValue(
- SavedSublist *sublist) {
- refreshMyTags(sublist);
- const auto list = [=] {
- return _myTags[sublist].tags;
- };
- return rpl::single(
- list()
- ) | rpl::then(_myTagsUpdated.events(
- ) | rpl::filter(
- rpl::mappers::_1 == sublist
- ) | rpl::map(list));
- }
- bool Reactions::isQuitPrevent() {
- for (auto i = begin(_sendPaidItems); i != end(_sendPaidItems);) {
- const auto item = i->first;
- if (_sendingPaid.contains(item)) {
- ++i;
- } else {
- i = _sendPaidItems.erase(i);
- sendPaid(item);
- }
- }
- if (_sendingPaid.empty()) {
- return false;
- }
- LOG(("Reactions prevents quit, sending paid..."));
- return true;
- }
- void Reactions::schedulePaid(not_null<HistoryItem*> item) {
- _sendPaidItems[item] = crl::now() + kPaidAccumulatePeriod;
- if (!_sendPaidTimer.isActive()) {
- _sendPaidTimer.callOnce(kPaidAccumulatePeriod);
- }
- }
- void Reactions::undoScheduledPaid(not_null<HistoryItem*> item) {
- _sendPaidItems.remove(item);
- item->cancelScheduledPaidReaction();
- }
- crl::time Reactions::sendingScheduledPaidAt(
- not_null<HistoryItem*> item) const {
- const auto i = _sendPaidItems.find(item);
- return (i != end(_sendPaidItems)) ? i->second : crl::time();
- }
- crl::time Reactions::ScheduledPaidDelay() {
- return kPaidAccumulatePeriod;
- }
- void Reactions::repaintCollected() {
- const auto now = crl::now();
- auto closest = crl::time();
- 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 {
- return _sentRequests.contains(item->fullId())
- || _sendingPaid.contains(item);
- }
- bool Reactions::HasUnread(const MTPMessageReactions &data) {
- return data.match([&](const MTPDmessageReactions &data) {
- if (const auto &recent = data.vrecent_reactions()) {
- for (const auto &one : recent->v) {
- if (one.match([&](const MTPDmessagePeerReaction &data) {
- return data.is_unread();
- })) {
- return true;
- }
- }
- }
- return false;
- });
- }
- void Reactions::CheckUnknownForUnread(
- not_null<Session*> owner,
- const MTPMessage &message) {
- message.match([&](const MTPDmessage &data) {
- if (data.vreactions() && HasUnread(*data.vreactions())) {
- const auto peerId = peerFromMTP(data.vpeer_id());
- if (const auto history = owner->historyLoaded(peerId)) {
- owner->histories().requestDialogEntry(history);
- }
- }
- }, [](const auto &) {
- });
- }
- void Reactions::sendPaid() {
- if (!_sendingPaid.empty()) {
- return;
- }
- auto next = crl::time();
- const auto now = crl::now();
- for (auto i = begin(_sendPaidItems); i != end(_sendPaidItems);) {
- const auto item = i->first;
- const auto when = i->second;
- if (when > now) {
- if (!next || next > when) {
- next = when;
- }
- ++i;
- } else {
- i = _sendPaidItems.erase(i);
- if (sendPaid(item)) {
- return;
- }
- }
- }
- if (next) {
- _sendPaidTimer.callOnce(next - now);
- }
- }
- bool Reactions::sendPaid(not_null<HistoryItem*> item) {
- const auto send = item->startPaidReactionSending();
- if (!send.valid) {
- return false;
- }
- sendPaidRequest(item, send);
- return true;
- }
- void Reactions::sendPaidPrivacyRequest(
- not_null<HistoryItem*> item,
- PaidReactionSend send) {
- Expects(!_sendingPaid.contains(item));
- Expects(send.shownPeer.has_value());
- Expects(!send.count);
- const auto id = item->fullId();
- auto &api = _owner->session().api();
- const auto requestId = api.request(
- MTPmessages_TogglePaidReactionPrivacy(
- item->history()->peer->input,
- MTP_int(id.msg),
- PaidReactionShownPeerToTL(&_owner->session(), send.shownPeer))
- ).done([=] {
- if (const auto item = _owner->message(id)) {
- if (_sendingPaid.remove(item)) {
- sendPaidFinish(item, send, true);
- }
- }
- checkQuitPreventFinished();
- }).fail([=](const MTP::Error &error) {
- if (const auto item = _owner->message(id)) {
- if (_sendingPaid.remove(item)) {
- sendPaidFinish(item, send, false);
- }
- }
- checkQuitPreventFinished();
- }).send();
- _sendingPaid[item] = requestId;
- }
- void Reactions::sendPaidRequest(
- not_null<HistoryItem*> item,
- PaidReactionSend send) {
- Expects(!_sendingPaid.contains(item));
- if (!send.count) {
- sendPaidPrivacyRequest(item, send);
- return;
- }
- const auto id = item->fullId();
- const auto randomId = base::unixtime::mtproto_msg_id();
- auto &api = _owner->session().api();
- using Flag = MTPmessages_SendPaidReaction::Flag;
- const auto requestId = api.request(MTPmessages_SendPaidReaction(
- MTP_flags(send.shownPeer ? Flag::f_private : Flag()),
- item->history()->peer->input,
- MTP_int(id.msg),
- MTP_int(send.count),
- MTP_long(randomId),
- (!send.shownPeer
- ? MTPPaidReactionPrivacy()
- : PaidReactionShownPeerToTL(&_owner->session(), *send.shownPeer))
- )).done([=](const MTPUpdates &result) {
- if (const auto item = _owner->message(id)) {
- if (_sendingPaid.remove(item)) {
- sendPaidFinish(item, send, true);
- }
- }
- _owner->session().api().applyUpdates(result);
- checkQuitPreventFinished();
- }).fail([=](const MTP::Error &error) {
- if (const auto item = _owner->message(id)) {
- _sendingPaid.remove(item);
- if (error.type() == u"RANDOM_ID_EXPIRED"_q) {
- sendPaidRequest(item, send);
- } else {
- sendPaidFinish(item, send, false);
- }
- }
- checkQuitPreventFinished();
- }).send();
- _sendingPaid[item] = requestId;
- }
- void Reactions::checkQuitPreventFinished() {
- if (_sendingPaid.empty()) {
- if (Core::Quitting()) {
- LOG(("Reactions doesn't prevent quit any more."));
- }
- Core::App().quitPreventFinished();
- }
- }
- void Reactions::sendPaidFinish(
- not_null<HistoryItem*> item,
- PaidReactionSend send,
- bool success) {
- item->finishPaidReactionSending(send, success);
- sendPaid();
- }
- MessageReactions::MessageReactions(not_null<HistoryItem*> item)
- : _item(item) {
- }
- MessageReactions::~MessageReactions() {
- cancelScheduledPaid();
- if (const auto paid = _paid.get()) {
- if (paid->sending > 0) {
- finishPaidSending({
- .count = int(paid->sending),
- .valid = true,
- .shownPeer = MaybeShownPeer(
- paid->sendingPrivacySet,
- paid->sendingShownPeer),
- }, false);
- }
- }
- }
- void MessageReactions::add(const ReactionId &id, bool addToRecent) {
- Expects(!id.empty());
- Expects(!id.paid());
- const auto history = _item->history();
- const auto myLimit = SentReactionsLimit(_item);
- if (ranges::contains(chosen(), id)) {
- return;
- }
- auto my = 0;
- const auto tags = _item->reactionsAreTags();
- if (tags) {
- const auto sublist = _item->savedSublist();
- history->owner().reactions().incrementMyTag(id, sublist);
- }
- _list.erase(ranges::remove_if(_list, [&](MessageReaction &one) {
- if (one.id.paid()) {
- return false;
- }
- const auto removing = one.my && (my == myLimit || ++my == myLimit);
- if (!removing) {
- return false;
- }
- one.my = false;
- const auto removed = !--one.count;
- const auto j = _recent.find(one.id);
- if (j != end(_recent)) {
- if (removed) {
- j->second.clear();
- _recent.erase(j);
- } else {
- j->second.erase(
- ranges::remove(j->second, true, &RecentReaction::my),
- end(j->second));
- if (j->second.empty()) {
- _recent.erase(j);
- }
- }
- }
- if (tags) {
- const auto sublist = _item->savedSublist();
- history->owner().reactions().decrementMyTag(one.id, sublist);
- }
- return removed;
- }), end(_list));
- const auto peer = history->peer;
- if (_item->canViewReactions() || peer->isUser()) {
- auto &list = _recent[id];
- const auto from = peer->session().sendAsPeers().resolveChosen(peer);
- list.insert(begin(list), RecentReaction{
- .peer = from,
- .my = true,
- });
- }
- const auto i = ranges::find(_list, id, &MessageReaction::id);
- if (i != end(_list)) {
- i->my = true;
- ++i->count;
- std::rotate(i, i + 1, end(_list));
- } else {
- _list.push_back({ .id = id, .count = 1, .my = true });
- }
- auto &owner = history->owner();
- owner.reactions().send(_item, addToRecent);
- owner.notifyItemDataChange(_item);
- }
- void MessageReactions::remove(const ReactionId &id) {
- Expects(!id.paid());
- const auto history = _item->history();
- const auto self = history->session().user();
- const auto i = ranges::find(_list, id, &MessageReaction::id);
- const auto j = _recent.find(id);
- if (i == end(_list)) {
- Assert(j == end(_recent));
- return;
- } else if (!i->my) {
- Assert(j == end(_recent)
- || !ranges::contains(j->second, self, &RecentReaction::peer));
- return;
- }
- i->my = false;
- const auto tags = _item->reactionsAreTags();
- const auto removed = !--i->count;
- if (removed) {
- _list.erase(i);
- }
- if (j != end(_recent)) {
- if (removed) {
- j->second.clear();
- _recent.erase(j);
- } else {
- j->second.erase(
- ranges::remove(j->second, true, &RecentReaction::my),
- end(j->second));
- if (j->second.empty()) {
- _recent.erase(j);
- }
- }
- }
- if (tags) {
- const auto sublist = _item->savedSublist();
- history->owner().reactions().decrementMyTag(id, sublist);
- }
- auto &owner = history->owner();
- owner.reactions().send(_item, false);
- owner.notifyItemDataChange(_item);
- }
- bool MessageReactions::checkIfChanged(
- const QVector<MTPReactionCount> &list,
- const QVector<MTPMessagePeerReaction> &recent,
- bool min) const {
- auto &owner = _item->history()->owner();
- if (owner.reactions().sending(_item)) {
- // We'll apply non-stale data from the request response.
- return false;
- }
- auto existing = base::flat_set<ReactionId>();
- for (const auto &count : list) {
- const auto changed = count.match([&](const MTPDreactionCount &data) {
- const auto id = ReactionFromMTP(data.vreaction());
- const auto nowCount = data.vcount().v;
- const auto i = ranges::find(_list, id, &MessageReaction::id);
- const auto wasCount = (i != end(_list)) ? i->count : 0;
- if (wasCount != nowCount) {
- return true;
- }
- existing.emplace(id);
- return false;
- });
- if (changed) {
- return true;
- }
- }
- for (const auto &reaction : _list) {
- if (!existing.contains(reaction.id)) {
- return true;
- }
- }
- auto parsed = base::flat_map<ReactionId, std::vector<RecentReaction>>();
- for (const auto &reaction : recent) {
- reaction.match([&](const MTPDmessagePeerReaction &data) {
- const auto id = ReactionFromMTP(data.vreaction());
- if (!ranges::contains(_list, id, &MessageReaction::id)) {
- return;
- }
- const auto peerId = peerFromMTP(data.vpeer_id());
- const auto peer = owner.peer(peerId);
- const auto my = IsMyRecent(data, id, peer, _recent, min);
- parsed[id].push_back({
- .peer = peer,
- .unread = data.is_unread(),
- .big = data.is_big(),
- .my = my,
- });
- });
- }
- return !ranges::equal(_recent, parsed, [](
- const auto &a,
- const auto &b) {
- return ranges::equal(a.second, b.second, [](
- const RecentReaction &a,
- const RecentReaction &b) {
- return (a.peer == b.peer) && (a.big == b.big) && (a.my == b.my);
- });
- });
- }
- bool MessageReactions::change(
- const QVector<MTPReactionCount> &list,
- const QVector<MTPMessagePeerReaction> &recent,
- const QVector<MTPMessageReactor> &top,
- bool min) {
- auto &owner = _item->history()->owner();
- if (owner.reactions().sending(_item)) {
- // We'll apply non-stale data from the request response.
- return false;
- }
- auto changed = false;
- auto existing = base::flat_set<ReactionId>();
- auto order = base::flat_map<ReactionId, int>();
- for (const auto &count : list) {
- count.match([&](const MTPDreactionCount &data) {
- const auto id = ReactionFromMTP(data.vreaction());
- const auto &chosen = data.vchosen_order();
- if (!min && chosen) {
- order[id] = chosen->v;
- }
- const auto i = ranges::find(_list, id, &MessageReaction::id);
- const auto nowCount = data.vcount().v;
- if (i == end(_list)) {
- changed = true;
- _list.push_back({
- .id = id,
- .count = nowCount,
- .my = (!min && chosen)
- });
- } else {
- const auto nowMy = min ? i->my : chosen.has_value();
- if (i->count != nowCount || i->my != nowMy) {
- i->count = nowCount;
- i->my = nowMy;
- changed = true;
- }
- }
- existing.emplace(id);
- });
- }
- if (!min && !order.empty()) {
- const auto minimal = std::numeric_limits<int>::min();
- const auto proj = [&](const MessageReaction &reaction) {
- return reaction.my ? order[reaction.id] : minimal;
- };
- const auto correctOrder = [&] {
- auto previousOrder = minimal;
- for (const auto &reaction : _list) {
- const auto nowOrder = proj(reaction);
- if (nowOrder < previousOrder) {
- return false;
- }
- previousOrder = nowOrder;
- }
- return true;
- }();
- if (!correctOrder) {
- changed = true;
- ranges::sort(_list, std::less(), proj);
- }
- }
- if (_list.size() != existing.size()) {
- changed = true;
- for (auto i = begin(_list); i != end(_list);) {
- if (!existing.contains(i->id)) {
- i = _list.erase(i);
- } else {
- ++i;
- }
- }
- }
- auto parsed = base::flat_map<ReactionId, std::vector<RecentReaction>>();
- for (const auto &reaction : recent) {
- reaction.match([&](const MTPDmessagePeerReaction &data) {
- const auto id = ReactionFromMTP(data.vreaction());
- const auto i = ranges::find(_list, id, &MessageReaction::id);
- if (i == end(_list)) {
- return;
- }
- auto &list = parsed[id];
- if (list.size() >= i->count) {
- return;
- }
- const auto peer = owner.peer(peerFromMTP(data.vpeer_id()));
- const auto my = IsMyRecent(data, id, peer, _recent, min);
- list.push_back({
- .peer = peer,
- .unread = data.is_unread(),
- .big = data.is_big(),
- .my = my,
- });
- });
- }
- if (_recent != parsed) {
- _recent = std::move(parsed);
- changed = true;
- }
- auto paidTop = std::vector<TopPaid>();
- const auto &paindTopNow = _paid ? _paid->top : std::vector<TopPaid>();
- for (const auto &reactor : top) {
- const auto &data = reactor.data();
- const auto peerId = (data.is_anonymous() || !data.vpeer_id())
- ? PeerId()
- : peerFromMTP(*data.vpeer_id());
- const auto peer = peerId ? owner.peer(peerId).get() : nullptr;
- paidTop.push_back({
- .peer = peer,
- .count = uint32(data.vcount().v),
- .top = data.is_top(),
- .my = IsMyTop(data, peer, paindTopNow, min),
- });
- }
- if (paidTop.empty()) {
- if (_paid && !_paid->top.empty()) {
- changed = true;
- if (localPaidData()) {
- _paid->top.clear();
- } else {
- _paid = nullptr;
- }
- }
- } else {
- if (min && _paid) {
- const auto mine = [](const TopPaid &entry) {
- return entry.my != 0;
- };
- if (!ranges::contains(paidTop, true, mine)) {
- const auto nonTopMine = [](const TopPaid &entry) {
- return entry.my && !entry.top;
- };
- const auto i = ranges::find(_paid->top, true, nonTopMine);
- if (i != end(_paid->top)) {
- paidTop.push_back(*i);
- }
- }
- }
- ranges::sort(paidTop, std::greater(), [](const TopPaid &entry) {
- return entry.count;
- });
- if (!_paid) {
- _paid = std::make_unique<Paid>();
- }
- if (_paid->top != paidTop) {
- _paid->top = std::move(paidTop);
- changed = true;
- }
- }
- return changed;
- }
- const std::vector<MessageReaction> &MessageReactions::list() const {
- return _list;
- }
- auto MessageReactions::recent() const
- -> const base::flat_map<ReactionId, std::vector<RecentReaction>> & {
- return _recent;
- }
- auto MessageReactions::topPaid() const -> const std::vector<TopPaid> & {
- static const auto kEmpty = std::vector<TopPaid>();
- return _paid ? _paid->top : kEmpty;
- }
- bool MessageReactions::empty() const {
- return _list.empty();
- }
- bool MessageReactions::hasUnread() const {
- for (auto &[emoji, list] : _recent) {
- if (ranges::contains(list, true, &RecentReaction::unread)) {
- return true;
- }
- }
- return false;
- }
- void MessageReactions::markRead() {
- for (auto &[emoji, list] : _recent) {
- for (auto &reaction : list) {
- reaction.unread = false;
- }
- }
- }
- void MessageReactions::scheduleSendPaid(
- int count,
- std::optional<PeerId> shownPeer) {
- Expects(count >= 0);
- if (!_paid) {
- _paid = std::make_unique<Paid>();
- }
- _paid->scheduled += count;
- _paid->scheduledFlag = 1;
- if (shownPeer.has_value()) {
- _paid->scheduledShownPeer = *shownPeer;
- _paid->scheduledPrivacySet = true;
- }
- if (count > 0) {
- _item->history()->session().credits().lock(StarsAmount(count));
- }
- _item->history()->owner().reactions().schedulePaid(_item);
- }
- int MessageReactions::scheduledPaid() const {
- return _paid ? _paid->scheduled : 0;
- }
- void MessageReactions::cancelScheduledPaid() {
- if (_paid) {
- if (_paid->scheduledFlag) {
- if (const auto amount = int(_paid->scheduled)) {
- _item->history()->session().credits().unlock(
- StarsAmount(amount));
- }
- _paid->scheduled = 0;
- _paid->scheduledFlag = 0;
- _paid->scheduledShownPeer = 0;
- _paid->scheduledPrivacySet = 0;
- }
- if (!_paid->sendingFlag && _paid->top.empty()) {
- _paid = nullptr;
- }
- }
- }
- PaidReactionSend MessageReactions::startPaidSending() {
- if (!_paid || !_paid->scheduledFlag || _paid->sendingFlag) {
- return {};
- }
- _paid->sending = _paid->scheduled;
- _paid->sendingFlag = _paid->scheduledFlag;
- _paid->sendingShownPeer = _paid->scheduledShownPeer;
- _paid->sendingPrivacySet = _paid->scheduledPrivacySet;
- _paid->scheduled = 0;
- _paid->scheduledFlag = 0;
- _paid->scheduledShownPeer = 0;
- _paid->scheduledPrivacySet = 0;
- return {
- .count = int(_paid->sending),
- .valid = true,
- .shownPeer = MaybeShownPeer(
- _paid->sendingPrivacySet,
- _paid->sendingShownPeer),
- };
- }
- void MessageReactions::finishPaidSending(
- PaidReactionSend send,
- bool success) {
- Expects(_paid != nullptr);
- Expects(send.count == _paid->sending);
- Expects(send.valid == (_paid->sendingFlag == 1));
- Expects(send.shownPeer == MaybeShownPeer(
- _paid->sendingPrivacySet,
- _paid->sendingShownPeer));
- _paid->sending = 0;
- _paid->sendingFlag = 0;
- _paid->sendingShownPeer = 0;
- _paid->sendingPrivacySet = 0;
- if (!_paid->scheduledFlag && _paid->top.empty()) {
- _paid = nullptr;
- } else if (!send.count) {
- const auto i = ranges::find_if(_paid->top, [](const TopPaid &top) {
- return top.my;
- });
- if (i != end(_paid->top)) {
- i->peer = send.shownPeer
- ? _item->history()->owner().peer(*send.shownPeer).get()
- : nullptr;
- }
- }
- if (const auto amount = send.count) {
- const auto credits = &_item->history()->session().credits();
- if (success) {
- credits->withdrawLocked(StarsAmount(amount));
- } else {
- credits->unlock(StarsAmount(amount));
- }
- }
- }
- bool MessageReactions::localPaidData() const {
- return _paid && (_paid->scheduledFlag || _paid->sendingFlag);
- }
- int MessageReactions::localPaidCount() const {
- return _paid ? (_paid->scheduled + _paid->sending) : 0;
- }
- PeerId MessageReactions::localPaidShownPeer() const {
- const auto minePaidShownPeer = [&] {
- for (const auto &entry : _paid->top) {
- if (entry.my) {
- return entry.peer ? entry.peer->id : PeerId();
- }
- }
- const auto api = &_item->history()->session().api();
- return api->globalPrivacy().paidReactionShownPeerCurrent();
- };
- return !_paid
- ? PeerId()
- : (_paid->scheduledFlag && _paid->scheduledPrivacySet)
- ? _paid->scheduledShownPeer
- : (_paid->sendingFlag && _paid->sendingPrivacySet)
- ? _paid->sendingShownPeer
- : minePaidShownPeer();
- }
- bool MessageReactions::clearCloudData() {
- const auto result = !_list.empty();
- _recent.clear();
- _list.clear();
- if (localPaidData()) {
- _paid->top.clear();
- } else {
- _paid = nullptr;
- }
- return result;
- }
- std::vector<ReactionId> MessageReactions::chosen() const {
- return _list
- | ranges::views::filter(&MessageReaction::my)
- | ranges::views::transform(&MessageReaction::id)
- | ranges::to_vector;
- }
- } // namespace Data
|