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