From 9bb1fa8782d5e6a5996474521e03ee08e950ad3c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 6 Aug 2024 12:48:06 +0200 Subject: [PATCH] Allow sending paid reactions. --- Telegram/CMakeLists.txt | 4 + .../animations/star_reaction/center.tgs | Bin 0 -> 5659 bytes .../animations/star_reaction/effect.tgs | Bin 8420 -> 0 bytes .../animations/star_reaction/effect1.tgs | Bin 0 -> 22618 bytes .../animations/star_reaction/effect2.tgs | Bin 0 -> 45596 bytes .../animations/star_reaction/effect3.tgs | Bin 0 -> 28698 bytes Telegram/Resources/langs/lang.strings | 8 + .../Resources/qrc/telegram/animations.qrc | 5 +- Telegram/SourceFiles/api/api_credits.cpp | 8 +- Telegram/SourceFiles/api/api_updates.cpp | 3 +- .../SourceFiles/boxes/send_credits_box.cpp | 11 +- .../SourceFiles/data/components/credits.cpp | 113 ++++++ .../SourceFiles/data/components/credits.h | 55 +++ .../data/data_message_reactions.cpp | 355 +++++++++++++++--- .../SourceFiles/data/data_message_reactions.h | 60 ++- .../history/history_inner_widget.cpp | 17 +- Telegram/SourceFiles/history/history_item.cpp | 65 +++- Telegram/SourceFiles/history/history_item.h | 19 +- .../view/history_view_context_menu.cpp | 4 +- .../history/view/history_view_list_widget.cpp | 6 +- .../history/view/history_view_message.cpp | 116 ++---- .../history/view/history_view_message.h | 1 - .../view/reactions/history_view_reactions.cpp | 2 +- Telegram/SourceFiles/main/main_session.cpp | 10 +- Telegram/SourceFiles/main/main_session.h | 9 +- .../payments/payments_non_panel_process.cpp | 68 ++-- .../payments/payments_reaction_process.cpp | 190 ++++++++++ .../payments/payments_reaction_process.h | 37 ++ .../payments/ui/payments_reaction_box.cpp | 148 ++++++++ .../payments/ui/payments_reaction_box.h | 40 ++ .../SourceFiles/settings/settings_credits.cpp | 9 +- .../settings/settings_credits_graphics.cpp | 122 ++++-- .../settings/settings_credits_graphics.h | 28 +- .../SourceFiles/settings/settings_main.cpp | 13 +- .../ui/effects/reaction_fly_animation.cpp | 4 +- Telegram/cmake/td_ui.cmake | 2 + 36 files changed, 1257 insertions(+), 275 deletions(-) create mode 100644 Telegram/Resources/animations/star_reaction/center.tgs delete mode 100644 Telegram/Resources/animations/star_reaction/effect.tgs create mode 100644 Telegram/Resources/animations/star_reaction/effect1.tgs create mode 100644 Telegram/Resources/animations/star_reaction/effect2.tgs create mode 100644 Telegram/Resources/animations/star_reaction/effect3.tgs create mode 100644 Telegram/SourceFiles/data/components/credits.cpp create mode 100644 Telegram/SourceFiles/data/components/credits.h create mode 100644 Telegram/SourceFiles/payments/payments_reaction_process.cpp create mode 100644 Telegram/SourceFiles/payments/payments_reaction_process.h create mode 100644 Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp create mode 100644 Telegram/SourceFiles/payments/ui/payments_reaction_box.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index d3fc32100..7ee4e538b 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -475,6 +475,8 @@ PRIVATE data/business/data_business_info.h data/business/data_shortcut_messages.cpp data/business/data_shortcut_messages.h + data/components/credits.cpp + data/components/credits.h data/components/factchecks.cpp data/components/factchecks.h data/components/location_pickers.cpp @@ -1236,6 +1238,8 @@ PRIVATE payments/payments_form.h payments/payments_non_panel_process.cpp payments/payments_non_panel_process.h + payments/payments_reaction_process.cpp + payments/payments_reaction_process.h platform/linux/file_utilities_linux.cpp platform/linux/file_utilities_linux.h platform/linux/launcher_linux.cpp diff --git a/Telegram/Resources/animations/star_reaction/center.tgs b/Telegram/Resources/animations/star_reaction/center.tgs new file mode 100644 index 0000000000000000000000000000000000000000..61f1538549f2aa06807dbf5c7d89f2b8f15ccef6 GIT binary patch literal 5659 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 B3rYY0 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/animations/star_reaction/effect.tgs b/Telegram/Resources/animations/star_reaction/effect.tgs deleted file mode 100644 index 0b87a225a4f0cb162cc29de04ca74261253d11dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8420 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 diff --git a/Telegram/Resources/animations/star_reaction/effect1.tgs b/Telegram/Resources/animations/star_reaction/effect1.tgs new file mode 100644 index 0000000000000000000000000000000000000000..f31897de51b445d2be1e18d1bbdcfea0df7385a4 GIT binary patch literal 22618 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../../animations/dice/winners.tgs ../../animations/star_reaction/appear.tgs - ../../animations/star_reaction/effect.tgs + ../../animations/star_reaction/center.tgs ../../animations/star_reaction/select.tgs + ../../animations/star_reaction/effect1.tgs + ../../animations/star_reaction/effect2.tgs + ../../animations/star_reaction/effect3.tgs diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 4a5aece89..4458a5446 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -200,10 +200,14 @@ void CreditsStatus::request( _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input )).done([=](const TLResult &result) { _requestId = 0; - done(StatusFromTL(result, _peer)); + if (const auto onstack = done) { + onstack(StatusFromTL(result, _peer)); + } }).fail([=] { _requestId = 0; - done({}); + if (const auto onstack = done) { + onstack({}); + } }).send(); } diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 1951dc292..3aa7479c7 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/mtproto_config.h" #include "mtproto/mtproto_dc_options.h" #include "data/business/data_shortcut_messages.h" +#include "data/components/credits.h" #include "data/components/scheduled_messages.h" #include "data/components/top_peers.h" #include "data/notify/data_notify_settings.h" @@ -2618,7 +2619,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updateStarsBalance: { const auto &data = update.c_updateStarsBalance(); - _session->setCredits(data.vbalance().v); + _session->credits().apply(data); } break; } diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index a8771cc17..61b3b8952 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_credits.h" #include "apiwrap.h" #include "core/ui_integration.h" // Core::MarkedTextContext. +#include "data/components/credits.h" #include "data/data_credits.h" #include "data/data_photo.h" #include "data/data_session.h" @@ -301,7 +302,7 @@ void SendCreditsBox( st::giveawayGiftCodeStartButton.height / 2); AddChildToWidgetCenter(button.data(), loadingAnimation); loadingAnimation->showOn(state->confirmButtonBusy.value()); - } + } { auto buttonText = tr::lng_credits_box_out_confirm( lt_count, @@ -361,15 +362,11 @@ void SendCreditsBox( } { + session->credits().load(true); const auto balance = Settings::AddBalanceWidget( content, - session->creditsValue(), + session->credits().balanceValue(), false); - const auto api = balance->lifetime().make_state( - session->user()); - api->request({}, [=](Data::CreditsStatusSlice slice) { - session->setCredits(slice.balance); - }); rpl::combine( balance->sizeValue(), content->sizeValue() diff --git a/Telegram/SourceFiles/data/components/credits.cpp b/Telegram/SourceFiles/data/components/credits.cpp new file mode 100644 index 000000000..e62b56ca2 --- /dev/null +++ b/Telegram/SourceFiles/data/components/credits.cpp @@ -0,0 +1,113 @@ +/* +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/components/credits.h" + +#include "api/api_credits.h" +#include "data/data_user.h" +#include "main/main_session.h" + +namespace Data { +namespace { + +constexpr auto kReloadThreshold = 60 * crl::time(1000); + +} // namespace + +Credits::Credits(not_null session) +: _session(session) +, _reload([=] { load(true); }) { +} + +Credits::~Credits() = default; + +void Credits::apply(const MTPDupdateStarsBalance &data) { + apply(data.vbalance().v); +} + +void Credits::load(bool force) { + if (_loader + || (!force + && _lastLoaded + && _lastLoaded + kReloadThreshold > crl::now())) { + return; + } + _loader = std::make_unique(_session->user()); + _loader->request({}, [=](Data::CreditsStatusSlice slice) { + _loader = nullptr; + apply(slice.balance); + }); +} + +bool Credits::loaded() const { + return _lastLoaded != 0; +} + +rpl::producer Credits::loadedValue() const { + if (loaded()) { + return rpl::single(true); + } + return rpl::single( + false + ) | rpl::then(_loadedChanges.events() | rpl::map_to(true)); +} + +uint64 Credits::balance() const { + const auto balance = _balance.current(); + const auto locked = _locked.current(); + return (balance >= locked) ? (balance - locked) : 0; +} + +rpl::producer Credits::balanceValue() const { + return rpl::combine( + _balance.value(), + _locked.value() + ) | rpl::map([=](uint64 balance, uint64 locked) { + return (balance >= locked) ? (balance - locked) : 0; + }); +} + +void Credits::lock(int count) { + Expects(loaded()); + Expects(count >= 0); + + _locked = _locked.current() + count; + + Ensures(_locked.current() <= _balance.current()); +} + +void Credits::unlock(int count) { + Expects(count >= 0); + Expects(_locked.current() >= count); + + _locked = _locked.current() - count; +} + +void Credits::withdrawLocked(int count) { + Expects(count >= 0); + Expects(_locked.current() >= count); + + const auto balance = _balance.current(); + _locked = _locked.current() - count; + apply(balance >= count ? (balance - count) : 0); + invalidate(); +} + +void Credits::invalidate() { + _reload.call(); +} + +void Credits::apply(uint64 balance) { + _balance = balance; + + const auto was = std::exchange(_lastLoaded, crl::now()); + if (!was) { + _loadedChanges.fire({}); + } +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/components/credits.h b/Telegram/SourceFiles/data/components/credits.h new file mode 100644 index 000000000..ff65f48f6 --- /dev/null +++ b/Telegram/SourceFiles/data/components/credits.h @@ -0,0 +1,55 @@ +/* +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 +*/ +#pragma once + +namespace Api { +class CreditsStatus; +} // namespace Api + +namespace Main { +class Session; +} // namespace Main + +namespace Data { + +class Credits final { +public: + explicit Credits(not_null session); + ~Credits(); + + void load(bool force = false); + void apply(uint64 balance); + + [[nodiscard]] bool loaded() const; + [[nodiscard]] rpl::producer loadedValue() const; + + [[nodiscard]] uint64 balance() const; + [[nodiscard]] rpl::producer balanceValue() const; + + void lock(int count); + void unlock(int count); + void withdrawLocked(int count); + void invalidate(); + + void apply(const MTPDupdateStarsBalance &data); + +private: + const not_null _session; + + std::unique_ptr _loader; + + rpl::variable _balance; + rpl::variable _locked; + rpl::event_stream<> _loadedChanges; + crl::time _lastLoaded = 0; + + SingleQueuedInvokation _reload; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index c2b92de24..01956f309 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #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_user.h" #include "data/data_session.h" #include "data/data_histories.h" @@ -34,6 +35,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "styles/style_chat.h" +#include "base/random.h" + namespace Data { namespace { @@ -45,6 +48,7 @@ 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); [[nodiscard]] QString ReactionIdToLog(const ReactionId &id) { if (const auto custom = id.custom()) { @@ -109,17 +113,17 @@ constexpr auto kTopReactionsLimit = 14; : config->get("reactions_user_max_default", 1); } -bool IsMyRecent( +[[nodiscard]] bool IsMyRecent( const MTPDmessagePeerReaction &data, const ReactionId &id, not_null peer, const base::flat_map< ReactionId, std::vector> &recent, - bool ignoreChosen) { - if (peer->id == peer->session().userPeerId()) { + bool min) { + if (peer->isSelf()) { return true; - } else if (!ignoreChosen) { + } else if (!min) { return data.is_my(); } const auto j = recent.find(id); @@ -133,6 +137,20 @@ bool IsMyRecent( return (k != end(j->second)) && k->my; } +[[nodiscard]] bool IsMyTop( + const MTPDmessageReactor &data, + not_null peer, + const std::vector &top, + bool min) { + if (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; +} + } // namespace PossibleItemReactionsRef LookupPossibleReactions( @@ -265,7 +283,8 @@ PossibleItemReactions::PossibleItemReactions( Reactions::Reactions(not_null owner) : _owner(owner) , _topRefreshTimer([=] { refreshTop(); }) -, _repaintTimer([=] { repaintCollected(); }) { +, _repaintTimer([=] { repaintCollected(); }) +, _sendPaidTimer([=] { sendPaid(); }) { refreshDefault(); _myTags.emplace(nullptr); @@ -284,6 +303,14 @@ Reactions::Reactions(not_null owner) _pollingItems.remove(item); _pollItems.remove(item); _repaintItems.remove(item); + _sendPaidItems.remove(item); + if (_sendingPaid == item) { + _sendingPaid = nullptr; + _owner->session().credits().invalidate(); + crl::on_main(&_owner->session(), [=] { + sendPaid(); + }); + } }, _lifetime); crl::on_main(&owner->session(), [=] { @@ -510,21 +537,49 @@ DocumentData *Reactions::chooseGenericAnimation( return i->aroundAnimation; } } - if (_genericAnimations.empty()) { + 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> list) const { + if (list.empty()) { return nullptr; } - auto copy = _genericAnimations; - ranges::shuffle(copy); - const auto first = copy.front(); + 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(copy, [&](not_null value) { + const auto k = ranges::find_if(list, [&](not_null value) { return value->createMediaView()->loaded(); }); - return (k != end(copy)) ? (*k) : first; + return (k != end(list)) ? (*k) : first; } void Reactions::applyFavorite(const ReactionId &id) { @@ -593,7 +648,7 @@ void Reactions::preloadImageFor(const ReactionId &id) { auto &set = _images.emplace(id).first->second; set.effect = (id.custom() != 0); if (id.paid()) { - loadImage(set, lookupPaid()->selectAnimation, true); + loadImage(set, lookupPaid()->centerIcon, true); return; } auto &list = set.effect ? _effects : _available; @@ -631,6 +686,20 @@ void Reactions::preloadEffect(const Reaction &effect) { } 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; @@ -641,15 +710,6 @@ void Reactions::preloadAnimationsFor(const ReactionId &id) { if (i == end(_available)) { return; } - const auto preload = [&](DocumentData *document) { - const auto view = document - ? document->activeMediaView() - : nullptr; - if (view) { - view->checkStickerLarge(); - } - }; - if (!custom) { preload(i->centerIcon); } @@ -1380,21 +1440,6 @@ void Reactions::send(not_null item, bool addToRecent) { }).send(); } -void Reactions::sendPaid(not_null item, int count) { - const auto id = item->fullId(); - const auto randomId = base::unixtime::mtproto_msg_id(); - auto &api = _owner->session().api(); - api.request(MTPmessages_SendPaidReaction( - item->history()->peer->input, - MTP_int(id.msg), - MTP_int(count), - MTP_long(randomId) - )).done([=](const MTPUpdates &result) { - _owner->session().api().applyUpdates(result); - }).fail([=](const MTP::Error &error) { - }).send(); -} - void Reactions::poll(not_null item, crl::time now) { // Group them by one second. const auto last = item->lastReactionsRefreshTime(); @@ -1460,16 +1505,22 @@ not_null Reactions::lookupPaid() { 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 = generate(u"star_reaction_appear"_q), + .appearAnimation = appear, .selectAnimation = select, - .centerIcon = select, - //.aroundAnimation = generate(u"star_reaction_effect"_q), + .centerIcon = center, .active = true, }); + _iconsCache.emplace(appear, appear->createMediaView()); + _iconsCache.emplace(center, center->createMediaView()); + _iconsCache.emplace(select, select->createMediaView()); + + fillPaidReactionAnimations(); } return &*_paid; } @@ -1488,6 +1539,13 @@ rpl::producer> Reactions::myTagsValue( ) | rpl::map(list)); } +void Reactions::schedulePaid(not_null item) { + _sendPaidItems[item] = crl::now() + kPaidAccumulatePeriod; + if (!_sendPaidTimer.isActive()) { + _sendPaidTimer.callOnce(kPaidAccumulatePeriod); + } +} + void Reactions::repaintCollected() { const auto now = crl::now(); auto closest = crl::time(); @@ -1543,7 +1601,7 @@ void Reactions::pollCollected() { } bool Reactions::sending(not_null item) const { - return _sentRequests.contains(item->fullId()); + return _sentRequests.contains(item->fullId()) || (_sendingPaid == item); } bool Reactions::HasUnread(const MTPMessageReactions &data) { @@ -1575,31 +1633,91 @@ void Reactions::CheckUnknownForUnread( }); } +void Reactions::sendPaid() { + if (_sendingPaid) { + 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 item) { + Expects(!_sendingPaid); + + const auto count = item->startPaidReactionSending(); + if (!count) { + return false; + } + + _sendingPaid = item; + sendPaidRequest(count); + return true; +} + +void Reactions::sendPaidRequest(int count) { + const auto id = _sendingPaid->fullId(); + const auto randomId = base::unixtime::mtproto_msg_id(); + auto &api = _owner->session().api(); + api.request(MTPmessages_SendPaidReaction( + _sendingPaid->history()->peer->input, + MTP_int(id.msg), + MTP_int(count), + MTP_long(randomId) + )).done([=](const MTPUpdates &result) { + sendPaidFinish(id, count, true); + _owner->session().api().applyUpdates(result); + }).fail([=](const MTP::Error &error) { + if (!_sendingPaid + || (_sendingPaid->fullId() != id) + || (error.type() != u"RANDOM_ID_EXPIRED"_q)) { + sendPaidFinish(id, count, false); + } else { + sendPaidRequest(count); + } + }).send(); +} + +void Reactions::sendPaidFinish(FullMsgId id, int count, bool success) { + if (_sendingPaid && _sendingPaid->fullId() == id) { + base::take(_sendingPaid)->finishPaidReactionSending(count, success); + sendPaid(); + } +} + MessageReactions::MessageReactions(not_null item) : _item(item) { } -void MessageReactions::addPaid(int count) { - Expects(_item->history()->peer->isBroadcast()); - - const auto id = Data::ReactionId::Paid(); - const auto history = _item->history(); - const auto peer = history->peer; - const auto i = ranges::find(_list, id, &MessageReaction::id); - if (i != end(_list)) { - i->my = true; - i->count += count; - std::rotate(i, i + 1, end(_list)); - } else { - _list.push_back({ .id = id, .count = count, .my = true }); +MessageReactions::~MessageReactions() { + cancelScheduledPaid(); + if (const auto paid = _paid.get()) { + if (paid->sending > 0) { + finishPaidSending(paid->sending, false); + } } - auto &owner = history->owner(); - owner.reactions().sendPaid(_item, count); - owner.notifyItemDataChange(_item); } void MessageReactions::add(const ReactionId &id, bool addToRecent) { Expects(!id.empty()); + Expects(!id.paid()); const auto history = _item->history(); const auto myLimit = SentReactionsLimit(_item); @@ -1613,6 +1731,9 @@ void MessageReactions::add(const ReactionId &id, bool addToRecent) { 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; @@ -1662,6 +1783,8 @@ void MessageReactions::add(const ReactionId &id, bool addToRecent) { } 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); @@ -1765,6 +1888,7 @@ bool MessageReactions::checkIfChanged( bool MessageReactions::change( const QVector &list, const QVector &recent, + const QVector &top, bool min) { auto &owner = _item->history()->owner(); if (owner.reactions().sending(_item)) { @@ -1844,8 +1968,7 @@ bool MessageReactions::change( if (list.size() >= i->count) { return; } - const auto peerId = peerFromMTP(data.vpeer_id()); - const auto peer = owner.peer(peerId); + const auto peer = owner.peer(peerFromMTP(data.vpeer_id())); const auto my = IsMyRecent(data, id, peer, _recent, min); list.push_back({ .peer = peer, @@ -1859,6 +1982,54 @@ bool MessageReactions::change( _recent = std::move(parsed); changed = true; } + + auto paidTop = std::vector(); + const auto &paindTopNow = _paid ? _paid->top : std::vector(); + for (const auto &reactor : top) { + const auto &data = reactor.data(); + const auto peer = owner.peer(peerFromMTP(data.vpeer_id())); + 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 (localPaidCount()) { + _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(); + } + if (_paid->top != paidTop) { + _paid->top = std::move(paidTop); + changed = true; + } + } return changed; } @@ -1892,6 +2063,74 @@ void MessageReactions::markRead() { } } +void MessageReactions::scheduleSendPaid(int count) { + Expects(count > 0); + + if (!_paid) { + _paid = std::make_unique(); + } + _paid->scheduled += count; + _item->history()->session().credits().lock(count); + _item->history()->owner().reactions().schedulePaid(_item); +} + +int MessageReactions::scheduledPaid() const { + return _paid ? _paid->scheduled : 0; +} + +void MessageReactions::cancelScheduledPaid() { + if (_paid) { + if (_paid->scheduled > 0) { + _item->history()->session().credits().unlock( + base::take(_paid->scheduled)); + } + if (!_paid->sending && _paid->top.empty()) { + _paid = nullptr; + } + } +} + +int MessageReactions::startPaidSending() { + if (!_paid || !_paid->scheduled || _paid->sending) { + return 0; + } + _paid->sending = _paid->scheduled; + _paid->scheduled = 0; + return _paid->sending; +} + +void MessageReactions::finishPaidSending(int count, bool success) { + Expects(count > 0); + Expects(_paid != nullptr); + Expects(count == _paid->sending); + + _paid->sending = 0; + if (!_paid->scheduled && _paid->top.empty()) { + _paid = nullptr; + } + if (success) { + _item->history()->session().credits().withdrawLocked(count); + } else { + _item->history()->session().credits().unlock(count); + } +} + +int MessageReactions::localPaidCount() const { + return _paid ? (_paid->scheduled + _paid->sending) : 0; +} + +bool MessageReactions::clearCloudData() { + const auto result = !_list.empty(); + _recent.clear(); + _list.clear(); + if (localPaidCount()) { + _paid->top.clear(); + } else { + _paid = nullptr; + } + return result; +} + std::vector MessageReactions::chosen() const { return _list | ranges::views::filter(&MessageReaction::my) diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index fff6fcf04..1bcf46847 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -106,6 +106,7 @@ public: void renameTag(const ReactionId &id, const QString &name); [[nodiscard]] DocumentData *chooseGenericAnimation( not_null custom) const; + [[nodiscard]] DocumentData *choosePaidReactionAnimation() const; [[nodiscard]] rpl::producer<> topUpdates() const; [[nodiscard]] rpl::producer<> recentUpdates() const; @@ -129,7 +130,6 @@ public: void preloadAnimationsFor(const ReactionId &emoji); void send(not_null item, bool addToRecent); - void sendPaid(not_null item, int count); [[nodiscard]] bool sending(not_null item) const; void poll(not_null item, crl::time now); @@ -143,6 +143,8 @@ public: [[nodiscard]] rpl::producer> myTagsValue( SavedSublist *sublist = nullptr); + void schedulePaid(not_null item); + [[nodiscard]] static bool HasUnread(const MTPMessageReactions &data); static void CheckUnknownForUnread( not_null owner, @@ -233,9 +235,18 @@ private: void resolveEffectImages(); void downloadTaskFinished(); + void fillPaidReactionAnimations() const; + [[nodiscard]] DocumentData *randomLoadedFrom( + std::vector> list) const; + void repaintCollected(); void pollCollected(); + void sendPaid(); + bool sendPaid(not_null item); + void sendPaidRequest(int count); + void sendPaidFinish(FullMsgId id, int count, bool success); + const not_null _owner; std::vector _active; @@ -254,6 +265,7 @@ private: std::vector _topIds; base::flat_set _unresolvedTop; std::vector> _genericAnimations; + mutable std::vector> _paidReactionAnimations; std::vector _effects; ReactionId _favoriteId; ReactionId _unresolvedFavoriteId; @@ -264,6 +276,9 @@ private: base::flat_map< not_null, std::shared_ptr> _genericCache; + mutable base::flat_map< + not_null, + std::shared_ptr> _paidReactionCache; rpl::event_stream<> _topUpdated; rpl::event_stream<> _recentUpdated; rpl::event_stream<> _defaultUpdated; @@ -311,6 +326,10 @@ private: base::flat_set> _pollingItems; mtpRequestId _pollRequestId = 0; + base::flat_map, crl::time> _sendPaidItems; + HistoryItem *_sendingPaid = nullptr; + base::Timer _sendPaidTimer; + mtpRequestId _saveFaveRequestId = 0; rpl::lifetime _lifetime; @@ -323,29 +342,40 @@ struct RecentReaction { bool big = false; bool my = false; - friend inline auto operator<=>( - const RecentReaction &a, - const RecentReaction &b) = default; friend inline bool operator==( const RecentReaction &a, const RecentReaction &b) = default; }; +struct MessageReactionsTopPaid { + not_null peer; + uint32 count : 30 = 0; + uint32 top : 1 = 0; + uint32 my : 1 = 0; + + friend inline bool operator==( + const MessageReactionsTopPaid &a, + const MessageReactionsTopPaid &b) = default; +}; + class MessageReactions final { public: explicit MessageReactions(not_null item); + ~MessageReactions(); + + using TopPaid = MessageReactionsTopPaid; void add(const ReactionId &id, bool addToRecent); - void addPaid(int count); void remove(const ReactionId &id); bool change( const QVector &list, const QVector &recent, - bool ignoreChosen); + const QVector &top, + bool min); [[nodiscard]] bool checkIfChanged( const QVector &list, const QVector &recent, - bool ignoreChosen) const; + bool min) const; [[nodiscard]] const std::vector &list() const; [[nodiscard]] auto recent() const -> const base::flat_map> &; @@ -355,11 +385,27 @@ public: [[nodiscard]] bool hasUnread() const; void markRead(); + void scheduleSendPaid(int count); + [[nodiscard]] int scheduledPaid() const; + void cancelScheduledPaid(); + + int startPaidSending(); + void finishPaidSending(int count, bool success); + + [[nodiscard]] int localPaidCount() const; + bool clearCloudData(); + private: + struct Paid { + std::vector top; + int scheduled = 0; + int sending = 0; + }; const not_null _item; std::vector _list; base::flat_map> _recent; + std::unique_ptr _paid; }; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 5a8e833a2..7d2b3d821 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_emoji_interactions.h" #include "history/history_item_components.h" #include "history/history_item_text.h" +#include "payments/payments_reaction_process.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/menu/menu_multiline_action.h" #include "ui/widgets/popup_menu.h" @@ -490,13 +491,11 @@ void HistoryInner::reactionChosen(const ChosenReaction &reaction) { if (!item) { return; } else if (reaction.id.paid()) { - _controller->show(Ui::MakeConfirmBox({ - .text = u"Send 10-stars reaction?"_q, - .confirmed = [=](Fn close) { - item->addPaidReaction(10, HistoryItem::ReactionSource::Selector); - close(); - }, - })); + Payments::ShowPaidReactionDetails( + _controller->uiShow(), + item, + viewByItem(item), + HistoryReactionSource::Selector); return; } else if (Window::ShowReactPremiumError( _controller, @@ -507,7 +506,7 @@ void HistoryInner::reactionChosen(const ChosenReaction &reaction) { } return; } - item->toggleReaction(reaction.id, HistoryItem::ReactionSource::Selector); + item->toggleReaction(reaction.id, HistoryReactionSource::Selector); if (!ranges::contains(item->chosenReactions(), reaction.id)) { return; } else if (const auto view = viewByItem(item)) { @@ -2047,7 +2046,7 @@ void HistoryInner::toggleFavoriteReaction(not_null view) const { view->animateReaction({ .id = favorite }); } } - item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick); + item->toggleReaction(favorite, HistoryReactionSource::Quick); } HistoryView::SelectedQuote HistoryInner::selectedQuote( diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index e40947033..ba8dc00ca 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -2514,19 +2514,34 @@ bool HistoryItem::canReact() const { return true; } -void HistoryItem::addPaidReaction(int count, ReactionSource source) { +void HistoryItem::addPaidReaction(int count) { + Expects(count > 0); Expects(_history->peer->isBroadcast()); if (!_reactions) { _reactions = std::make_unique(this); } - _reactions->addPaid(count); + _reactions->scheduleSendPaid(count); + _history->owner().notifyItemDataChange(this); +} + +int HistoryItem::startPaidReactionSending() { + return _reactions ? _reactions->startPaidSending() : 0; +} + +void HistoryItem::finishPaidReactionSending(int count, bool success) { + Expects(_reactions != nullptr); + + _reactions->finishPaidSending(count, success); _history->owner().notifyItemDataChange(this); } void HistoryItem::toggleReaction( const Data::ReactionId &reaction, - ReactionSource source) { + HistoryReactionSource source) { + Expects(!reaction.paid()); + + const auto addToRecent = (source == HistoryReactionSource::Selector); if (!_reactions) { _reactions = std::make_unique(this); const auto canViewReactions = !isDiscussionPost() @@ -2534,7 +2549,7 @@ void HistoryItem::toggleReaction( if (canViewReactions) { _flags |= MessageFlag::CanViewReactions; } - _reactions->add(reaction, (source == ReactionSource::Selector)); + _reactions->add(reaction, addToRecent); } else if (ranges::contains(_reactions->chosen(), reaction)) { _reactions->remove(reaction); if (_reactions->empty()) { @@ -2542,7 +2557,7 @@ void HistoryItem::toggleReaction( _flags &= ~MessageFlag::CanViewReactions; } } else { - _reactions->add(reaction, (source == ReactionSource::Selector)); + _reactions->add(reaction, addToRecent); } _history->owner().notifyItemDataChange(this); } @@ -2556,6 +2571,30 @@ const std::vector &HistoryItem::reactions() const { return _reactions ? _reactions->list() : kEmpty; } +std::vector HistoryItem::reactionsWithLocal() const { + auto result = reactions(); + if (const auto local = _reactions ? _reactions->localPaidCount() : 0) { + const auto i = ranges::find( + result, + Data::ReactionId::Paid(), + &Data::MessageReaction::id); + if (i != end(result)) { + i->my = true; + i->count += local; + if (i != begin(result)) { + std::rotate(begin(result), i, i + 1); + } + } else { + result.insert(begin(result), Data::MessageReaction{ + .id = Data::ReactionId::Paid(), + .count = local, + .my = true, + }); + } + } + return result; +} + bool HistoryItem::reactionsAreTags() const { return _flags & MessageFlag::ReactionsAreTags; } @@ -3729,12 +3768,21 @@ bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) { if (reactions || _reactionsLastRefreshed) { _reactionsLastRefreshed = crl::now(); } + const auto changeToEmpty = [&] { + if (!_reactions) { + return false; + } else if (!_reactions->localPaidCount()) { + _reactions = nullptr; + return true; + } + return _reactions->clearCloudData(); + }; if (!reactions) { _flags &= ~MessageFlag::CanViewReactions; if (_history->peer->isSelf()) { _flags |= MessageFlag::ReactionsAreTags; } - return (base::take(_reactions) != nullptr); + return changeToEmpty(); } const auto &data = reactions->data(); const auto empty = data.vresults().v.isEmpty(); @@ -3750,13 +3798,14 @@ bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) { _flags &= ~MessageFlag::CanViewReactions; } if (empty) { - return (base::take(_reactions) != nullptr); + return changeToEmpty(); } else if (!_reactions) { _reactions = std::make_unique(this); } const auto min = data.is_min(); const auto &list = data.vresults().v; const auto &recent = data.vrecent_reactions().value_or_empty(); + const auto &top = data.vtop_reactors().value_or_empty(); if (min && hasUnreadReaction()) { // We can't update reactions from min if we have unread. if (_reactions->checkIfChanged(list, recent, min)) { @@ -3764,7 +3813,7 @@ bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) { } return false; } - return _reactions->change(list, recent, min); + return _reactions->change(list, recent, top, min); } void HistoryItem::applyTTL(const MTPDmessage &data) { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 9e852c77e..bead81aec 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -107,6 +107,12 @@ struct HistoryItemCommonFields { HistoryMessageMarkupData markup; }; +enum class HistoryReactionSource : char { + Selector, + Quick, + Existing, +}; + class HistoryItem final : public RuntimeComposer { public: [[nodiscard]] static std::unique_ptr CreateMedia( @@ -435,18 +441,17 @@ public: void translationDone(LanguageId to, TextWithEntities result); [[nodiscard]] bool canReact() const; - enum class ReactionSource { - Selector, - Quick, - Existing, - }; void toggleReaction( const Data::ReactionId &reaction, - ReactionSource source); - void addPaidReaction(int count, ReactionSource source); + HistoryReactionSource source); + void addPaidReaction(int count); + [[nodiscard]] int startPaidReactionSending(); + void finishPaidReactionSending(int count, bool success); void updateReactionsUnknown(); [[nodiscard]] auto reactions() const -> const std::vector &; + [[nodiscard]] auto reactionsWithLocal() const + -> std::vector; [[nodiscard]] auto recentReactions() const -> const base::flat_map< Data::ReactionId, diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 49013f1c8..6bda63135 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -1549,9 +1549,7 @@ void ShowTagMenu( if (const auto item = owner->message(itemId)) { const auto &list = item->reactions(); if (ranges::contains(list, id, &MessageReaction::id)) { - item->toggleReaction( - id, - HistoryItem::ReactionSource::Quick); + item->toggleReaction(id, HistoryReactionSource::Quick); } } }; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 4155adf9b..97aa4e81f 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -2650,7 +2650,7 @@ void ListWidget::toggleFavoriteReaction(not_null view) const { view->animateReaction({ .id = favorite }); } } - item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick); + item->toggleReaction(favorite, HistoryReactionSource::Quick); } void ListWidget::trySwitchToWordSelection() { @@ -2807,9 +2807,7 @@ void ListWidget::reactionChosen(ChosenReaction reaction) { } return; } - item->toggleReaction( - reaction.id, - HistoryItem::ReactionSource::Selector); + item->toggleReaction(reaction.id, HistoryReactionSource::Selector); if (!ranges::contains(item->chosenReactions(), reaction.id)) { return; } else if (const auto view = viewForItem(item)) { diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 14597582d..cb408e7fc 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "mainwidget.h" #include "main/main_session.h" +#include "payments/payments_reaction_process.h" // TryAddingPaidReaction. #include "ui/text/text_options.h" #include "ui/painter.h" #include "window/window_session_controller.h" @@ -808,11 +809,7 @@ QSize Message::performCountOptimalSize() { const auto markup = item->inlineReplyMarkup(); const auto reactionsKey = [&] { - return embedReactionsInBottomInfo() - ? 0 - : embedReactionsInBubble() - ? 1 - : 2; + return embedReactionsInBubble() ? 0 : 1; }; const auto oldKey = reactionsKey(); validateText(); @@ -3248,97 +3245,62 @@ bool Message::isSignedAuthorElided() const { return _bottomInfo.isSignedAuthorElided(); } -bool Message::embedReactionsInBottomInfo() const { - return false; -#if 0 // legacy - const auto item = data(); - const auto user = item->history()->peer->asUser(); - if (!user - || user->isPremium() - || user->isSelf() - || user->session().premium()) { - // Only in messages of a non premium user with a non premium user. - // In saved messages we use reactions for tags, we don't embed them. - return false; - } - auto seenMy = false; - auto seenHis = false; - for (const auto &reaction : item->reactions()) { - if (reaction.id.custom()) { - // Only in messages without any custom emoji reactions. - return false; - } - // Only in messages without two reactions from the same person. - if (reaction.my) { - if (seenMy) { - return false; - } - seenMy = true; - } - if (!reaction.my || (reaction.count > 1)) { - if (seenHis) { - return false; - } - seenHis = true; - } - } - return true; -#endif -} - bool Message::embedReactionsInBubble() const { return needInfoDisplay(); } void Message::refreshReactions() { - const auto item = data(); - const auto &list = item->reactions(); - if (list.empty() || embedReactionsInBottomInfo()) { + using namespace Reactions; + auto reactionsData = InlineListDataFromMessage(this); + if (reactionsData.reactions.empty()) { setReactions(nullptr); return; } - using namespace Reactions; - auto reactionsData = InlineListDataFromMessage(this); if (!_reactions) { const auto handlerFactory = [=](ReactionId id) { const auto weak = base::make_weak(this); return std::make_shared([=]( ClickContext context) { - if (const auto strong = weak.get()) { - const auto item = strong->data(); - if (id.paid()) { - item->addPaidReaction( - 1, - HistoryItem::ReactionSource::Existing); - return; - } else if (item->reactionsAreTags()) { - if (item->history()->session().premium()) { - const auto tag = Data::SearchTagToQuery(id); - HashtagClickHandler(tag).onClick(context); - } else if (const auto controller - = ExtractController(context)) { - ShowPremiumPreviewBox( - controller, - PremiumFeature::TagsForMessages); - } - return; + const auto strong = weak.get(); + if (!strong) { + return; + } + const auto item = strong->data(); + const auto controller = ExtractController(context); + if (item->reactionsAreTags()) { + if (item->history()->session().premium()) { + const auto tag = Data::SearchTagToQuery(id); + HashtagClickHandler(tag).onClick(context); + } else if (controller) { + ShowPremiumPreviewBox( + controller, + PremiumFeature::TagsForMessages); } - item->toggleReaction( - id, - HistoryItem::ReactionSource::Existing); - if (const auto now = weak.get()) { - const auto chosen = now->data()->chosenReactions(); - if (ranges::contains(chosen, id)) { - now->animateReaction({ - .id = id, - }); - } + return; + } + if (id.paid()) { + Payments::TryAddingPaidReaction( + item, + weak.get(), + 1, + controller->uiShow()); + return; + } else { + const auto source = HistoryReactionSource::Existing; + item->toggleReaction(id, source); + } + if (const auto now = weak.get()) { + const auto chosen = now->data()->chosenReactions(); + if (id.paid() || ranges::contains(chosen, id)) { + now->animateReaction({ + .id = id, + }); } } }); }; setReactions(std::make_unique( - &item->history()->owner().reactions(), + &history()->owner().reactions(), handlerFactory, [=] { customEmojiRepaint(); }, std::move(reactionsData))); diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 5c61fdaaa..48a45bb03 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -77,7 +77,6 @@ public: [[nodiscard]] const HistoryMessageEdited *displayedEditBadge() const; [[nodiscard]] HistoryMessageEdited *displayedEditBadge(); - [[nodiscard]] bool embedReactionsInBottomInfo() const; [[nodiscard]] bool embedReactionsInBubble() const; int marginTop() const override; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp index 7dcf5929b..71c0a6c49 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp @@ -791,7 +791,7 @@ InlineListData InlineListDataFromMessage(not_null message) { using Flag = InlineListData::Flag; const auto item = message->data(); auto result = InlineListData(); - result.reactions = item->reactions(); + result.reactions = item->reactionsWithLocal(); if (const auto user = item->history()->peer->asUser()) { // Always show userpics, we have all information. result.recent.reserve(result.reactions.size()); diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index 5dd80e988..7f398e0a7 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/file_upload.h" #include "storage/storage_account.h" #include "storage/storage_facade.h" +#include "data/components/credits.h" #include "data/components/factchecks.h" #include "data/components/location_pickers.h" #include "data/components/recent_peers.h" @@ -115,6 +116,7 @@ Session::Session( std::make_unique(this, Data::TopPeerType::BotApp)) , _factchecks(std::make_unique(this)) , _locationPickers(std::make_unique()) +, _credits(std::make_unique(this)) , _cachedReactionIconFactory(std::make_unique()) , _supportHelper(Support::Helper::Create(this)) , _saveSettingsTimer([=] { saveSettings(); }) { @@ -290,14 +292,6 @@ bool Session::premiumCanBuy() const { return _premiumPossible.current(); } -rpl::producer Session::creditsValue() const { - return _credits.value(); -} - -void Session::setCredits(uint64 credits) { - _credits = credits; -} - bool Session::isTestMode() const { return mtp().isTestMode(); } diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index ba5dbcd99..0a6917f38 100644 --- a/Telegram/SourceFiles/main/main_session.h +++ b/Telegram/SourceFiles/main/main_session.h @@ -37,6 +37,7 @@ class SponsoredMessages; class TopPeers; class Factchecks; class LocationPickers; +class Credits; } // namespace Data namespace HistoryView::Reactions { @@ -102,9 +103,6 @@ public: [[nodiscard]] bool premiumBadgesShown() const; [[nodiscard]] bool premiumCanBuy() const; - [[nodiscard]] rpl::producer creditsValue() const; - void setCredits(uint64 credits); - [[nodiscard]] bool isTestMode() const; [[nodiscard]] uint64 uniqueId() const; // userId() with TestDC shift. [[nodiscard]] UserId userId() const; @@ -138,6 +136,9 @@ public: [[nodiscard]] Data::LocationPickers &locationPickers() const { return *_locationPickers; } + [[nodiscard]] Data::Credits &credits() const { + return *_credits; + } [[nodiscard]] Api::Updates &updates() const { return *_updates; } @@ -268,6 +269,7 @@ private: const std::unique_ptr _topBotApps; const std::unique_ptr _factchecks; const std::unique_ptr _locationPickers; + const std::unique_ptr _credits; using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory; const std::unique_ptr _cachedReactionIconFactory; @@ -275,7 +277,6 @@ private: const std::unique_ptr _supportHelper; std::shared_ptr _selfUserpicView; - rpl::variable _credits = 0; rpl::variable _premiumPossible = false; rpl::event_stream _termsLockChanges; diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index b62987a82..d05c256d4 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_credits.h" #include "base/unixtime.h" #include "boxes/send_credits_box.h" +#include "data/components/credits.h" #include "data/data_credits.h" #include "data/data_photo.h" #include "data/data_user.h" @@ -39,27 +40,35 @@ bool IsCreditsInvoice(not_null item) { } void ProcessCreditsPayment( - std::shared_ptr show, - QPointer fireworks, - std::shared_ptr form, - Fn maybeReturnToBot) { - const auto lifetime = std::make_shared(); - const auto api = lifetime->make_state( - show->session().user()); - const auto sendBox = [=] { + std::shared_ptr show, + QPointer fireworks, + std::shared_ptr form, + Fn maybeReturnToBot) { + const auto done = [=](Settings::SmallBalanceResult result) { + if (result == Settings::SmallBalanceResult::Blocked) { + if (const auto onstack = maybeReturnToBot) { + onstack(CheckoutResult::Failed); + } + return; + } else if (result == Settings::SmallBalanceResult::Cancelled) { + if (const auto onstack = maybeReturnToBot) { + onstack(CheckoutResult::Cancelled); + } + return; + } const auto unsuccessful = std::make_shared(true); const auto box = show->show(Box( Ui::SendCreditsBox, form, [=] { - *unsuccessful = false; - if (const auto widget = fireworks.data()) { - Ui::StartFireworks(widget); - } - if (maybeReturnToBot) { - maybeReturnToBot(CheckoutResult::Paid); - } - })); + *unsuccessful = false; + if (const auto widget = fireworks.data()) { + Ui::StartFireworks(widget); + } + if (maybeReturnToBot) { + maybeReturnToBot(CheckoutResult::Paid); + } + })); box->boxClosing() | rpl::start_with_next([=] { crl::on_main([=] { if ((*unsuccessful) && maybeReturnToBot) { @@ -68,28 +77,11 @@ void ProcessCreditsPayment( }); }, box->lifetime()); }; - api->request({}, [=](Data::CreditsStatusSlice slice) { - show->session().setCredits(slice.balance); - const auto creditsNeeded = int64(form->invoice.credits) - - int64(slice.balance); - if (creditsNeeded <= 0) { - sendBox(); - } else if (show->session().premiumPossible()) { - show->show(Box( - Settings::SmallBalanceBox, - show, - creditsNeeded, - form->botId, - sendBox)); - } else { - show->showToast( - tr::lng_credits_purchase_blocked(tr::now)); - if (maybeReturnToBot) { - maybeReturnToBot(CheckoutResult::Failed); - } - } - lifetime->destroy(); - }); + Settings::MaybeRequestBalanceIncrease( + show, + form->invoice.credits, + Settings::SmallBalanceBot{ .botId = form->botId }, + done); } void ProcessCreditsReceipt( diff --git a/Telegram/SourceFiles/payments/payments_reaction_process.cpp b/Telegram/SourceFiles/payments/payments_reaction_process.cpp new file mode 100644 index 000000000..a5248776a --- /dev/null +++ b/Telegram/SourceFiles/payments/payments_reaction_process.cpp @@ -0,0 +1,190 @@ +/* +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 "payments/payments_reaction_process.h" + +#include "api/api_credits.h" +#include "boxes/send_credits_box.h" // CreditsEmojiSmall. +#include "core/ui_integration.h" // MarkedTextContext. +#include "data/components/credits.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "history/view/history_view_element.h" +#include "history/history.h" +#include "history/history_item.h" +#include "lang/lang_keys.h" +#include "main/session/session_show.h" +#include "main/main_app_config.h" +#include "main/main_session.h" +#include "payments/ui/payments_reaction_box.h" +#include "settings/settings_credits_graphics.h" +#include "ui/effects/reaction_fly_animation.h" +#include "ui/layers/box_content.h" +#include "ui/layers/generic_box.h" +#include "ui/layers/show.h" +#include "ui/text/text_utilities.h" + +namespace Payments { +namespace { + +constexpr auto kMaxPerReactionFallback = 2'500; +constexpr auto kDefaultPerReaction = 20; + +void TryAddingPaidReaction( + not_null session, + FullMsgId itemId, + base::weak_ptr weakView, + int count, + std::shared_ptr show, + Fn finished) { + const auto checkItem = [=] { + const auto item = session->data().message(itemId); + if (!item) { + if (const auto onstack = finished) { + onstack(false); + } + } + return item; + }; + + const auto item = checkItem(); + if (!item) { + return; + } + const auto done = [=](Settings::SmallBalanceResult result) { + if (result == Settings::SmallBalanceResult::Success) { + if (const auto item = checkItem()) { + item->addPaidReaction(count); + if (const auto view = weakView.get()) { + view->animateReaction({ + .id = Data::ReactionId::Paid(), + }); + } + if (const auto onstack = finished) { + onstack(true); + } + } + } else if (const auto onstack = finished) { + onstack(false); + } + }; + const auto channelId = peerToChannel(itemId.peer); + Settings::MaybeRequestBalanceIncrease( + Main::MakeSessionShow(show, session), + count, + Settings::SmallBalanceReaction{ .channelId = channelId }, + done); +} + +} // namespace + +void TryAddingPaidReaction( + not_null item, + HistoryView::Element *view, + int count, + std::shared_ptr show, + Fn finished) { + TryAddingPaidReaction( + &item->history()->session(), + item->fullId(), + view, + count, + std::move(show), + std::move(finished)); +} + +void ShowPaidReactionDetails( + std::shared_ptr show, + not_null item, + HistoryView::Element *view, + HistoryReactionSource source) { + Expects(item->history()->peer->isBroadcast()); + + const auto itemId = item->fullId(); + const auto session = &item->history()->session(); + const auto appConfig = &session->appConfig(); + + const auto min = 1; + const auto max = std::max( + appConfig->get( + u"stars_paid_reaction_amount_max"_q, + kMaxPerReactionFallback), + min); + const auto chosen = std::clamp(kDefaultPerReaction, min, max); + + struct State { + QPointer selectBox; + bool sending = false; + }; + const auto state = std::make_shared(); + session->credits().load(true); + + const auto weakView = base::make_weak(view); + const auto send = [=](int count, auto resend) -> void { + Expects(count > 0); + + const auto finish = [=](bool success) { + state->sending = false; + if (success) { + if (const auto strong = state->selectBox.data()) { + strong->closeBox(); + } + } + }; + if (state->sending) { + return; + } else if (const auto item = session->data().message(itemId)) { + state->sending = true; + TryAddingPaidReaction( + item, + weakView.get(), + count, + show, + finish); + } + }; + + auto submitText = [=](rpl::producer amount) { + auto nice = std::move(amount) | rpl::map([=](int count) { + return Ui::CreditsEmojiSmall(session).append( + Lang::FormatCountDecimal(count)); + }); + return tr::lng_paid_react_send( + lt_price, + std::move(nice), + Ui::Text::RichLangValue + ) | rpl::map([=](TextWithEntities &&text) { + return Ui::TextWithContext{ + .text = std::move(text), + .context = Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = [] {}, + }, + }; + }); + }; + state->selectBox = show->show(Ui::MakePaidReactionBox({ + .min = min, + .max = max, + .chosen = chosen, + .channel = item->history()->peer->name(), + .submit = std::move(submitText), + .balanceValue = session->credits().balanceValue(), + .send = [=](int count) { send(count, send); }, + })); + + if (const auto strong = state->selectBox.data()) { + session->data().itemRemoved( + ) | rpl::start_with_next([=](not_null removed) { + if (removed == item) { + strong->closeBox(); + } + }, strong->lifetime()); + } +} + +} // namespace Payments diff --git a/Telegram/SourceFiles/payments/payments_reaction_process.h b/Telegram/SourceFiles/payments/payments_reaction_process.h new file mode 100644 index 000000000..9bd6c0457 --- /dev/null +++ b/Telegram/SourceFiles/payments/payments_reaction_process.h @@ -0,0 +1,37 @@ +/* +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 +*/ +#pragma once + +enum class HistoryReactionSource : char; + +class HistoryItem; + +namespace HistoryView { +class Element; +} // namespace HistoryView + +namespace Ui { +class Show; +} // namespace Ui + +namespace Payments { + +void TryAddingPaidReaction( + not_null item, + HistoryView::Element *view, + int count, + std::shared_ptr show, + Fn finished = nullptr); + +void ShowPaidReactionDetails( + std::shared_ptr show, + not_null item, + HistoryView::Element *view, + HistoryReactionSource source); + +} // namespace Payments diff --git a/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp b/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp new file mode 100644 index 000000000..30550786f --- /dev/null +++ b/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp @@ -0,0 +1,148 @@ +/* +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 "payments/ui/payments_reaction_box.h" + +#include "lang/lang_keys.h" +#include "ui/layers/generic_box.h" +#include "ui/text/text_utilities.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/continuous_sliders.h" +#include "styles/style_credits.h" +#include "styles/style_layers.h" +#include "styles/style_premium.h" +#include "styles/style_settings.h" + +namespace Settings { +[[nodiscard]] not_null AddBalanceWidget( + not_null parent, + rpl::producer balanceValue, + bool rightAlign); +} // namespace Settings + +namespace Ui { +namespace { + +void PaidReactionSlider( + not_null container, + int min, + int current, + int max, + Fn changed) { + const auto top = st::boxTitleClose.height + st::creditsHistoryRightSkip; + const auto slider = container->add( + object_ptr(container, st::settingsScale), + st::boxRowPadding + QMargins(0, top, 0, 0)); + slider->resize(slider->width(), st::settingsScale.seekSize.height()); + slider->setPseudoDiscrete( + max + 1 - min, + [=](int index) { return min + index; }, + current - min, + changed, + changed); +} + +} // namespace + +void PaidReactionsBox( + not_null box, + PaidReactionBoxArgs &&args) { + box->setWidth(st::boxWideWidth); + box->setStyle(st::boostBox); + box->setNoContentMargin(true); + + struct State { + rpl::variable chosen; + }; + const auto state = box->lifetime().make_state(); + state->chosen = args.chosen; + const auto changed = [=](int count) { + state->chosen = count; + }; + PaidReactionSlider( + box->verticalLayout(), + args.min, + args.chosen, + args.max, + changed); + + box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); + + box->addRow( + object_ptr( + box, + tr::lng_paid_react_title(), + st::boostCenteredTitle), + st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0)); + box->addRow( + object_ptr( + box, + tr::lng_paid_react_about( + lt_channel, + rpl::single(Text::Bold(args.channel)), + Text::RichLangValue), + st::boostText), + (st::boxRowPadding + + QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip))); + + const auto button = box->addButton(rpl::single(QString()), [=] { + args.send(state->chosen.current()); + }); + { + const auto buttonLabel = Ui::CreateChild( + button, + rpl::single(QString()), + st::creditsBoxButtonLabel); + args.submit( + state->chosen.value() + ) | rpl::start_with_next([=](const TextWithContext &text) { + buttonLabel->setMarkedText( + text.text, + text.context); + }, buttonLabel->lifetime()); + buttonLabel->setTextColorOverride( + box->getDelegate()->style().button.textFg->c); + button->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + buttonLabel->moveToLeft( + (size.width() - buttonLabel->width()) / 2, + (size.height() - buttonLabel->height()) / 2); + }, buttonLabel->lifetime()); + buttonLabel->setAttribute(Qt::WA_TransparentForMouseEvents); + } + + box->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto &padding = st::boostBox.buttonPadding; + button->resizeToWidth(width + - padding.left() + - padding.right()); + button->moveToLeft(padding.left(), button->y()); + }, button->lifetime()); + + { + const auto balance = Settings::AddBalanceWidget( + box->verticalLayout(), + std::move(args.balanceValue), + false); + rpl::combine( + balance->sizeValue(), + box->widthValue() + ) | rpl::start_with_next([=] { + balance->moveToLeft( + st::creditsHistoryRightSkip * 2, + st::creditsHistoryRightSkip); + balance->update(); + }, balance->lifetime()); + } +} + +object_ptr MakePaidReactionBox(PaidReactionBoxArgs &&args) { + return Box(PaidReactionsBox, std::move(args)); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/payments/ui/payments_reaction_box.h b/Telegram/SourceFiles/payments/ui/payments_reaction_box.h new file mode 100644 index 000000000..9da69c368 --- /dev/null +++ b/Telegram/SourceFiles/payments/ui/payments_reaction_box.h @@ -0,0 +1,40 @@ +/* +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 +*/ +#pragma once + +#include "base/object_ptr.h" + +namespace Ui { + +class BoxContent; +class GenericBox; + +struct TextWithContext { + TextWithEntities text; + std::any context; +}; + +struct PaidReactionBoxArgs { + int min = 0; + int max = 0; + int chosen = 0; + + QString channel; + Fn(rpl::producer amount)> submit; + rpl::producer balanceValue; + Fn send; +}; + +void PaidReactionsBox( + not_null box, + PaidReactionBoxArgs &&args); + +[[nodiscard]] object_ptr MakePaidReactionBox( + PaidReactionBoxArgs &&args); + +} // namespace Ui diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 0d7d12490..d413551f0 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/gift_credits_box.h" #include "boxes/gift_premium_box.h" #include "core/click_handler_types.h" +#include "data/components/credits.h" #include "data/data_file_origin.h" #include "data/data_photo_media.h" #include "data/data_session.h" @@ -360,13 +361,9 @@ QPointer Credits::createPinnedToTop( { const auto balance = AddBalanceWidget( content, - _controller->session().creditsValue(), + _controller->session().credits().balanceValue(), true); - const auto api = balance->lifetime().make_state( - _controller->session().user()); - api->request({}, [=](Data::CreditsStatusSlice slice) { - _controller->session().setCredits(slice.balance); - }); + _controller->session().credits().load(true); rpl::combine( balance->sizeValue(), content->sizeValue() diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index c620f19d9..57c24f88a 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/click_handler_types.h" #include "core/click_handler_types.h" // UrlClickHandler #include "core/ui_integration.h" +#include "data/components/credits.h" #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_file_origin.h" @@ -416,16 +417,20 @@ not_null AddBalanceWidget( }); count->draw(p, { .position = QPoint( - balance->width() - count->maxWidth(), + (rightAlign + ? (balance->width() - count->maxWidth()) + : (starSize.width() + diffBetweenStarAndCount)), label->minHeight() + (starSize.height() - count->minHeight()) / 2), .availableWidth = balance->width(), }); p.drawImage( - balance->width() - - count->maxWidth() - - starSize.width() - - diffBetweenStarAndCount, + (rightAlign + ? (balance->width() + - count->maxWidth() + - starSize.width() + - diffBetweenStarAndCount) + : 0), label->minHeight(), *balanceStar); }, balance->lifetime()); @@ -859,9 +864,11 @@ object_ptr PaidMediaThumbnail( void SmallBalanceBox( not_null box, std::shared_ptr show, - int creditsNeeded, - UserId botId, + uint64 credits, + SmallBalanceSource source, Fn paid) { + Expects(show->session().credits().loaded()); + box->setWidth(st::boxWideWidth); box->addButton(tr::lng_close(), [=] { box->closeBox(); }); const auto done = [=] { @@ -869,8 +876,17 @@ void SmallBalanceBox( paid(); }; - const auto bot = show->session().data().user(botId).get(); + const auto owner = &show->session().data(); + const auto peer = v::match(source, [&](SmallBalanceBot value) { + return owner->peer(peerFromUser(value.botId)); + }, [&](SmallBalanceReaction value) { + return owner->peer(peerFromChannel(value.channelId)); + }); + auto needed = show->session().credits().balanceValue( + ) | rpl::map([=](uint64 balance) { + return (balance < credits) ? (credits - balance) : 0; + }); const auto content = [&]() -> Ui::Premium::TopBarAbstract* { return box->setPinnedToTopContent(object_ptr( box, @@ -878,11 +894,18 @@ void SmallBalanceBox( Ui::Premium::TopBarDescriptor{ .title = tr::lng_credits_small_balance_title( lt_count, - rpl::single(creditsNeeded) | tr::to_count()), - .about = tr::lng_credits_small_balance_about( - lt_bot, - rpl::single(TextWithEntities{ bot->name() }), - Ui::Text::RichLangValue), + rpl::duplicate( + needed + ) | rpl::filter(rpl::mappers::_1 > 0) | tr::to_count()), + .about = (peer->isBroadcast() + ? tr::lng_credits_small_balance_reaction( + lt_channel, + rpl::single(Ui::Text::Bold(peer->name())), + Ui::Text::RichLangValue) + : tr::lng_credits_small_balance_about( + lt_bot, + rpl::single(TextWithEntities{ peer->name() }), + Ui::Text::RichLangValue)), .light = true, .gradientStops = Ui::Premium::CreditsIconGradientStops(), })); @@ -892,8 +915,8 @@ void SmallBalanceBox( show, box->verticalLayout(), show->session().user(), - creditsNeeded, - done); + credits - show->session().credits().balance(), + [=] { show->session().credits().load(true); }); content->setMaximumHeight(st::creditsLowBalancePremiumCoverHeight); content->setMinimumHeight(st::infoLayerTopBarHeight); @@ -912,13 +935,10 @@ void SmallBalanceBox( { const auto balance = AddBalanceWidget( content, - show->session().creditsValue(), + show->session().credits().balanceValue(), true); - const auto api = balance->lifetime().make_state( - show->session().user()); - api->request({}, [=](Data::CreditsStatusSlice slice) { - show->session().setCredits(slice.balance); - }); + show->session().credits().load(true); + rpl::combine( balance->sizeValue(), content->sizeValue() @@ -929,6 +949,12 @@ void SmallBalanceBox( balance->update(); }, balance->lifetime()); } + + std::move( + needed + ) | rpl::filter( + !rpl::mappers::_1 + ) | rpl::start_with_next(done, content->lifetime()); } void AddWithdrawalWidget( @@ -1229,4 +1255,58 @@ void AddWithdrawalWidget( Ui::AddSkip(container); } +void MaybeRequestBalanceIncrease( + std::shared_ptr show, + uint64 credits, + SmallBalanceSource source, + Fn done) { + struct State { + rpl::lifetime lifetime; + bool success = false; + }; + const auto state = std::make_shared(); + + const auto session = &show->session(); + session->credits().load(); + session->credits().loadedValue( + ) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] { + state->lifetime.destroy(); + + const auto balance = session->credits().balance(); + if (credits <= balance) { + if (const auto onstack = done) { + onstack(SmallBalanceResult::Success); + } + } else if (show->session().premiumPossible()) { + const auto success = [=] { + state->success = true; + if (const auto onstack = done) { + onstack(SmallBalanceResult::Success); + } + }; + const auto box = show->show(Box( + Settings::SmallBalanceBox, + show, + credits, + source, + success)); + box->boxClosing() | rpl::start_with_next([=] { + crl::on_main([=] { + if (!state->success) { + if (const auto onstack = done) { + onstack(SmallBalanceResult::Cancelled); + } + } + }); + }, box->lifetime()); + } else { + show->showToast( + tr::lng_credits_purchase_blocked(tr::now)); + if (const auto onstack = done) { + onstack(SmallBalanceResult::Blocked); + } + } + }, state->lifetime); +} + } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index e07262d6d..9a8f05e3e 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -87,12 +87,36 @@ void ShowRefundInfoBox( int totalCount, int photoSize); +struct SmallBalanceBot { + UserId botId = 0; +}; +struct SmallBalanceReaction { + ChannelId channelId = 0; +}; +struct SmallBalanceSource : std::variant< + SmallBalanceBot, + SmallBalanceReaction> { + using variant::variant; +}; + void SmallBalanceBox( not_null box, std::shared_ptr show, - int creditsNeeded, - UserId botId, + uint64 credits, + SmallBalanceSource source, Fn paid); +enum class SmallBalanceResult { + Success, + Blocked, + Cancelled, +}; + +void MaybeRequestBalanceIncrease( + std::shared_ptr show, + uint64 credits, + SmallBalanceSource source, + Fn done); + } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index 363ad342f..402b34159 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/vertical_list.h" #include "info/profile/info_profile_badge.h" #include "info/profile/info_profile_emoji_status_panel.h" +#include "data/components/credits.h" #include "data/data_user.h" #include "data/data_session.h" #include "data/data_cloud_themes.h" @@ -492,19 +493,21 @@ void SetupPremium( showOther(PremiumId()); }); { + controller->session().credits().load(); + const auto wrap = container->add( object_ptr>( container, object_ptr(container))); wrap->toggleOn( - controller->session().creditsValue( + controller->session().credits().balanceValue( ) | rpl::map(rpl::mappers::_1 > 0)); wrap->finishAnimating(); AddPremiumStar( AddButtonWithLabel( wrap->entity(), tr::lng_settings_credits(), - controller->session().creditsValue( + controller->session().credits().balanceValue( ) | rpl::map([=](uint64 c) { return c ? Lang::FormatCountToShort(c).string : QString{}; }), @@ -525,12 +528,6 @@ void SetupPremium( }); Ui::NewBadge::AddToRight(button); - const auto api = button->lifetime().make_state( - controller->session().user()); - api->request({}, [=](Data::CreditsStatusSlice slice) { - controller->session().setCredits(slice.balance); - }); - if (controller->session().premiumCanBuy()) { const auto button = AddButtonWithIcon( container, diff --git a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp index e26c8c9d1..a193bf32d 100644 --- a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp @@ -89,8 +89,8 @@ ReactionFlyAnimation::ReactionFlyAnimation( } else if (args.id.paid()) { const auto fake = owner->lookupPaid(); centerIcon = fake->centerIcon; - aroundAnimation = fake->aroundAnimation; - _centerSizeMultiplier = 1.;// fake->centerIcon ? 1. : 0.5; + aroundAnimation = owner->choosePaidReactionAnimation(); + _centerSizeMultiplier = 0.5; } else { const auto i = ranges::find(list, args.id, &::Data::Reaction::id); if (i == end(list)/* || !i->centerIcon*/) { diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 9bc3f6c76..1bb4eba5d 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -197,6 +197,8 @@ PRIVATE payments/ui/payments_panel.h payments/ui/payments_panel_data.h payments/ui/payments_panel_delegate.h + payments/ui/payments_reaction_box.cpp + payments/ui/payments_reaction_box.h platform/linux/current_geo_location_linux.cpp platform/linux/current_geo_location_linux.h