Show paid stars information above a message.

This commit is contained in:
John Preston 2025-02-19 13:55:46 +04:00
parent 4729e51e14
commit 5b809c4fc6
9 changed files with 94 additions and 10 deletions

View file

@ -2170,6 +2170,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_paid_message_sent#other" = "You paid {count} Stars to send a message"; "lng_action_paid_message_sent#other" = "You paid {count} Stars to send a message";
"lng_action_paid_message_got#one" = "You received {count} Star from {name}"; "lng_action_paid_message_got#one" = "You received {count} Star from {name}";
"lng_action_paid_message_got#other" = "You received {count} Stars from {name}"; "lng_action_paid_message_got#other" = "You received {count} Stars from {name}";
"lng_action_paid_message_group#one" = "{from} paid {count} Star to send a message";
"lng_action_paid_message_group#other" = "{from} paid {count} Stars to send a message";
"lng_similar_channels_title" = "Similar channels"; "lng_similar_channels_title" = "Similar channels";
"lng_similar_channels_view_all" = "View all"; "lng_similar_channels_view_all" = "View all";

View file

@ -229,6 +229,7 @@ void SendExistingMedia(
.replyTo = action.replyTo, .replyTo = action.replyTo,
.date = NewMessageDate(action.options), .date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId, .shortcutId = action.options.shortcutId,
.starsPaid = starsPaid,
.postAuthor = NewMessagePostAuthor(action), .postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId, .effectId = action.options.effectId,
}, media, caption); }, media, caption);
@ -411,6 +412,7 @@ bool SendDice(MessageToSend &message) {
.replyTo = action.replyTo, .replyTo = action.replyTo,
.date = NewMessageDate(action.options), .date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId, .shortcutId = action.options.shortcutId,
.starsPaid = starsPaid,
.postAuthor = NewMessagePostAuthor(action), .postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId, .effectId = action.options.effectId,
}, TextWithEntities(), MTP_messageMediaDice( }, TextWithEntities(), MTP_messageMediaDice(

View file

@ -3373,12 +3373,12 @@ void ApiWrap::forwardMessages(
const auto requestType = Data::Histories::RequestType::Send; const auto requestType = Data::Histories::RequestType::Send;
const auto idsCopy = localIds; const auto idsCopy = localIds;
const auto scheduled = action.options.scheduled; const auto scheduled = action.options.scheduled;
auto paidStars = std::min( const auto starsPaid = std::min(
action.options.starsApproved, action.options.starsApproved,
int(ids.size() * peer->starsPerMessageChecked())); int(ids.size() * peer->starsPerMessageChecked()));
auto oneFlags = sendFlags; auto oneFlags = sendFlags;
if (paidStars) { if (starsPaid) {
action.options.starsApproved -= paidStars; action.options.starsApproved -= starsPaid;
oneFlags |= SendFlag::f_allow_paid_stars; oneFlags |= SendFlag::f_allow_paid_stars;
} }
histories.sendRequest(history, requestType, [=](Fn<void()> finish) { histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
@ -3393,7 +3393,7 @@ void ApiWrap::forwardMessages(
(sendAs ? sendAs->input : MTP_inputPeerEmpty()), (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, action.options.shortcutId), Data::ShortcutIdToMTP(_session, action.options.shortcutId),
MTPint(), // video_timestamp MTPint(), // video_timestamp
MTP_long(paidStars) MTP_long(starsPaid)
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
if (!scheduled) { if (!scheduled) {
this->updates().checkForSentToScheduled(result); this->updates().checkForSentToScheduled(result);
@ -3438,6 +3438,7 @@ void ApiWrap::forwardMessages(
.replyTo = { .topicRootId = topMsgId }, .replyTo = { .topicRootId = topMsgId },
.date = NewMessageDate(action.options), .date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId, .shortcutId = action.options.shortcutId,
.starsPaid = action.options.starsApproved,
.postAuthor = NewMessagePostAuthor(action), .postAuthor = NewMessagePostAuthor(action),
// forwarded messages don't have effects // forwarded messages don't have effects
@ -3906,6 +3907,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
.replyTo = action.replyTo, .replyTo = action.replyTo,
.date = NewMessageDate(action.options), .date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId, .shortcutId = action.options.shortcutId,
.starsPaid = starsPaid,
.postAuthor = NewMessagePostAuthor(action), .postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId, .effectId = action.options.effectId,
}, sending, media); }, sending, media);
@ -4092,6 +4094,7 @@ void ApiWrap::sendInlineResult(
.replyTo = action.replyTo, .replyTo = action.replyTo,
.date = NewMessageDate(action.options), .date = NewMessageDate(action.options),
.shortcutId = action.options.shortcutId, .shortcutId = action.options.shortcutId,
.starsPaid = starsPaid,
.viaBotId = ((bot && !action.options.hideViaBot) .viaBotId = ((bot && !action.options.hideViaBot)
? peerToUser(bot->id) ? peerToUser(bot->id)
: UserId()), : UserId()),

View file

@ -460,6 +460,9 @@ not_null<HistoryItem*> History::createItem(
}); });
if (newMessage && result->out() && result->isRegular()) { if (newMessage && result->out() && result->isRegular()) {
session().topPeers().increment(peer, result->date()); session().topPeers().increment(peer, result->date());
if (result->starsPaid()) {
session().credits().load(true);
}
} }
return result; return result;
} }

View file

@ -401,6 +401,7 @@ HistoryItem::HistoryItem(
.from = data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0), .from = data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0),
.date = data.vdate().v, .date = data.vdate().v,
.shortcutId = data.vquick_reply_shortcut_id().value_or_empty(), .shortcutId = data.vquick_reply_shortcut_id().value_or_empty(),
.starsPaid = int(data.vpaid_message_stars().value_or_empty()),
.effectId = data.veffect().value_or_empty(), .effectId = data.veffect().value_or_empty(),
}) { }) {
_boostsApplied = data.vfrom_boosts_applied().value_or_empty(); _boostsApplied = data.vfrom_boosts_applied().value_or_empty();
@ -745,6 +746,7 @@ HistoryItem::HistoryItem(
: history->peer) : history->peer)
, _flags(FinalizeMessageFlags(history, fields.flags)) , _flags(FinalizeMessageFlags(history, fields.flags))
, _date(fields.date) , _date(fields.date)
, _starsPaid(fields.starsPaid)
, _shortcutId(fields.shortcutId) , _shortcutId(fields.shortcutId)
, _effectId(fields.effectId) { , _effectId(fields.effectId) {
Expects(!_shortcutId Expects(!_shortcutId
@ -793,6 +795,10 @@ TimeId HistoryItem::date() const {
return _date; return _date;
} }
int HistoryItem::starsPaid() const {
return _starsPaid;
}
bool HistoryItem::awaitingVideoProcessing() const { bool HistoryItem::awaitingVideoProcessing() const {
return (_flags & MessageFlag::EstimatedDate); return (_flags & MessageFlag::EstimatedDate);
} }

View file

@ -103,6 +103,7 @@ struct HistoryItemCommonFields {
FullReplyTo replyTo; FullReplyTo replyTo;
TimeId date = 0; TimeId date = 0;
BusinessShortcutId shortcutId = 0; BusinessShortcutId shortcutId = 0;
int starsPaid = 0;
UserId viaBotId = 0; UserId viaBotId = 0;
QString postAuthor; QString postAuthor;
uint64 groupedId = 0; uint64 groupedId = 0;
@ -548,6 +549,7 @@ public:
// content uses the color of the original sender. // content uses the color of the original sender.
[[nodiscard]] PeerData *contentColorsFrom() const; [[nodiscard]] PeerData *contentColorsFrom() const;
[[nodiscard]] uint8 contentColorIndex() const; [[nodiscard]] uint8 contentColorIndex() const;
[[nodiscard]] int starsPaid() const;
[[nodiscard]] std::unique_ptr<HistoryView::Element> createView( [[nodiscard]] std::unique_ptr<HistoryView::Element> createView(
not_null<HistoryView::ElementDelegate*> delegate, not_null<HistoryView::ElementDelegate*> delegate,
@ -682,6 +684,7 @@ private:
TimeId _date = 0; TimeId _date = 0;
TimeId _ttlDestroyAt = 0; TimeId _ttlDestroyAt = 0;
int _boostsApplied = 0; int _boostsApplied = 0;
int _starsPaid = 0;
BusinessShortcutId _shortcutId = 0; BusinessShortcutId _shortcutId = 0;
MessageGroupId _groupId = MessageGroupId(); MessageGroupId _groupId = MessageGroupId();

View file

@ -472,12 +472,15 @@ void DateBadge::paint(
ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide); ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide);
} }
void ServicePreMessage::init(TextWithEntities string) { void ServicePreMessage::init(PreparedServiceText string) {
text = Ui::Text::String( text = Ui::Text::String(
st::serviceTextStyle, st::serviceTextStyle,
string, string.text,
kMarkupTextOptions, kMarkupTextOptions,
st::msgMinWidth); st::msgMinWidth);
for (auto i = 0; i != int(string.links.size()); ++i) {
text.setLink(i + 1, string.links[i]);
}
} }
int ServicePreMessage::resizeToWidth(int newWidth, bool chatWide) { int ServicePreMessage::resizeToWidth(int newWidth, bool chatWide) {
@ -547,6 +550,27 @@ void ServicePreMessage::paint(
p.translate(0, -top); p.translate(0, -top);
} }
ClickHandlerPtr ServicePreMessage::textState(
QPoint point,
const StateRequest &request,
QRect g) const {
const auto top = g.top() - height - st::msgMargin.top();
const auto rect = QRect(0, top, width, height)
- st::msgServiceMargin;
const auto trect = rect - st::msgServicePadding;
if (trect.contains(point)) {
auto textRequest = request.forText();
textRequest.align = style::al_center;
return text.getState(
point - trect.topLeft(),
trect.width(),
textRequest).link;
}
return {};
}
void FakeBotAboutTop::init() { void FakeBotAboutTop::init() {
if (!text.isEmpty()) { if (!text.isEmpty()) {
return; return;
@ -1411,8 +1435,8 @@ void Element::setDisplayDate(bool displayDate) {
} }
} }
void Element::setServicePreMessage(TextWithEntities text) { void Element::setServicePreMessage(PreparedServiceText text) {
if (!text.empty()) { if (!text.text.empty()) {
AddComponents(ServicePreMessage::Bit()); AddComponents(ServicePreMessage::Bit());
const auto service = Get<ServicePreMessage>(); const auto service = Get<ServicePreMessage>();
service->init(std::move(text)); service->init(std::move(text));

View file

@ -16,6 +16,7 @@ class History;
class HistoryBlock; class HistoryBlock;
class HistoryItem; class HistoryItem;
struct HistoryMessageReply; struct HistoryMessageReply;
struct PreparedServiceText;
namespace Data { namespace Data {
struct Reaction; struct Reaction;
@ -260,7 +261,7 @@ struct DateBadge : public RuntimeComponent<DateBadge, Element> {
// displaying some text in layout of a service message above the message. // displaying some text in layout of a service message above the message.
struct ServicePreMessage struct ServicePreMessage
: public RuntimeComponent<ServicePreMessage, Element> { : public RuntimeComponent<ServicePreMessage, Element> {
void init(TextWithEntities string); void init(PreparedServiceText string);
int resizeToWidth(int newWidth, bool chatWide); int resizeToWidth(int newWidth, bool chatWide);
@ -269,6 +270,10 @@ struct ServicePreMessage
const PaintContext &context, const PaintContext &context,
QRect g, QRect g,
bool chatWide) const; bool chatWide) const;
[[nodiscard]] ClickHandlerPtr textState(
QPoint point,
const StateRequest &request,
QRect g) const;
Ui::Text::String text; Ui::Text::String text;
int width = 0; int width = 0;
@ -403,7 +408,7 @@ public:
// For blocks context this should be called only from recountDisplayDate(). // For blocks context this should be called only from recountDisplayDate().
void setDisplayDate(bool displayDate); void setDisplayDate(bool displayDate);
void setServicePreMessage(TextWithEntities text); void setServicePreMessage(PreparedServiceText text);
bool computeIsAttachToPrevious(not_null<Element*> previous); bool computeIsAttachToPrevious(not_null<Element*> previous);

View file

@ -431,6 +431,35 @@ Message::Message(
_rightAction->second->link = ReportSponsoredClickHandler(data); _rightAction->second->link = ReportSponsoredClickHandler(data);
} }
} }
if (const auto stars = data->starsPaid()) {
auto text = PreparedServiceText{
.text = data->out()
? tr::lng_action_paid_message_sent(
tr::now,
lt_count,
stars,
Ui::Text::WithEntities)
: history()->peer->isUser()
? tr::lng_action_paid_message_got(
tr::now,
lt_count,
stars,
lt_name,
Ui::Text::Link(data->from()->shortName(), 1),
Ui::Text::WithEntities)
: tr::lng_action_paid_message_group(
tr::now,
lt_count,
stars,
lt_from,
Ui::Text::Link(data->from()->shortName(), 1),
Ui::Text::WithEntities),
};
if (!data->out()) {
text.links.push_back(data->from()->createOpenLink());
}
setServicePreMessage(std::move(text));
}
} }
Message::~Message() { Message::~Message() {
@ -2448,6 +2477,13 @@ TextState Message::textState(
return result; return result;
} }
if (const auto service = Get<ServicePreMessage>()) {
result.link = service->textState(point, request, g);
if (result.link) {
return result;
}
}
const auto bubble = drawBubble(); const auto bubble = drawBubble();
const auto reactionsInBubble = _reactions && embedReactionsInBubble(); const auto reactionsInBubble = _reactions && embedReactionsInBubble();
const auto mediaDisplayed = media && media->isDisplayed(); const auto mediaDisplayed = media && media->isDisplayed();