Implement star gift view box.

This commit is contained in:
John Preston 2024-09-20 12:03:44 +04:00
parent 18904412cd
commit efbd3ca8fa
16 changed files with 279 additions and 49 deletions

View file

@ -3007,6 +3007,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_sent_title" = "Gift Sent!";
"lng_gift_sent_about#one" = "You spent **{count}** Star from your balance.";
"lng_gift_sent_about#other" = "You spent **{count}** Stars from your balance.";
"lng_gift_limited_of_one" = "unique";
"lng_gift_limited_of_count" = "1 of {amount}";
"lng_gift_anonymous_hint" = "Only you can see the sender's name.";
"lng_gift_availability" = "Availability";
"lng_gift_availability_left#one" = "{count} of {amount} left";
"lng_gift_availability_left#other" = "{count} of {amount} left";
"lng_gift_availability_none" = "None of {amount} left";
"lng_accounts_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";

View file

@ -72,7 +72,7 @@ constexpr auto kTransactionsLimit = 100;
return Data::CreditsHistoryEntry{
.id = qs(tl.data().vid()),
.title = qs(tl.data().vtitle().value_or_empty()),
.description = qs(tl.data().vdescription().value_or_empty()),
.description = { qs(tl.data().vdescription().value_or_empty()) },
.date = base::unixtime::parse(tl.data().vdate().v),
.photoId = photo ? photo->id : 0,
.extended = std::move(extended),

View file

@ -612,6 +612,7 @@ auto PremiumGiftCodeOptions::requestStarGifts()
gifts.push_back(StarGift{
.id = uint64(data.vid().v),
.stars = int64(data.vstars().v),
.convertStars = int64(data.vconvert_stars().v),
.document = document,
.limitedLeft = remaining.value_or_empty(),
.limitedCount = total.value_or_empty(),

View file

@ -76,6 +76,7 @@ struct GiftOptionData {
struct StarGift {
uint64 id = 0;
int64 stars = 0;
int64 convertStars = 0;
not_null<DocumentData*> document;
int limitedLeft = 0;
int limitedCount = 0;

View file

@ -100,6 +100,7 @@ struct PremiumGiftsDescriptor {
struct GiftTypeStars {
uint64 id = 0;
int64 stars = 0;
int64 convertStars = 0;
DocumentData *document = nullptr;
bool limited = false;
@ -240,7 +241,7 @@ auto GenerateGiftMedia(
auto textFallback = tr::lng_action_gift_got_stars_text(
tr::now,
lt_count,
v::get<GiftTypeStars>(descriptor).stars,
v::get<GiftTypeStars>(descriptor).convertStars,
Ui::Text::RichLangValue);
auto description = data.text.isEmpty()
? std::move(textFallback)
@ -519,6 +520,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
list.push_back({
.id = gift.id,
.stars = gift.stars,
.convertStars = gift.convertStars,
.document = gift.document,
.limited = (gift.limitedCount > 0),
});
@ -1428,8 +1430,7 @@ void GiftBox(
const auto &stUser = st::premiumGiftsUserpicButton;
const auto content = box->verticalLayout();
AddSkip(content);
AddSkip(content);
AddSkip(content, st::defaultVerticalListSkip * 5);
content->add(
object_ptr<CenterWrap<>>(

View file

@ -799,10 +799,12 @@ void AddTableRow(
object_ptr<Ui::RpWidget> value,
style::margins valueMargins) {
table->addRow(
object_ptr<Ui::FlatLabel>(
table,
std::move(label),
st::giveawayGiftCodeLabel),
(label
? object_ptr<Ui::FlatLabel>(
table,
std::move(label),
st::giveawayGiftCodeLabel)
: object_ptr<Ui::FlatLabel>(nullptr)),
std::move(value),
st::giveawayGiftCodeLabelMargin,
valueMargins);
@ -1708,6 +1710,59 @@ void ResolveGiveawayInfo(
crl::guard(controller, show));
}
void AddStarGiftTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry) {
auto table = container->add(
object_ptr<Ui::TableLayout>(
container,
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(entry.barePeerId);
if (peerId) {
auto text = entry.in
? tr::lng_credits_box_history_entry_peer_in()
: tr::lng_credits_box_history_entry_peer();
AddTableRow(table, std::move(text), controller, peerId);
}
if (!entry.date.isNull()) {
AddTableRow(
table,
tr::lng_gift_link_label_date(),
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
}
if (entry.limitedCount > 0) {
auto amount = rpl::single(TextWithEntities{
QString::number(entry.limitedCount)
});
AddTableRow(
table,
tr::lng_gift_availability(),
((entry.limitedLeft > 0)
? tr::lng_gift_availability_left(
lt_count,
rpl::single(entry.limitedLeft * 1.),
lt_amount,
std::move(amount),
Ui::Text::WithEntities)
: tr::lng_gift_availability_none(
lt_amount,
std::move(amount),
Ui::Text::WithEntities)));
}
if (!entry.description.empty()) {
table->addRow(
nullptr,
object_ptr<Ui::FlatLabel>(
table,
rpl::single(entry.description),
st::giveawayGiftCodeValue),
st::giveawayGiftCodeLabelMargin,
st::giveawayGiftCodeValueMargin);
}
}
void AddCreditsHistoryEntryTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,

View file

@ -76,6 +76,10 @@ void ResolveGiveawayInfo(
std::optional<Data::GiveawayStart> start,
std::optional<Data::GiveawayResults> results);
void AddStarGiftTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry);
void AddCreditsHistoryEntryTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,

View file

@ -50,7 +50,7 @@ struct CreditsHistoryEntry final {
QString id;
QString title;
QString description;
TextWithEntities description;
QDateTime date;
PhotoId photoId = 0;
std::vector<CreditsHistoryMedia> extended;
@ -58,10 +58,17 @@ struct CreditsHistoryEntry final {
uint64 bareMsgId = 0;
uint64 barePeerId = 0;
uint64 bareGiveawayMsgId = 0;
uint64 bareGiftStickerId = 0;
PeerType peerType;
QDateTime subscriptionUntil;
QDateTime successDate;
QString successLink;
int limitedCount = 0;
int limitedLeft = 0;
int convertStars = 0;
bool converted = false;
bool anonymous = false;
bool savedToProfile = false;
bool reaction = false;
bool refunded = false;
bool pending = false;

View file

@ -141,6 +141,7 @@ struct GiftCode {
MsgId giveawayMsgId = 0;
int convertStars = 0;
int limitedCount = 0;
int limitedLeft = 0;
int count = 0;
GiftType type = GiftType::Premium;
bool viaGiveaway : 1 = false;

View file

@ -5490,6 +5490,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
: TextWithEntities()),
.convertStars = int(data.vconvert_stars().v),
.limitedCount = gift.vavailability_total().value_or_empty(),
.limitedLeft = gift.vavailability_remains().value_or_empty(),
.count = int(gift.vstars().v),
.type = Data::GiftType::StarGift,
.anonymous = data.is_name_hidden(),

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "data/data_credits.h"
#include "data/data_document.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "history/history.h"
#include "history/history_item.h"
@ -79,14 +80,14 @@ TextWithEntities PremiumGift::subtitle() {
? tr::lng_action_gift_sent_text(
tr::now,
lt_count,
_data.count,
_data.convertStars,
lt_user,
Ui::Text::Bold(_parent->history()->peer->shortName()),
Ui::Text::RichLangValue)
: tr::lng_action_gift_got_stars_text(
tr::now,
lt_count,
_data.count,
_data.convertStars,
Ui::Text::RichLangValue);
}
const auto isCreditsPrize = creditsPrize();
@ -149,6 +150,7 @@ ClickHandlerPtr PremiumGift::createViewLink() {
return nullptr;
}
const auto from = _gift->from();
const auto itemId = _parent->data()->fullId();
const auto peer = _parent->history()->peer;
const auto date = _parent->data()->date();
const auto data = _gift->data();
@ -157,25 +159,21 @@ ClickHandlerPtr PremiumGift::createViewLink() {
if (const auto controller = my.sessionWindow.get()) {
const auto selfId = controller->session().userPeerId();
const auto sent = (from->id == selfId);
if (creditsPrize()) {
using Type = Data::CreditsHistoryEntry::PeerType;
if (starGift()) {
const auto item = controller->session().data().message(itemId);
if (item) {
controller->show(Box(
Settings::StarGiftViewBox,
controller,
data,
item));
}
} else if (creditsPrize()) {
controller->show(Box(
Settings::ReceiptCreditsBox,
Settings::CreditsPrizeBox,
controller,
Data::CreditsHistoryEntry{
.id = data.slug,
.title = QString(),
.description = QString(),
.date = base::unixtime::parse(date),
.credits = uint64(data.count),
.barePeerId = data.channel
? data.channel->id.value
: 0,
.bareGiveawayMsgId = uint64(data.giveawayMsgId.bare),
.peerType = Type::Peer,
.in = true,
},
Data::SubscriptionEntry()));
data,
date));
} else if (data.type == Data::GiftType::Credits) {
const auto to = sent ? peer : peer->session().user();
controller->show(Box(
@ -212,6 +210,20 @@ void PremiumGift::draw(
}
}
QString PremiumGift::cornerTagText() {
if (const auto count = _data.limitedCount) {
return (count == 1)
? tr::lng_gift_limited_of_one(tr::now)
: tr::lng_gift_limited_of_count(
tr::now,
lt_amount,
((count % 1000)
? Lang::FormatCountDecimal(count)
: Lang::FormatCountToShort(count).string));
}
return QString();
}
bool PremiumGift::hideServiceText() {
return !gift();
}

View file

@ -29,6 +29,7 @@ public:
QString title() override;
TextWithEntities subtitle() override;
rpl::producer<QString> button() override;
QString cornerTagText() override;
int buttonSkip() override;
void draw(
Painter &p,

View file

@ -151,6 +151,30 @@ void ServiceBox::draw(Painter &p, const PaintContext &context) const {
_content->draw(p, context, content);
if (const auto tag = _content->cornerTagText(); !tag.isEmpty()) {
const auto font = st::semiboldFont;
p.setFont(font);
p.setPen(Qt::NoPen);
const auto twidth = font->width(tag);
const auto pos = QPoint(_innerSize.width() - twidth, font->height);
const auto add = style::ConvertScale(2);
p.save();
p.setClipRect(
-add,
-add,
_innerSize.width() + 2 * add,
_innerSize.height() + 2 * add);
p.translate(pos);
p.rotate(45.);
p.translate(-pos);
p.setPen(Qt::NoPen);
p.setBrush(context.st->msgServiceBg()); // ?
p.drawRect(-5 * twidth, 0, twidth * 12, font->height);
p.setPen(context.st->msgServiceFg());
p.drawText(pos - QPoint(0, font->descent), tag);
p.restore();
}
p.translate(0, -st::msgServiceGiftBoxTopSkip);
}

View file

@ -28,6 +28,9 @@ public:
return top();
}
[[nodiscard]] virtual rpl::producer<QString> button() = 0;
[[nodiscard]] virtual QString cornerTagText() {
return {};
}
virtual void draw(
Painter &p,
const PaintContext &context,

View file

@ -16,11 +16,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/gift_premium_box.h"
#include "chat_helpers/stickers_gift_box_pack.h"
#include "chat_helpers/stickers_lottie.h"
#include "core/application.h"
#include "core/click_handler_types.h"
#include "core/click_handler_types.h" // UrlClickHandler
#include "core/ui_integration.h"
#include "data/components/credits.h"
#include "data/data_boosts.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_file_origin.h"
@ -35,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
#include "info/settings/info_settings_widget.h" // SectionCustomTopBarData.
#include "info/statistics/info_statistics_list_controllers.h"
#include "iv/iv_instance.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_single_player.h"
#include "main/main_app_config.h"
@ -657,6 +660,10 @@ void ReceiptCreditsBox(
const auto &stUser = st::boostReplaceUserpic;
const auto session = &controller->session();
const auto isPrize = e.bareGiveawayMsgId > 0;
const auto isStarGift = (e.convertStars > 0);
const auto starGiftSticker = (isStarGift && e.bareGiftStickerId)
? session->data().document(e.bareGiftStickerId).get()
: nullptr;
const auto peer = isPrize
? nullptr
: (s.barePeerId)
@ -682,14 +689,15 @@ void ReceiptCreditsBox(
std::unique_ptr<Lottie::SinglePlayer> lottie;
rpl::lifetime downloadLifetime;
};
Ui::AddSkip(
content,
st::creditsHistoryEntryGiftStickerSpace);
Ui::AddSkip(content, st::creditsHistoryEntryGiftStickerSpace
- (isStarGift ? st::creditsHistoryEntryGiftStickerSkip : 0));
const auto icon = Ui::CreateChild<Ui::RpWidget>(content);
icon->resize(Size(st::creditsHistoryEntryGiftStickerSize));
const auto state = icon->lifetime().make_state<State>();
auto &packs = session->giftBoxStickersPacks();
const auto document = packs.lookup(packs.monthsForStars(e.credits));
const auto document = starGiftSticker
? starGiftSticker
: packs.lookup(packs.monthsForStars(e.credits));
if (document && document->sticker()) {
state->sticker = document;
state->media = document->createMediaView();
@ -729,7 +737,7 @@ void ReceiptCreditsBox(
) | rpl::start_with_next([=](const QSize &size) {
icon->move(
(size.width() - icon->width()) / 2,
st::creditsHistoryEntryGiftStickerSkip);
isStarGift ? 0 : st::creditsHistoryEntryGiftStickerSkip);
}, icon->lifetime());
} else {
const auto widget = content->add(
@ -810,7 +818,11 @@ void ReceiptCreditsBox(
context);
} else {
auto t = TextWithEntities()
.append(e.in ? u"+"_q : e.gift ? QString() : QString(kMinus))
.append((e.in && !isStarGift)
? u"+"_q
: e.gift
? QString()
: QString(kMinus))
.append(Lang::FormatCountDecimal(std::abs(int64(e.credits))))
.append(QChar(' '))
.append(session->data().customEmojiManager().creditsEmoji());
@ -882,7 +894,7 @@ void ReceiptCreditsBox(
}, amount->lifetime());
}
if (!e.description.isEmpty()) {
if (!isStarGift && !e.description.empty()) {
Ui::AddSkip(content);
box->addRow(object_ptr<Ui::CenterWrap<>>(
box,
@ -891,7 +903,30 @@ void ReceiptCreditsBox(
rpl::single(e.description),
st::creditsBoxAbout)));
}
if (e.gift || isPrize) {
if (isStarGift) {
Ui::AddSkip(content);
const auto about = box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
object_ptr<Ui::FlatLabel>(
box,
rpl::combine(
tr::lng_action_gift_got_stars_text(
lt_count,
rpl::single(e.convertStars * 1.),
Ui::Text::RichLangValue),
tr::lng_paid_about_link()
) | rpl::map([](TextWithEntities text, QString link) {
return text.append(' ').append(Ui::Text::Link(link));
}),
st::creditsBoxAbout)))->entity();
about->setClickHandlerFilter([=](const auto &...) {
Core::App().iv().openWithIvPreferred(
session,
tr::lng_paid_about_link_url(tr::now));
return false;
});
} else if (e.gift || isPrize) {
Ui::AddSkip(content);
const auto arrow = Ui::Text::SingleCustomEmoji(
session->data().customEmojiManager().registerInternalEmoji(
@ -929,23 +964,35 @@ void ReceiptCreditsBox(
Ui::AddSkip(content);
Ui::AddSkip(content);
AddCreditsHistoryEntryTable(controller, content, e);
AddSubscriptionEntryTable(controller, content, s);
if (isStarGift) {
AddStarGiftTable(controller, content, e);
} else {
AddCreditsHistoryEntryTable(controller, content, e);
AddSubscriptionEntryTable(controller, content, s);
}
Ui::AddSkip(content);
box->addRow(object_ptr<Ui::CenterWrap<>>(
box,
object_ptr<Ui::FlatLabel>(
if (!isStarGift) {
box->addRow(object_ptr<Ui::CenterWrap<>>(
box,
tr::lng_credits_box_out_about(
lt_link,
tr::lng_payments_terms_link(
) | Ui::Text::ToLink(
tr::lng_credits_box_out_about_link(tr::now)),
Ui::Text::WithEntities),
st::creditsBoxAboutDivider)));
object_ptr<Ui::FlatLabel>(
box,
tr::lng_credits_box_out_about(
lt_link,
tr::lng_payments_terms_link(
) | Ui::Text::ToLink(
tr::lng_credits_box_out_about_link(tr::now)),
Ui::Text::WithEntities),
st::creditsBoxAboutDivider)));
} else if (e.anonymous) {
box->addRow(object_ptr<Ui::CenterWrap<>>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_gift_anonymous_hint(),
st::creditsBoxAboutDivider)));
}
if (s) {
Ui::AddSkip(content);
box->addRow(object_ptr<Ui::CenterWrap<>>(
@ -1085,6 +1132,60 @@ void GiftedCreditsBox(
}, {});
}
void CreditsPrizeBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
const Data::GiftCode &data,
TimeId date) {
using Type = Data::CreditsHistoryEntry::PeerType;
Settings::ReceiptCreditsBox(
box,
controller,
Data::CreditsHistoryEntry{
.id = data.slug,
.title = QString(),
.description = QString(),
.date = base::unixtime::parse(date),
.credits = uint64(data.count),
.barePeerId = data.channel
? data.channel->id.value
: 0,
.bareGiveawayMsgId = uint64(data.giveawayMsgId.bare),
.peerType = Type::Peer,
.in = true,
},
Data::SubscriptionEntry());
}
void StarGiftViewBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
const Data::GiftCode &data,
not_null<HistoryItem*> item) {
Settings::ReceiptCreditsBox(
box,
controller,
Data::CreditsHistoryEntry{
.id = data.slug,
.description = data.message,
.date = base::unixtime::parse(item->date()),
.credits = uint64(data.count),
.bareMsgId = uint64(item->id.bare),
.barePeerId = item->history()->peer->id.value,
.bareGiftStickerId = data.document ? data.document->id : 0,
.peerType = Data::CreditsHistoryEntry::PeerType::Peer,
.limitedCount = data.limitedCount,
.limitedLeft = data.limitedLeft,
.convertStars = data.convertStars,
.converted = data.converted,
.anonymous = data.anonymous,
.savedToProfile = data.saved,
.in = true,
.gift = true,
},
Data::SubscriptionEntry());
}
void ShowRefundInfoBox(
not_null<Window::SessionController*> controller,
FullMsgId refundItemId) {

View file

@ -16,6 +16,7 @@ namespace Data {
struct Boost;
struct CreditsHistoryEntry;
struct SubscriptionEntry;
struct GiftCode;
} // namespace Data
namespace Main {
@ -86,6 +87,16 @@ void GiftedCreditsBox(
not_null<PeerData*> to,
int count,
TimeId date);
void CreditsPrizeBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
const Data::GiftCode &data,
TimeId date);
void StarGiftViewBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
const Data::GiftCode &data,
not_null<HistoryItem*> item);
void ShowRefundInfoBox(
not_null<Window::SessionController*> controller,
FullMsgId refundItemId);