mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-17 22:57:11 +02:00
Support anonymous paid reactions.
This commit is contained in:
parent
37283a7a35
commit
284f1a5210
14 changed files with 443 additions and 108 deletions
|
@ -3477,6 +3477,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_paid_react_toast_text#one" = "You reacted with **{count} Star**.";
|
||||
"lng_paid_react_toast_text#other" = "You reacted with **{count} Stars**.";
|
||||
"lng_paid_react_undo" = "Undo";
|
||||
"lng_paid_react_show_in_top" = "Show me in Top Senders";
|
||||
"lng_paid_react_anonymous" = "Anonymous";
|
||||
|
||||
"lng_translate_show_original" = "Show Original";
|
||||
"lng_translate_bar_to" = "Translate to {name}";
|
||||
|
|
|
@ -155,7 +155,8 @@ constexpr auto kPaidAccumulatePeriod = 5 * crl::time(1000) + 500;
|
|||
} // namespace
|
||||
|
||||
PossibleItemReactionsRef LookupPossibleReactions(
|
||||
not_null<HistoryItem*> item) {
|
||||
not_null<HistoryItem*> item,
|
||||
bool paidInFront) {
|
||||
if (!item->canReact()) {
|
||||
return {};
|
||||
}
|
||||
|
@ -258,12 +259,15 @@ PossibleItemReactionsRef LookupPossibleReactions(
|
|||
&& premiumPossible;
|
||||
}
|
||||
if (!item->reactionsAreTags()) {
|
||||
const auto i = ranges::find(
|
||||
result.recent,
|
||||
reactions->favoriteId(),
|
||||
&Reaction::id);
|
||||
if (i != end(result.recent) && i != begin(result.recent)) {
|
||||
std::rotate(begin(result.recent), i, i + 1);
|
||||
const auto toFront = [&](Data::ReactionId id) {
|
||||
const auto i = ranges::find(result.recent, id, &Reaction::id);
|
||||
if (i != end(result.recent) && i != begin(result.recent)) {
|
||||
std::rotate(begin(result.recent), i, i + 1);
|
||||
}
|
||||
};
|
||||
toFront(reactions->favoriteId());
|
||||
if (paidInFront) {
|
||||
toFront(Data::ReactionId::Paid());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
@ -1704,31 +1708,71 @@ void Reactions::sendPaid() {
|
|||
}
|
||||
|
||||
bool Reactions::sendPaid(not_null<HistoryItem*> item) {
|
||||
const auto count = item->startPaidReactionSending();
|
||||
if (!count) {
|
||||
const auto send = item->startPaidReactionSending();
|
||||
if (!send.valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sendPaidRequest(item, count);
|
||||
sendPaidRequest(item, send);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Reactions::sendPaidRequest(not_null<HistoryItem*> item, int count) {
|
||||
void Reactions::sendPaidPrivacyRequest(
|
||||
not_null<HistoryItem*> item,
|
||||
PaidReactionSend send) {
|
||||
Expects(!_sendingPaid.contains(item));
|
||||
Expects(!send.count);
|
||||
|
||||
const auto id = item->fullId();
|
||||
auto &api = _owner->session().api();
|
||||
using Flag = MTPmessages_SendPaidReaction::Flag;
|
||||
const auto requestId = api.request(
|
||||
MTPmessages_TogglePaidReactionPrivacy(
|
||||
item->history()->peer->input,
|
||||
MTP_int(id.msg),
|
||||
MTP_bool(send.anonymous))
|
||||
).done([=] {
|
||||
if (const auto item = _owner->message(id)) {
|
||||
if (_sendingPaid.remove(item)) {
|
||||
sendPaidFinish(item, send, true);
|
||||
}
|
||||
}
|
||||
checkQuitPreventFinished();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (const auto item = _owner->message(id)) {
|
||||
if (_sendingPaid.remove(item)) {
|
||||
sendPaidFinish(item, send, false);
|
||||
}
|
||||
}
|
||||
checkQuitPreventFinished();
|
||||
}).send();
|
||||
_sendingPaid[item] = requestId;
|
||||
}
|
||||
|
||||
void Reactions::sendPaidRequest(
|
||||
not_null<HistoryItem*> item,
|
||||
PaidReactionSend send) {
|
||||
Expects(!_sendingPaid.contains(item));
|
||||
|
||||
if (!send.count) {
|
||||
sendPaidPrivacyRequest(item, send);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto id = item->fullId();
|
||||
const auto randomId = base::unixtime::mtproto_msg_id();
|
||||
auto &api = _owner->session().api();
|
||||
using Flag = MTPmessages_SendPaidReaction::Flag;
|
||||
const auto requestId = api.request(MTPmessages_SendPaidReaction(
|
||||
MTP_flags(0),
|
||||
MTP_flags(send.anonymous ? Flag::f_private : Flag()),
|
||||
item->history()->peer->input,
|
||||
MTP_int(id.msg),
|
||||
MTP_int(count),
|
||||
MTP_int(send.count),
|
||||
MTP_long(randomId)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
if (const auto item = _owner->message(id)) {
|
||||
if (_sendingPaid.remove(item)) {
|
||||
sendPaidFinish(item, count, true);
|
||||
sendPaidFinish(item, send, true);
|
||||
}
|
||||
}
|
||||
_owner->session().api().applyUpdates(result);
|
||||
|
@ -1737,9 +1781,9 @@ void Reactions::sendPaidRequest(not_null<HistoryItem*> item, int count) {
|
|||
if (const auto item = _owner->message(id)) {
|
||||
_sendingPaid.remove(item);
|
||||
if (error.type() == u"RANDOM_ID_EXPIRED"_q) {
|
||||
sendPaidRequest(item, count);
|
||||
sendPaidRequest(item, send);
|
||||
} else {
|
||||
sendPaidFinish(item, count, false);
|
||||
sendPaidFinish(item, send, false);
|
||||
}
|
||||
}
|
||||
checkQuitPreventFinished();
|
||||
|
@ -1758,9 +1802,9 @@ void Reactions::checkQuitPreventFinished() {
|
|||
|
||||
void Reactions::sendPaidFinish(
|
||||
not_null<HistoryItem*> item,
|
||||
int count,
|
||||
PaidReactionSend send,
|
||||
bool success) {
|
||||
item->finishPaidReactionSending(count, success);
|
||||
item->finishPaidReactionSending(send, success);
|
||||
sendPaid();
|
||||
}
|
||||
|
||||
|
@ -1772,7 +1816,9 @@ MessageReactions::~MessageReactions() {
|
|||
cancelScheduledPaid();
|
||||
if (const auto paid = _paid.get()) {
|
||||
if (paid->sending > 0) {
|
||||
finishPaidSending(paid->sending, false);
|
||||
finishPaidSending(
|
||||
{ int(paid->sending), (paid->sendingAnonymous == 1) },
|
||||
false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2063,7 +2109,7 @@ bool MessageReactions::change(
|
|||
if (paidTop.empty()) {
|
||||
if (_paid && !_paid->top.empty()) {
|
||||
changed = true;
|
||||
if (localPaidCount()) {
|
||||
if (localPaidData()) {
|
||||
_paid->top.clear();
|
||||
} else {
|
||||
_paid = nullptr;
|
||||
|
@ -2133,14 +2179,18 @@ void MessageReactions::markRead() {
|
|||
}
|
||||
}
|
||||
|
||||
void MessageReactions::scheduleSendPaid(int count) {
|
||||
Expects(count > 0);
|
||||
void MessageReactions::scheduleSendPaid(int count, bool anonymous) {
|
||||
Expects(count >= 0);
|
||||
|
||||
if (!_paid) {
|
||||
_paid = std::make_unique<Paid>();
|
||||
}
|
||||
_paid->scheduled += count;
|
||||
_item->history()->session().credits().lock(count);
|
||||
_paid->scheduledFlag = 1;
|
||||
_paid->scheduledAnonymous = anonymous ? 1 : 0;
|
||||
if (count > 0) {
|
||||
_item->history()->session().credits().lock(count);
|
||||
}
|
||||
_item->history()->owner().reactions().schedulePaid(_item);
|
||||
}
|
||||
|
||||
|
@ -2150,50 +2200,100 @@ int MessageReactions::scheduledPaid() const {
|
|||
|
||||
void MessageReactions::cancelScheduledPaid() {
|
||||
if (_paid) {
|
||||
if (_paid->scheduled > 0) {
|
||||
_item->history()->session().credits().unlock(
|
||||
base::take(_paid->scheduled));
|
||||
if (_paid->scheduledFlag) {
|
||||
if (const auto amount = int(_paid->scheduled)) {
|
||||
_item->history()->session().credits().unlock(amount);
|
||||
}
|
||||
_paid->scheduled = 0;
|
||||
_paid->scheduledFlag = 0;
|
||||
_paid->scheduledAnonymous = 0;
|
||||
}
|
||||
if (!_paid->sending && _paid->top.empty()) {
|
||||
if (!_paid->sendingFlag && _paid->top.empty()) {
|
||||
_paid = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int MessageReactions::startPaidSending() {
|
||||
if (!_paid || !_paid->scheduled || _paid->sending) {
|
||||
return 0;
|
||||
PaidReactionSend MessageReactions::startPaidSending() {
|
||||
if (!_paid || !_paid->scheduledFlag || _paid->sendingFlag) {
|
||||
return {};
|
||||
}
|
||||
_paid->sending = _paid->scheduled;
|
||||
_paid->sendingFlag = _paid->scheduledFlag;
|
||||
_paid->sendingAnonymous = _paid->scheduledAnonymous;
|
||||
_paid->scheduled = 0;
|
||||
return _paid->sending;
|
||||
_paid->scheduledFlag = 0;
|
||||
_paid->scheduledAnonymous = 0;
|
||||
return {
|
||||
.count = int(_paid->sending),
|
||||
.valid = true,
|
||||
.anonymous = (_paid->sendingAnonymous == 1),
|
||||
};
|
||||
}
|
||||
|
||||
void MessageReactions::finishPaidSending(int count, bool success) {
|
||||
Expects(count > 0);
|
||||
void MessageReactions::finishPaidSending(
|
||||
PaidReactionSend send,
|
||||
bool success) {
|
||||
Expects(_paid != nullptr);
|
||||
Expects(count == _paid->sending);
|
||||
Expects(send.count == _paid->sending);
|
||||
Expects(send.valid == (_paid->sendingFlag == 1));
|
||||
Expects(send.anonymous == (_paid->sendingAnonymous == 1));
|
||||
|
||||
_paid->sending = 0;
|
||||
if (!_paid->scheduled && _paid->top.empty()) {
|
||||
_paid->sendingFlag = 0;
|
||||
_paid->sendingAnonymous = 0;
|
||||
if (!_paid->scheduledFlag && _paid->top.empty()) {
|
||||
_paid = nullptr;
|
||||
} else if (!send.count) {
|
||||
const auto i = ranges::find_if(_paid->top, [](const TopPaid &top) {
|
||||
return top.my;
|
||||
});
|
||||
if (i != end(_paid->top)) {
|
||||
i->peer = send.anonymous
|
||||
? nullptr
|
||||
: _item->history()->session().user().get();
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
_item->history()->session().credits().withdrawLocked(count);
|
||||
} else {
|
||||
_item->history()->session().credits().unlock(count);
|
||||
if (const auto amount = send.count) {
|
||||
const auto credits = &_item->history()->session().credits();
|
||||
if (success) {
|
||||
credits->withdrawLocked(amount);
|
||||
} else {
|
||||
credits->unlock(amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MessageReactions::localPaidData() const {
|
||||
return _paid && (_paid->scheduledFlag || _paid->sendingFlag);
|
||||
}
|
||||
|
||||
int MessageReactions::localPaidCount() const {
|
||||
return _paid ? (_paid->scheduled + _paid->sending) : 0;
|
||||
}
|
||||
|
||||
bool MessageReactions::localPaidAnonymous() const {
|
||||
const auto minePaidAnonymous = [&] {
|
||||
for (const auto &entry : _paid->top) {
|
||||
if (entry.my) {
|
||||
return !entry.peer;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return _paid
|
||||
&& (_paid->scheduledFlag
|
||||
? (_paid->scheduledAnonymous == 1)
|
||||
: _paid->sendingFlag
|
||||
? (_paid->sendingAnonymous == 1)
|
||||
: minePaidAnonymous());
|
||||
}
|
||||
|
||||
bool MessageReactions::clearCloudData() {
|
||||
const auto result = !_list.empty();
|
||||
_recent.clear();
|
||||
_list.clear();
|
||||
if (localPaidCount()) {
|
||||
if (localPaidData()) {
|
||||
_paid->top.clear();
|
||||
} else {
|
||||
_paid = nullptr;
|
||||
|
|
|
@ -59,7 +59,8 @@ struct PossibleItemReactions {
|
|||
};
|
||||
|
||||
[[nodiscard]] PossibleItemReactionsRef LookupPossibleReactions(
|
||||
not_null<HistoryItem*> item);
|
||||
not_null<HistoryItem*> item,
|
||||
bool paidInFront = false);
|
||||
|
||||
struct MyTagInfo {
|
||||
ReactionId id;
|
||||
|
@ -67,6 +68,12 @@ struct MyTagInfo {
|
|||
int count = 0;
|
||||
};
|
||||
|
||||
struct PaidReactionSend {
|
||||
int count = 0;
|
||||
bool valid = false;
|
||||
bool anonymous = false;
|
||||
};
|
||||
|
||||
class Reactions final : private CustomEmojiManager::Listener {
|
||||
public:
|
||||
explicit Reactions(not_null<Session*> owner);
|
||||
|
@ -250,10 +257,15 @@ private:
|
|||
|
||||
void sendPaid();
|
||||
bool sendPaid(not_null<HistoryItem*> item);
|
||||
void sendPaidRequest(not_null<HistoryItem*> item, int count);
|
||||
void sendPaidRequest(
|
||||
not_null<HistoryItem*> item,
|
||||
PaidReactionSend send);
|
||||
void sendPaidPrivacyRequest(
|
||||
not_null<HistoryItem*> item,
|
||||
PaidReactionSend send);
|
||||
void sendPaidFinish(
|
||||
not_null<HistoryItem*> item,
|
||||
int count,
|
||||
PaidReactionSend send,
|
||||
bool success);
|
||||
void checkQuitPreventFinished();
|
||||
|
||||
|
@ -397,21 +409,27 @@ public:
|
|||
[[nodiscard]] bool hasUnread() const;
|
||||
void markRead();
|
||||
|
||||
void scheduleSendPaid(int count);
|
||||
void scheduleSendPaid(int count, bool anonymous);
|
||||
[[nodiscard]] int scheduledPaid() const;
|
||||
void cancelScheduledPaid();
|
||||
|
||||
int startPaidSending();
|
||||
void finishPaidSending(int count, bool success);
|
||||
[[nodiscard]] PaidReactionSend startPaidSending();
|
||||
void finishPaidSending(PaidReactionSend send, bool success);
|
||||
|
||||
[[nodiscard]] bool localPaidData() const;
|
||||
[[nodiscard]] int localPaidCount() const;
|
||||
[[nodiscard]] bool localPaidAnonymous() const;
|
||||
bool clearCloudData();
|
||||
|
||||
private:
|
||||
struct Paid {
|
||||
std::vector<TopPaid> top;
|
||||
int scheduled = 0;
|
||||
int sending = 0;
|
||||
uint32 scheduled: 30 = 0;
|
||||
uint32 scheduledFlag : 1 = 0;
|
||||
uint32 scheduledAnonymous : 1 = 0;
|
||||
uint32 sending : 30 = 0;
|
||||
uint32 sendingFlag : 1 = 0;
|
||||
uint32 sendingAnonymous : 1 = 0;
|
||||
};
|
||||
const not_null<HistoryItem*> _item;
|
||||
|
||||
|
|
|
@ -492,7 +492,7 @@ void HistoryInner::reactionChosen(const ChosenReaction &reaction) {
|
|||
return;
|
||||
} else if (reaction.id.paid()) {
|
||||
Payments::ShowPaidReactionDetails(
|
||||
_controller->uiShow(),
|
||||
_controller,
|
||||
item,
|
||||
viewByItem(item),
|
||||
HistoryReactionSource::Selector);
|
||||
|
|
|
@ -2521,15 +2521,17 @@ bool HistoryItem::canReact() const {
|
|||
return true;
|
||||
}
|
||||
|
||||
void HistoryItem::addPaidReaction(int count) {
|
||||
Expects(count > 0);
|
||||
void HistoryItem::addPaidReaction(int count, bool anonymous) {
|
||||
Expects(count >= 0);
|
||||
Expects(_history->peer->isBroadcast());
|
||||
|
||||
if (!_reactions) {
|
||||
_reactions = std::make_unique<Data::MessageReactions>(this);
|
||||
}
|
||||
_reactions->scheduleSendPaid(count);
|
||||
_history->owner().notifyItemDataChange(this);
|
||||
_reactions->scheduleSendPaid(count, anonymous);
|
||||
if (count > 0) {
|
||||
_history->owner().notifyItemDataChange(this);
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::cancelScheduledPaidReaction() {
|
||||
|
@ -2539,14 +2541,18 @@ void HistoryItem::cancelScheduledPaidReaction() {
|
|||
}
|
||||
}
|
||||
|
||||
int HistoryItem::startPaidReactionSending() {
|
||||
return _reactions ? _reactions->startPaidSending() : 0;
|
||||
Data::PaidReactionSend HistoryItem::startPaidReactionSending() {
|
||||
return _reactions
|
||||
? _reactions->startPaidSending()
|
||||
: Data::PaidReactionSend();
|
||||
}
|
||||
|
||||
void HistoryItem::finishPaidReactionSending(int count, bool success) {
|
||||
void HistoryItem::finishPaidReactionSending(
|
||||
Data::PaidReactionSend send,
|
||||
bool success) {
|
||||
Expects(_reactions != nullptr);
|
||||
|
||||
_reactions->finishPaidSending(count, success);
|
||||
_reactions->finishPaidSending(send, success);
|
||||
_history->owner().notifyItemDataChange(this);
|
||||
}
|
||||
|
||||
|
@ -2586,12 +2592,15 @@ const std::vector<Data::MessageReaction> &HistoryItem::reactions() const {
|
|||
}
|
||||
|
||||
std::vector<Data::MessageReaction> HistoryItem::reactionsWithLocal() const {
|
||||
auto result = reactions();
|
||||
if (!_reactions) {
|
||||
return {};
|
||||
}
|
||||
auto result = _reactions->list();
|
||||
const auto i = ranges::find(
|
||||
result,
|
||||
Data::ReactionId::Paid(),
|
||||
&Data::MessageReaction::id);
|
||||
if (const auto local = _reactions ? _reactions->localPaidCount() : 0) {
|
||||
if (const auto local = _reactions->localPaidCount()) {
|
||||
if (i != end(result)) {
|
||||
i->my = true;
|
||||
i->count += local;
|
||||
|
@ -2629,10 +2638,41 @@ auto HistoryItem::recentReactions() const
|
|||
return _reactions ? _reactions->recent() : kEmpty;
|
||||
}
|
||||
|
||||
auto HistoryItem::topPaidReactions() const
|
||||
-> const std::vector<Data::MessageReactionsTopPaid> & {
|
||||
static const auto kEmpty = std::vector<Data::MessageReactionsTopPaid>();
|
||||
return _reactions ? _reactions->topPaid() : kEmpty;
|
||||
auto HistoryItem::topPaidReactionsWithLocal() const
|
||||
-> std::vector<Data::MessageReactionsTopPaid> {
|
||||
if (!_reactions) {
|
||||
return {};
|
||||
}
|
||||
using TopPaid = Data::MessageReactionsTopPaid;
|
||||
auto result = _reactions->topPaid();
|
||||
const auto i = ranges::find_if(
|
||||
result,
|
||||
[](const TopPaid &entry) { return entry.my != 0; });
|
||||
const auto peer = _reactions->localPaidAnonymous()
|
||||
? nullptr
|
||||
: history()->session().user().get();
|
||||
if (const auto local = _reactions->localPaidCount()) {
|
||||
const auto top = [&](int mine) {
|
||||
return ranges::count_if(result, [&](const TopPaid &entry) {
|
||||
return !entry.my && entry.count >= mine;
|
||||
}) < 3;
|
||||
};
|
||||
if (i != end(result)) {
|
||||
i->count += local;
|
||||
i->peer = peer;
|
||||
i->top = top(i->count) ? 1 : 0;
|
||||
} else {
|
||||
result.push_back({
|
||||
.peer = peer,
|
||||
.count = uint32(local),
|
||||
.top = uint32(top(local) ? 1 : 0),
|
||||
.my = uint32(1),
|
||||
});
|
||||
}
|
||||
} else if (i != end(result)) {
|
||||
i->peer = peer;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HistoryItem::canViewReactions() const {
|
||||
|
@ -3834,7 +3874,7 @@ bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) {
|
|||
const auto changeToEmpty = [&] {
|
||||
if (!_reactions) {
|
||||
return false;
|
||||
} else if (!_reactions->localPaidCount()) {
|
||||
} else if (!_reactions->localPaidData()) {
|
||||
_reactions = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ class Thread;
|
|||
struct SponsoredFrom;
|
||||
class Story;
|
||||
class SavedSublist;
|
||||
struct PaidReactionSend;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
|
@ -445,10 +446,12 @@ public:
|
|||
void toggleReaction(
|
||||
const Data::ReactionId &reaction,
|
||||
HistoryReactionSource source);
|
||||
void addPaidReaction(int count);
|
||||
void addPaidReaction(int count, bool anonymous);
|
||||
void cancelScheduledPaidReaction();
|
||||
[[nodiscard]] int startPaidReactionSending();
|
||||
void finishPaidReactionSending(int count, bool success);
|
||||
[[nodiscard]] Data::PaidReactionSend startPaidReactionSending();
|
||||
void finishPaidReactionSending(
|
||||
Data::PaidReactionSend send,
|
||||
bool success);
|
||||
void updateReactionsUnknown();
|
||||
[[nodiscard]] auto reactions() const
|
||||
-> const std::vector<Data::MessageReaction> &;
|
||||
|
@ -458,8 +461,8 @@ public:
|
|||
-> const base::flat_map<
|
||||
Data::ReactionId,
|
||||
std::vector<Data::RecentReaction>> &;
|
||||
[[nodiscard]] auto topPaidReactions() const
|
||||
-> const std::vector<Data::MessageReactionsTopPaid> &;
|
||||
[[nodiscard]] auto topPaidReactionsWithLocal() const
|
||||
-> std::vector<Data::MessageReactionsTopPaid>;
|
||||
[[nodiscard]] int reactionsPaidScheduled() const;
|
||||
[[nodiscard]] bool canViewReactions() const;
|
||||
[[nodiscard]] std::vector<Data::ReactionId> chosenReactions() const;
|
||||
|
|
|
@ -3283,6 +3283,7 @@ void Message::refreshReactions() {
|
|||
item,
|
||||
weak.get(),
|
||||
1,
|
||||
Payments::LookupMyPaidAnonymous(item),
|
||||
controller->uiShow());
|
||||
return;
|
||||
} else {
|
||||
|
|
|
@ -1358,7 +1358,7 @@ AttachSelectorResult AttachSelectorToMenu(
|
|||
desiredPosition,
|
||||
st::reactPanelEmojiPan,
|
||||
controller->uiShow(),
|
||||
Data::LookupPossibleReactions(item),
|
||||
Data::LookupPossibleReactions(item, true),
|
||||
std::move(about),
|
||||
std::move(iconFactory));
|
||||
if (!result) {
|
||||
|
|
|
@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/layers/show.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/dynamic_thumbnails.h"
|
||||
#include "window/window_session_controller.h"
|
||||
|
||||
namespace Payments {
|
||||
namespace {
|
||||
|
@ -41,6 +42,7 @@ void TryAddingPaidReaction(
|
|||
FullMsgId itemId,
|
||||
base::weak_ptr<HistoryView::Element> weakView,
|
||||
int count,
|
||||
bool anonymous,
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
Fn<void(bool)> finished) {
|
||||
const auto checkItem = [=] {
|
||||
|
@ -60,8 +62,8 @@ void TryAddingPaidReaction(
|
|||
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()) {
|
||||
item->addPaidReaction(count, anonymous);
|
||||
if (const auto view = count ? weakView.get() : nullptr) {
|
||||
const auto history = view->history();
|
||||
history->owner().notifyViewPaidReactionSent(view);
|
||||
view->animateReaction({
|
||||
|
@ -97,10 +99,20 @@ void TryAddingPaidReaction(
|
|||
|
||||
} // namespace
|
||||
|
||||
bool LookupMyPaidAnonymous(not_null<HistoryItem*> item) {
|
||||
for (const auto &entry : item->topPaidReactionsWithLocal()) {
|
||||
if (entry.my) {
|
||||
return !entry.peer;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TryAddingPaidReaction(
|
||||
not_null<HistoryItem*> item,
|
||||
HistoryView::Element *view,
|
||||
int count,
|
||||
bool anonymous,
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
Fn<void(bool)> finished) {
|
||||
TryAddingPaidReaction(
|
||||
|
@ -108,17 +120,19 @@ void TryAddingPaidReaction(
|
|||
item->fullId(),
|
||||
view,
|
||||
count,
|
||||
anonymous,
|
||||
std::move(show),
|
||||
std::move(finished));
|
||||
}
|
||||
|
||||
void ShowPaidReactionDetails(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item,
|
||||
HistoryView::Element *view,
|
||||
HistoryReactionSource source) {
|
||||
Expects(item->history()->peer->isBroadcast());
|
||||
|
||||
const auto show = controller->uiShow();
|
||||
const auto itemId = item->fullId();
|
||||
const auto session = &item->history()->session();
|
||||
const auto appConfig = &session->appConfig();
|
||||
|
@ -132,24 +146,26 @@ void ShowPaidReactionDetails(
|
|||
|
||||
struct State {
|
||||
QPointer<Ui::BoxContent> selectBox;
|
||||
bool ignoreAnonymousSwitch = false;
|
||||
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 send = [=](int count, bool anonymous, auto resend) -> void {
|
||||
Expects(count >= 0);
|
||||
|
||||
const auto finish = [=](bool success) {
|
||||
state->sending = false;
|
||||
if (success) {
|
||||
if (success && count > 0) {
|
||||
state->ignoreAnonymousSwitch = true;
|
||||
if (const auto strong = state->selectBox.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
}
|
||||
};
|
||||
if (state->sending) {
|
||||
if (state->sending || (!count && state->ignoreAnonymousSwitch)) {
|
||||
return;
|
||||
} else if (const auto item = session->data().message(itemId)) {
|
||||
state->sending = true;
|
||||
|
@ -157,6 +173,7 @@ void ShowPaidReactionDetails(
|
|||
item,
|
||||
weakView.get(),
|
||||
count,
|
||||
anonymous,
|
||||
show,
|
||||
finish);
|
||||
}
|
||||
|
@ -181,34 +198,57 @@ void ShowPaidReactionDetails(
|
|||
};
|
||||
});
|
||||
};
|
||||
auto already = 0;
|
||||
auto top = std::vector<Ui::PaidReactionTop>();
|
||||
const auto &topPaid = item->topPaidReactions();
|
||||
top.reserve(topPaid.size());
|
||||
for (const auto &entry : topPaid) {
|
||||
if (entry.my) {
|
||||
already = entry.count;
|
||||
}
|
||||
if (!entry.top) {
|
||||
continue;
|
||||
}
|
||||
const auto add = [&](const Data::MessageReactionsTopPaid &entry) {
|
||||
const auto peer = entry.peer;
|
||||
const auto name = peer
|
||||
? peer->shortName()
|
||||
: tr::lng_paid_react_anonymous(tr::now);
|
||||
const auto open = [=] {
|
||||
controller->showPeerInfo(peer);
|
||||
};
|
||||
top.push_back({
|
||||
.name = entry.peer->shortName(),
|
||||
.photo = Ui::MakeUserpicThumbnail(entry.peer),
|
||||
.name = name,
|
||||
.photo = (peer
|
||||
? Ui::MakeUserpicThumbnail(peer)
|
||||
: Ui::MakeHiddenAuthorThumbnail()),
|
||||
.count = int(entry.count),
|
||||
.click = peer ? open : Fn<void()>(),
|
||||
.my = (entry.my == 1),
|
||||
});
|
||||
};
|
||||
const auto topPaid = item->topPaidReactionsWithLocal();
|
||||
top.reserve(topPaid.size() + 2);
|
||||
for (const auto &entry : topPaid) {
|
||||
add(entry);
|
||||
if (entry.my) {
|
||||
auto copy = entry;
|
||||
copy.peer = entry.peer ? nullptr : session->user().get();
|
||||
add(copy);
|
||||
}
|
||||
}
|
||||
if (!ranges::contains(top, true, &Ui::PaidReactionTop::my)) {
|
||||
auto entry = Data::MessageReactionsTopPaid{
|
||||
.peer = session->user(),
|
||||
.count = 0,
|
||||
.my = true,
|
||||
};
|
||||
add(entry);
|
||||
entry.peer = nullptr;
|
||||
add(entry);
|
||||
}
|
||||
ranges::sort(top, ranges::greater(), &Ui::PaidReactionTop::count);
|
||||
|
||||
state->selectBox = show->show(Ui::MakePaidReactionBox({
|
||||
.already = already + CountLocalPaid(item),
|
||||
.chosen = chosen,
|
||||
.max = max,
|
||||
.top = std::move(top),
|
||||
.channel = item->history()->peer->name(),
|
||||
.submit = std::move(submitText),
|
||||
.balanceValue = session->credits().balanceValue(),
|
||||
.send = [=](int count) { send(count, send); },
|
||||
.send = [=](int count, bool anonymous) {
|
||||
send(count, anonymous, send);
|
||||
},
|
||||
}));
|
||||
|
||||
if (const auto strong = state->selectBox.data()) {
|
||||
|
|
|
@ -19,17 +19,24 @@ namespace Ui {
|
|||
class Show;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Payments {
|
||||
|
||||
[[nodiscard]] bool LookupMyPaidAnonymous(not_null<HistoryItem*> item);
|
||||
|
||||
void TryAddingPaidReaction(
|
||||
not_null<HistoryItem*> item,
|
||||
HistoryView::Element *view,
|
||||
int count,
|
||||
bool anonymous,
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
Fn<void(bool)> finished = nullptr);
|
||||
|
||||
void ShowPaidReactionDetails(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item,
|
||||
HistoryView::Element *view,
|
||||
HistoryReactionSource source);
|
||||
|
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/continuous_sliders.h"
|
||||
#include "ui/dynamic_image.h"
|
||||
#include "ui/painter.h"
|
||||
|
@ -178,8 +179,13 @@ void PaidReactionSlider(
|
|||
[[nodiscard]] not_null<RpWidget*> MakeTopReactor(
|
||||
not_null<QWidget*> parent,
|
||||
const PaidReactionTop &data) {
|
||||
const auto result = CreateChild<RpWidget>(parent);
|
||||
const auto result = CreateChild<AbstractButton>(parent);
|
||||
result->show();
|
||||
if (data.click && !data.my) {
|
||||
result->setClickedCallback(data.click);
|
||||
} else {
|
||||
result->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
}
|
||||
|
||||
struct State {
|
||||
QImage badge;
|
||||
|
@ -224,7 +230,9 @@ void PaidReactionSlider(
|
|||
|
||||
void FillTopReactors(
|
||||
not_null<VerticalLayout*> container,
|
||||
std::vector<PaidReactionTop> top) {
|
||||
std::vector<PaidReactionTop> top,
|
||||
rpl::producer<int> chosen,
|
||||
rpl::producer<bool> anonymous) {
|
||||
container->add(
|
||||
MakeBoostFeaturesBadge(
|
||||
container,
|
||||
|
@ -238,20 +246,53 @@ void FillTopReactors(
|
|||
st::paidReactTopMargin);
|
||||
struct State {
|
||||
std::vector<not_null<RpWidget*>> widgets;
|
||||
rpl::event_stream<> updated;
|
||||
};
|
||||
const auto state = wrap->lifetime().make_state<State>();
|
||||
|
||||
const auto topCount = std::min(int(top.size()), kMaxTopPaidShown);
|
||||
for (auto i = 0; i != topCount; ++i) {
|
||||
state->widgets.push_back(MakeTopReactor(wrap, top[i]));
|
||||
}
|
||||
rpl::combine(
|
||||
std::move(chosen),
|
||||
std::move(anonymous)
|
||||
) | rpl::start_with_next([=](int chosen, bool anonymous) {
|
||||
for (const auto &widget : state->widgets) {
|
||||
delete widget;
|
||||
}
|
||||
state->widgets.clear();
|
||||
|
||||
wrap->widthValue() | rpl::start_with_next([=](int width) {
|
||||
auto list = std::vector<PaidReactionTop>();
|
||||
list.reserve(kMaxTopPaidShown + 1);
|
||||
for (const auto &entry : top) {
|
||||
if (!entry.my) {
|
||||
list.push_back(entry);
|
||||
} else if (!entry.click == anonymous) {
|
||||
auto copy = entry;
|
||||
copy.count += chosen;
|
||||
list.push_back(copy);
|
||||
}
|
||||
}
|
||||
ranges::stable_sort(
|
||||
list,
|
||||
ranges::greater(),
|
||||
&PaidReactionTop::count);
|
||||
while (list.size() > kMaxTopPaidShown) {
|
||||
list.pop_back();
|
||||
}
|
||||
for (const auto &entry : list) {
|
||||
state->widgets.push_back(MakeTopReactor(wrap, entry));
|
||||
}
|
||||
state->updated.fire({});
|
||||
}, wrap->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
state->updated.events_starting_with({}),
|
||||
wrap->widthValue()
|
||||
) | rpl::start_with_next([=](auto, int width) {
|
||||
const auto single = width / 4;
|
||||
if (single <= st::paidReactTopUserpic) {
|
||||
return;
|
||||
}
|
||||
auto left = (width - single * topCount) / 2;
|
||||
const auto count = int(state->widgets.size());
|
||||
auto left = (width - single * count) / 2;
|
||||
for (const auto widget : state->widgets) {
|
||||
widget->setGeometry(left, 0, single, height);
|
||||
left += single;
|
||||
|
@ -264,6 +305,8 @@ void FillTopReactors(
|
|||
void PaidReactionsBox(
|
||||
not_null<GenericBox*> box,
|
||||
PaidReactionBoxArgs &&args) {
|
||||
Expects(!args.top.empty());
|
||||
|
||||
args.max = std::max(args.max, 2);
|
||||
args.chosen = std::clamp(args.chosen, 1, args.max);
|
||||
|
||||
|
@ -273,13 +316,22 @@ void PaidReactionsBox(
|
|||
|
||||
struct State {
|
||||
rpl::variable<int> chosen;
|
||||
rpl::variable<bool> anonymous;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
|
||||
state->chosen = args.chosen;
|
||||
const auto changed = [=](int count) {
|
||||
state->chosen = count;
|
||||
};
|
||||
|
||||
const auto initialAnonymous = ranges::find(
|
||||
args.top,
|
||||
true,
|
||||
&PaidReactionTop::my
|
||||
)->click == nullptr;
|
||||
state->anonymous = initialAnonymous;
|
||||
|
||||
const auto content = box->verticalLayout();
|
||||
AddSkip(content, st::boxTitleClose.height + st::paidReactBubbleTop);
|
||||
|
||||
|
@ -307,6 +359,10 @@ void PaidReactionsBox(
|
|||
&st::paidReactBubbleIcon,
|
||||
st::boxRowPadding);
|
||||
|
||||
const auto already = ranges::find(
|
||||
args.top,
|
||||
true,
|
||||
&PaidReactionTop::my)->count;
|
||||
PaidReactionSlider(content, args.chosen, args.max, changed);
|
||||
|
||||
box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); });
|
||||
|
@ -323,10 +379,10 @@ void PaidReactionsBox(
|
|||
+ QMargins(0, st::lineWidth, 0, st::boostBottomSkip)));
|
||||
const auto label = CreateChild<FlatLabel>(
|
||||
labelWrap,
|
||||
(args.already
|
||||
(already
|
||||
? tr::lng_paid_react_already(
|
||||
lt_count,
|
||||
rpl::single(args.already) | tr::to_count(),
|
||||
rpl::single(already) | tr::to_count(),
|
||||
Text::RichLangValue)
|
||||
: tr::lng_paid_react_about(
|
||||
lt_channel,
|
||||
|
@ -343,13 +399,31 @@ void PaidReactionsBox(
|
|||
label->moveToLeft(0, skip);
|
||||
}, label->lifetime());
|
||||
|
||||
if (!args.top.empty()) {
|
||||
FillTopReactors(content, std::move(args.top));
|
||||
}
|
||||
FillTopReactors(
|
||||
content,
|
||||
std::move(args.top),
|
||||
state->chosen.value(),
|
||||
state->anonymous.value());
|
||||
|
||||
const auto named = box->addRow(object_ptr<CenterWrap<Checkbox>>(
|
||||
box,
|
||||
object_ptr<Checkbox>(
|
||||
box,
|
||||
tr::lng_paid_react_show_in_top(tr::now),
|
||||
!state->anonymous.current())));
|
||||
state->anonymous = named->entity()->checkedValue(
|
||||
) | rpl::map(!rpl::mappers::_1);
|
||||
|
||||
const auto button = box->addButton(rpl::single(QString()), [=] {
|
||||
args.send(state->chosen.current());
|
||||
args.send(state->chosen.current(), !named->entity()->checked());
|
||||
});
|
||||
|
||||
box->boxClosing() | rpl::filter([=] {
|
||||
return state->anonymous.current() != initialAnonymous;
|
||||
}) | rpl::start_with_next([=] {
|
||||
args.send(0, state->anonymous.current());
|
||||
}, box->lifetime());
|
||||
|
||||
{
|
||||
const auto buttonLabel = CreateChild<FlatLabel>(
|
||||
button,
|
||||
|
|
|
@ -24,10 +24,11 @@ struct PaidReactionTop {
|
|||
QString name;
|
||||
std::shared_ptr<DynamicImage> photo;
|
||||
int count = 0;
|
||||
Fn<void()> click;
|
||||
bool my = false;
|
||||
};
|
||||
|
||||
struct PaidReactionBoxArgs {
|
||||
int already = 0;
|
||||
int chosen = 0;
|
||||
int max = 0;
|
||||
|
||||
|
@ -36,7 +37,7 @@ struct PaidReactionBoxArgs {
|
|||
QString channel;
|
||||
Fn<rpl::producer<TextWithContext>(rpl::producer<int> amount)> submit;
|
||||
rpl::producer<uint64> balanceValue;
|
||||
Fn<void(int)> send;
|
||||
Fn<void(int, bool)> send;
|
||||
};
|
||||
|
||||
void PaidReactionsBox(
|
||||
|
|
|
@ -157,6 +157,19 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class HiddenAuthorUserpic final : public DynamicImage {
|
||||
public:
|
||||
std::shared_ptr<DynamicImage> clone() override;
|
||||
|
||||
QImage image(int size) override;
|
||||
void subscribeToUpdates(Fn<void()> callback) override;
|
||||
|
||||
private:
|
||||
QImage _frame;
|
||||
int _paletteVersion = 0;
|
||||
|
||||
};
|
||||
|
||||
class IconThumbnail final : public DynamicImage {
|
||||
public:
|
||||
explicit IconThumbnail(const style::icon &icon);
|
||||
|
@ -476,6 +489,37 @@ void RepliesUserpic::subscribeToUpdates(Fn<void()> callback) {
|
|||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<DynamicImage> HiddenAuthorUserpic::clone() {
|
||||
return std::make_shared<HiddenAuthorUserpic>();
|
||||
}
|
||||
|
||||
QImage HiddenAuthorUserpic::image(int size) {
|
||||
const auto good = (_frame.width() == size * _frame.devicePixelRatio());
|
||||
const auto paletteVersion = style::PaletteVersion();
|
||||
if (!good || _paletteVersion != paletteVersion) {
|
||||
_paletteVersion = paletteVersion;
|
||||
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
if (!good) {
|
||||
_frame = QImage(
|
||||
QSize(size, size) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_frame.setDevicePixelRatio(ratio);
|
||||
}
|
||||
_frame.fill(Qt::transparent);
|
||||
|
||||
auto p = Painter(&_frame);
|
||||
Ui::EmptyUserpic::PaintHiddenAuthor(p, 0, 0, size, size);
|
||||
}
|
||||
return _frame;
|
||||
}
|
||||
|
||||
void HiddenAuthorUserpic::subscribeToUpdates(Fn<void()> callback) {
|
||||
if (!callback) {
|
||||
_frame = {};
|
||||
}
|
||||
}
|
||||
|
||||
IconThumbnail::IconThumbnail(const style::icon &icon) : _icon(icon) {
|
||||
}
|
||||
|
||||
|
@ -573,6 +617,10 @@ std::shared_ptr<DynamicImage> MakeRepliesThumbnail() {
|
|||
return std::make_shared<RepliesUserpic>();
|
||||
}
|
||||
|
||||
std::shared_ptr<DynamicImage> MakeHiddenAuthorThumbnail() {
|
||||
return std::make_shared<HiddenAuthorUserpic>();
|
||||
}
|
||||
|
||||
std::shared_ptr<DynamicImage> MakeStoryThumbnail(
|
||||
not_null<Data::Story*> story) {
|
||||
using Result = std::shared_ptr<DynamicImage>;
|
||||
|
|
|
@ -23,6 +23,7 @@ class DynamicImage;
|
|||
bool forceRound = false);
|
||||
[[nodiscard]] std::shared_ptr<DynamicImage> MakeSavedMessagesThumbnail();
|
||||
[[nodiscard]] std::shared_ptr<DynamicImage> MakeRepliesThumbnail();
|
||||
[[nodiscard]] std::shared_ptr<DynamicImage> MakeHiddenAuthorThumbnail();
|
||||
[[nodiscard]] std::shared_ptr<DynamicImage> MakeStoryThumbnail(
|
||||
not_null<Data::Story*> story);
|
||||
[[nodiscard]] std::shared_ptr<DynamicImage> MakeIconThumbnail(
|
||||
|
|
Loading…
Add table
Reference in a new issue