mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 05:07:10 +02:00
Allow sending paid reactions.
This commit is contained in:
parent
bb3fc17489
commit
9bb1fa8782
36 changed files with 1257 additions and 275 deletions
|
@ -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
|
||||
|
|
BIN
Telegram/Resources/animations/star_reaction/center.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/center.tgs
Normal file
Binary file not shown.
Binary file not shown.
BIN
Telegram/Resources/animations/star_reaction/effect1.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/effect1.tgs
Normal file
Binary file not shown.
BIN
Telegram/Resources/animations/star_reaction/effect2.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/effect2.tgs
Normal file
Binary file not shown.
BIN
Telegram/Resources/animations/star_reaction/effect3.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/effect3.tgs
Normal file
Binary file not shown.
|
@ -2399,6 +2399,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_credits_small_balance_title#one" = "{count} Star Needed";
|
||||
"lng_credits_small_balance_title#other" = "{count} Stars Needed";
|
||||
"lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps.";
|
||||
"lng_credits_small_balance_reaction" = "Buy **Stars** and send them to {channel} to support their posts.";
|
||||
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
|
||||
|
||||
"lng_credits_gift_title" = "Gift Telegram Stars";
|
||||
|
@ -3416,6 +3417,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_paid_about_link_url" = "https://telegram.org/blog/telegram-stars";
|
||||
"lng_paid_price" = "Unlock for {price}";
|
||||
|
||||
"lng_paid_react_title" = "Star Reaction";
|
||||
"lng_paid_react_about" = "Choose how many stars you want to send to {channel} to support this post.";
|
||||
"lng_paid_react_top_title" = "Top Senders";
|
||||
"lng_paid_react_send" = "Send {price}";
|
||||
"lng_paid_react_agree" = "By sending stars, you agree to the {link}.";
|
||||
"lng_paid_react_agree_link" = "Terms of Service";
|
||||
|
||||
"lng_translate_show_original" = "Show Original";
|
||||
"lng_translate_bar_to" = "Translate to {name}";
|
||||
"lng_translate_bar_to_other" = "Translate to {name}";
|
||||
|
|
|
@ -41,7 +41,10 @@
|
|||
<file alias="winners.tgs">../../animations/dice/winners.tgs</file>
|
||||
|
||||
<file alias="star_reaction_appear.tgs">../../animations/star_reaction/appear.tgs</file>
|
||||
<file alias="star_reaction_effect.tgs">../../animations/star_reaction/effect.tgs</file>
|
||||
<file alias="star_reaction_center.tgs">../../animations/star_reaction/center.tgs</file>
|
||||
<file alias="star_reaction_select.tgs">../../animations/star_reaction/select.tgs</file>
|
||||
<file alias="star_reaction_effect1.tgs">../../animations/star_reaction/effect1.tgs</file>
|
||||
<file alias="star_reaction_effect2.tgs">../../animations/star_reaction/effect2.tgs</file>
|
||||
<file alias="star_reaction_effect3.tgs">../../animations/star_reaction/effect3.tgs</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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<Api::CreditsStatus>(
|
||||
session->user());
|
||||
api->request({}, [=](Data::CreditsStatusSlice slice) {
|
||||
session->setCredits(slice.balance);
|
||||
});
|
||||
rpl::combine(
|
||||
balance->sizeValue(),
|
||||
content->sizeValue()
|
||||
|
|
113
Telegram/SourceFiles/data/components/credits.cpp
Normal file
113
Telegram/SourceFiles/data/components/credits.cpp
Normal file
|
@ -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<Main::Session*> 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<Api::CreditsStatus>(_session->user());
|
||||
_loader->request({}, [=](Data::CreditsStatusSlice slice) {
|
||||
_loader = nullptr;
|
||||
apply(slice.balance);
|
||||
});
|
||||
}
|
||||
|
||||
bool Credits::loaded() const {
|
||||
return _lastLoaded != 0;
|
||||
}
|
||||
|
||||
rpl::producer<bool> 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<uint64> 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
|
55
Telegram/SourceFiles/data/components/credits.h
Normal file
55
Telegram/SourceFiles/data/components/credits.h
Normal file
|
@ -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<Main::Session*> session);
|
||||
~Credits();
|
||||
|
||||
void load(bool force = false);
|
||||
void apply(uint64 balance);
|
||||
|
||||
[[nodiscard]] bool loaded() const;
|
||||
[[nodiscard]] rpl::producer<bool> loadedValue() const;
|
||||
|
||||
[[nodiscard]] uint64 balance() const;
|
||||
[[nodiscard]] rpl::producer<uint64> 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<Main::Session*> _session;
|
||||
|
||||
std::unique_ptr<Api::CreditsStatus> _loader;
|
||||
|
||||
rpl::variable<uint64> _balance;
|
||||
rpl::variable<uint64> _locked;
|
||||
rpl::event_stream<> _loadedChanges;
|
||||
crl::time _lastLoaded = 0;
|
||||
|
||||
SingleQueuedInvokation _reload;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
|
@ -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<int>("reactions_user_max_default", 1);
|
||||
}
|
||||
|
||||
bool IsMyRecent(
|
||||
[[nodiscard]] bool IsMyRecent(
|
||||
const MTPDmessagePeerReaction &data,
|
||||
const ReactionId &id,
|
||||
not_null<PeerData*> peer,
|
||||
const base::flat_map<
|
||||
ReactionId,
|
||||
std::vector<RecentReaction>> &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<PeerData*> peer,
|
||||
const std::vector<MessageReactionsTopPaid> &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<Session*> owner)
|
||||
: _owner(owner)
|
||||
, _topRefreshTimer([=] { refreshTop(); })
|
||||
, _repaintTimer([=] { repaintCollected(); }) {
|
||||
, _repaintTimer([=] { repaintCollected(); })
|
||||
, _sendPaidTimer([=] { sendPaid(); }) {
|
||||
refreshDefault();
|
||||
|
||||
_myTags.emplace(nullptr);
|
||||
|
@ -284,6 +303,14 @@ Reactions::Reactions(not_null<Session*> 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<not_null<DocumentData*>> 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<DocumentData*> value) {
|
||||
const auto k = ranges::find_if(list, [&](not_null<DocumentData*> 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<HistoryItem*> item, bool addToRecent) {
|
|||
}).send();
|
||||
}
|
||||
|
||||
void Reactions::sendPaid(not_null<HistoryItem*> 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<HistoryItem*> item, crl::time now) {
|
||||
// Group them by one second.
|
||||
const auto last = item->lastReactionsRefreshTime();
|
||||
|
@ -1460,16 +1505,22 @@ not_null<Reaction*> 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<std::vector<Reaction>> Reactions::myTagsValue(
|
|||
) | rpl::map(list));
|
||||
}
|
||||
|
||||
void Reactions::schedulePaid(not_null<HistoryItem*> 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<HistoryItem*> 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<HistoryItem*> 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<HistoryItem*> 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<MTPReactionCount> &list,
|
||||
const QVector<MTPMessagePeerReaction> &recent,
|
||||
const QVector<MTPMessageReactor> &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<TopPaid>();
|
||||
const auto &paindTopNow = _paid ? _paid->top : std::vector<TopPaid>();
|
||||
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<Paid>();
|
||||
}
|
||||
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>();
|
||||
}
|
||||
_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<ReactionId> MessageReactions::chosen() const {
|
||||
return _list
|
||||
| ranges::views::filter(&MessageReaction::my)
|
||||
|
|
|
@ -106,6 +106,7 @@ public:
|
|||
void renameTag(const ReactionId &id, const QString &name);
|
||||
[[nodiscard]] DocumentData *chooseGenericAnimation(
|
||||
not_null<DocumentData*> 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<HistoryItem*> item, bool addToRecent);
|
||||
void sendPaid(not_null<HistoryItem*> item, int count);
|
||||
[[nodiscard]] bool sending(not_null<HistoryItem*> item) const;
|
||||
|
||||
void poll(not_null<HistoryItem*> item, crl::time now);
|
||||
|
@ -143,6 +143,8 @@ public:
|
|||
[[nodiscard]] rpl::producer<std::vector<Reaction>> myTagsValue(
|
||||
SavedSublist *sublist = nullptr);
|
||||
|
||||
void schedulePaid(not_null<HistoryItem*> item);
|
||||
|
||||
[[nodiscard]] static bool HasUnread(const MTPMessageReactions &data);
|
||||
static void CheckUnknownForUnread(
|
||||
not_null<Session*> owner,
|
||||
|
@ -233,9 +235,18 @@ private:
|
|||
void resolveEffectImages();
|
||||
void downloadTaskFinished();
|
||||
|
||||
void fillPaidReactionAnimations() const;
|
||||
[[nodiscard]] DocumentData *randomLoadedFrom(
|
||||
std::vector<not_null<DocumentData*>> list) const;
|
||||
|
||||
void repaintCollected();
|
||||
void pollCollected();
|
||||
|
||||
void sendPaid();
|
||||
bool sendPaid(not_null<HistoryItem*> item);
|
||||
void sendPaidRequest(int count);
|
||||
void sendPaidFinish(FullMsgId id, int count, bool success);
|
||||
|
||||
const not_null<Session*> _owner;
|
||||
|
||||
std::vector<Reaction> _active;
|
||||
|
@ -254,6 +265,7 @@ private:
|
|||
std::vector<ReactionId> _topIds;
|
||||
base::flat_set<ReactionId> _unresolvedTop;
|
||||
std::vector<not_null<DocumentData*>> _genericAnimations;
|
||||
mutable std::vector<not_null<DocumentData*>> _paidReactionAnimations;
|
||||
std::vector<Reaction> _effects;
|
||||
ReactionId _favoriteId;
|
||||
ReactionId _unresolvedFavoriteId;
|
||||
|
@ -264,6 +276,9 @@ private:
|
|||
base::flat_map<
|
||||
not_null<DocumentData*>,
|
||||
std::shared_ptr<DocumentMedia>> _genericCache;
|
||||
mutable base::flat_map<
|
||||
not_null<DocumentData*>,
|
||||
std::shared_ptr<DocumentMedia>> _paidReactionCache;
|
||||
rpl::event_stream<> _topUpdated;
|
||||
rpl::event_stream<> _recentUpdated;
|
||||
rpl::event_stream<> _defaultUpdated;
|
||||
|
@ -311,6 +326,10 @@ private:
|
|||
base::flat_set<not_null<HistoryItem*>> _pollingItems;
|
||||
mtpRequestId _pollRequestId = 0;
|
||||
|
||||
base::flat_map<not_null<HistoryItem*>, 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<PeerData*> 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<HistoryItem*> 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<MTPReactionCount> &list,
|
||||
const QVector<MTPMessagePeerReaction> &recent,
|
||||
bool ignoreChosen);
|
||||
const QVector<MTPMessageReactor> &top,
|
||||
bool min);
|
||||
[[nodiscard]] bool checkIfChanged(
|
||||
const QVector<MTPReactionCount> &list,
|
||||
const QVector<MTPMessagePeerReaction> &recent,
|
||||
bool ignoreChosen) const;
|
||||
bool min) const;
|
||||
[[nodiscard]] const std::vector<MessageReaction> &list() const;
|
||||
[[nodiscard]] auto recent() const
|
||||
-> const base::flat_map<ReactionId, std::vector<RecentReaction>> &;
|
||||
|
@ -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<TopPaid> top;
|
||||
int scheduled = 0;
|
||||
int sending = 0;
|
||||
};
|
||||
const not_null<HistoryItem*> _item;
|
||||
|
||||
std::vector<MessageReaction> _list;
|
||||
base::flat_map<ReactionId, std::vector<RecentReaction>> _recent;
|
||||
std::unique_ptr<Paid> _paid;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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<void()> 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<Element*> view) const {
|
|||
view->animateReaction({ .id = favorite });
|
||||
}
|
||||
}
|
||||
item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick);
|
||||
item->toggleReaction(favorite, HistoryReactionSource::Quick);
|
||||
}
|
||||
|
||||
HistoryView::SelectedQuote HistoryInner::selectedQuote(
|
||||
|
|
|
@ -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<Data::MessageReactions>(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<Data::MessageReactions>(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<Data::MessageReaction> &HistoryItem::reactions() const {
|
|||
return _reactions ? _reactions->list() : kEmpty;
|
||||
}
|
||||
|
||||
std::vector<Data::MessageReaction> 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<Data::MessageReactions>(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) {
|
||||
|
|
|
@ -107,6 +107,12 @@ struct HistoryItemCommonFields {
|
|||
HistoryMessageMarkupData markup;
|
||||
};
|
||||
|
||||
enum class HistoryReactionSource : char {
|
||||
Selector,
|
||||
Quick,
|
||||
Existing,
|
||||
};
|
||||
|
||||
class HistoryItem final : public RuntimeComposer<HistoryItem> {
|
||||
public:
|
||||
[[nodiscard]] static std::unique_ptr<Data::Media> 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<Data::MessageReaction> &;
|
||||
[[nodiscard]] auto reactionsWithLocal() const
|
||||
-> std::vector<Data::MessageReaction>;
|
||||
[[nodiscard]] auto recentReactions() const
|
||||
-> const base::flat_map<
|
||||
Data::ReactionId,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2650,7 +2650,7 @@ void ListWidget::toggleFavoriteReaction(not_null<Element*> 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)) {
|
||||
|
|
|
@ -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<LambdaClickHandler>([=](
|
||||
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<InlineList>(
|
||||
&item->history()->owner().reactions(),
|
||||
&history()->owner().reactions(),
|
||||
handlerFactory,
|
||||
[=] { customEmojiRepaint(); },
|
||||
std::move(reactionsData)));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -791,7 +791,7 @@ InlineListData InlineListDataFromMessage(not_null<Message*> 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());
|
||||
|
|
|
@ -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<Data::TopPeers>(this, Data::TopPeerType::BotApp))
|
||||
, _factchecks(std::make_unique<Data::Factchecks>(this))
|
||||
, _locationPickers(std::make_unique<Data::LocationPickers>())
|
||||
, _credits(std::make_unique<Data::Credits>(this))
|
||||
, _cachedReactionIconFactory(std::make_unique<ReactionIconFactory>())
|
||||
, _supportHelper(Support::Helper::Create(this))
|
||||
, _saveSettingsTimer([=] { saveSettings(); }) {
|
||||
|
@ -290,14 +292,6 @@ bool Session::premiumCanBuy() const {
|
|||
return _premiumPossible.current();
|
||||
}
|
||||
|
||||
rpl::producer<uint64> Session::creditsValue() const {
|
||||
return _credits.value();
|
||||
}
|
||||
|
||||
void Session::setCredits(uint64 credits) {
|
||||
_credits = credits;
|
||||
}
|
||||
|
||||
bool Session::isTestMode() const {
|
||||
return mtp().isTestMode();
|
||||
}
|
||||
|
|
|
@ -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<uint64> 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<Data::TopPeers> _topBotApps;
|
||||
const std::unique_ptr<Data::Factchecks> _factchecks;
|
||||
const std::unique_ptr<Data::LocationPickers> _locationPickers;
|
||||
const std::unique_ptr<Data::Credits> _credits;
|
||||
|
||||
using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory;
|
||||
const std::unique_ptr<ReactionIconFactory> _cachedReactionIconFactory;
|
||||
|
@ -275,7 +277,6 @@ private:
|
|||
const std::unique_ptr<Support::Helper> _supportHelper;
|
||||
|
||||
std::shared_ptr<QImage> _selfUserpicView;
|
||||
rpl::variable<uint64> _credits = 0;
|
||||
rpl::variable<bool> _premiumPossible = false;
|
||||
|
||||
rpl::event_stream<bool> _termsLockChanges;
|
||||
|
|
|
@ -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<HistoryItem*> item) {
|
|||
}
|
||||
|
||||
void ProcessCreditsPayment(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
QPointer<QWidget> fireworks,
|
||||
std::shared_ptr<CreditsFormData> form,
|
||||
Fn<void(CheckoutResult)> maybeReturnToBot) {
|
||||
const auto lifetime = std::make_shared<rpl::lifetime>();
|
||||
const auto api = lifetime->make_state<Api::CreditsStatus>(
|
||||
show->session().user());
|
||||
const auto sendBox = [=] {
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
QPointer<QWidget> fireworks,
|
||||
std::shared_ptr<CreditsFormData> form,
|
||||
Fn<void(CheckoutResult)> 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<bool>(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(
|
||||
|
|
190
Telegram/SourceFiles/payments/payments_reaction_process.cpp
Normal file
190
Telegram/SourceFiles/payments/payments_reaction_process.cpp
Normal file
|
@ -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<Main::Session*> session,
|
||||
FullMsgId itemId,
|
||||
base::weak_ptr<HistoryView::Element> weakView,
|
||||
int count,
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
Fn<void(bool)> 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<HistoryItem*> item,
|
||||
HistoryView::Element *view,
|
||||
int count,
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
Fn<void(bool)> finished) {
|
||||
TryAddingPaidReaction(
|
||||
&item->history()->session(),
|
||||
item->fullId(),
|
||||
view,
|
||||
count,
|
||||
std::move(show),
|
||||
std::move(finished));
|
||||
}
|
||||
|
||||
void ShowPaidReactionDetails(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<HistoryItem*> 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<int>(
|
||||
u"stars_paid_reaction_amount_max"_q,
|
||||
kMaxPerReactionFallback),
|
||||
min);
|
||||
const auto chosen = std::clamp(kDefaultPerReaction, min, max);
|
||||
|
||||
struct State {
|
||||
QPointer<Ui::BoxContent> selectBox;
|
||||
bool sending = false;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
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<int> 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<const HistoryItem*> removed) {
|
||||
if (removed == item) {
|
||||
strong->closeBox();
|
||||
}
|
||||
}, strong->lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Payments
|
37
Telegram/SourceFiles/payments/payments_reaction_process.h
Normal file
37
Telegram/SourceFiles/payments/payments_reaction_process.h
Normal file
|
@ -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<HistoryItem*> item,
|
||||
HistoryView::Element *view,
|
||||
int count,
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
Fn<void(bool)> finished = nullptr);
|
||||
|
||||
void ShowPaidReactionDetails(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<HistoryItem*> item,
|
||||
HistoryView::Element *view,
|
||||
HistoryReactionSource source);
|
||||
|
||||
} // namespace Payments
|
148
Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp
Normal file
148
Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp
Normal file
|
@ -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<Ui::RpWidget*> AddBalanceWidget(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<uint64> balanceValue,
|
||||
bool rightAlign);
|
||||
} // namespace Settings
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
void PaidReactionSlider(
|
||||
not_null<VerticalLayout*> container,
|
||||
int min,
|
||||
int current,
|
||||
int max,
|
||||
Fn<void(int)> changed) {
|
||||
const auto top = st::boxTitleClose.height + st::creditsHistoryRightSkip;
|
||||
const auto slider = container->add(
|
||||
object_ptr<MediaSlider>(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<GenericBox*> box,
|
||||
PaidReactionBoxArgs &&args) {
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->setStyle(st::boostBox);
|
||||
box->setNoContentMargin(true);
|
||||
|
||||
struct State {
|
||||
rpl::variable<int> chosen;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<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<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_paid_react_title(),
|
||||
st::boostCenteredTitle),
|
||||
st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0));
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
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<Ui::FlatLabel>(
|
||||
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<BoxContent> MakePaidReactionBox(PaidReactionBoxArgs &&args) {
|
||||
return Box(PaidReactionsBox, std::move(args));
|
||||
}
|
||||
|
||||
} // namespace Ui
|
40
Telegram/SourceFiles/payments/ui/payments_reaction_box.h
Normal file
40
Telegram/SourceFiles/payments/ui/payments_reaction_box.h
Normal file
|
@ -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<TextWithContext>(rpl::producer<int> amount)> submit;
|
||||
rpl::producer<uint64> balanceValue;
|
||||
Fn<void(int)> send;
|
||||
};
|
||||
|
||||
void PaidReactionsBox(
|
||||
not_null<GenericBox*> box,
|
||||
PaidReactionBoxArgs &&args);
|
||||
|
||||
[[nodiscard]] object_ptr<BoxContent> MakePaidReactionBox(
|
||||
PaidReactionBoxArgs &&args);
|
||||
|
||||
} // namespace Ui
|
|
@ -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<Ui::RpWidget> Credits::createPinnedToTop(
|
|||
{
|
||||
const auto balance = AddBalanceWidget(
|
||||
content,
|
||||
_controller->session().creditsValue(),
|
||||
_controller->session().credits().balanceValue(),
|
||||
true);
|
||||
const auto api = balance->lifetime().make_state<Api::CreditsStatus>(
|
||||
_controller->session().user());
|
||||
api->request({}, [=](Data::CreditsStatusSlice slice) {
|
||||
_controller->session().setCredits(slice.balance);
|
||||
});
|
||||
_controller->session().credits().load(true);
|
||||
rpl::combine(
|
||||
balance->sizeValue(),
|
||||
content->sizeValue()
|
||||
|
|
|
@ -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<Ui::RpWidget*> 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<Ui::RpWidget> PaidMediaThumbnail(
|
|||
void SmallBalanceBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
int creditsNeeded,
|
||||
UserId botId,
|
||||
uint64 credits,
|
||||
SmallBalanceSource source,
|
||||
Fn<void()> 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<Ui::Premium::TopBar>(
|
||||
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<Api::CreditsStatus>(
|
||||
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<Main::SessionShow> show,
|
||||
uint64 credits,
|
||||
SmallBalanceSource source,
|
||||
Fn<void(SmallBalanceResult)> done) {
|
||||
struct State {
|
||||
rpl::lifetime lifetime;
|
||||
bool success = false;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
|
||||
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
|
||||
|
|
|
@ -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<Ui::GenericBox*> box,
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
int creditsNeeded,
|
||||
UserId botId,
|
||||
uint64 credits,
|
||||
SmallBalanceSource source,
|
||||
Fn<void()> paid);
|
||||
|
||||
enum class SmallBalanceResult {
|
||||
Success,
|
||||
Blocked,
|
||||
Cancelled,
|
||||
};
|
||||
|
||||
void MaybeRequestBalanceIncrease(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
uint64 credits,
|
||||
SmallBalanceSource source,
|
||||
Fn<void(SmallBalanceResult)> done);
|
||||
|
||||
} // namespace Settings
|
||||
|
||||
|
|
|
@ -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<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(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<Api::CreditsStatus>(
|
||||
controller->session().user());
|
||||
api->request({}, [=](Data::CreditsStatusSlice slice) {
|
||||
controller->session().setCredits(slice.balance);
|
||||
});
|
||||
|
||||
if (controller->session().premiumCanBuy()) {
|
||||
const auto button = AddButtonWithIcon(
|
||||
container,
|
||||
|
|
|
@ -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*/) {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue