Implement upgraded unique gifts.

This commit is contained in:
John Preston 2024-12-26 10:10:44 +04:00
parent a87d19998e
commit 13d2f70c3a
22 changed files with 1077 additions and 157 deletions

View file

@ -2023,6 +2023,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_gift_unique_received" = "{user} sent you a unique collectible item"; "lng_action_gift_unique_received" = "{user} sent you a unique collectible item";
"lng_action_gift_sent" = "You sent a gift for {cost}"; "lng_action_gift_sent" = "You sent a gift for {cost}";
"lng_action_gift_unique_sent" = "You sent a unique collectible item"; "lng_action_gift_unique_sent" = "You sent a unique collectible item";
"lng_action_gift_upgraded" = "{user} turned the gift from you to a unique collectible";
"lng_action_gift_upgraded_mine" = "You turned the gift from {user} to a unique collectible";
"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}"; "lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}";
"lng_action_gift_for_stars#one" = "{count} Star"; "lng_action_gift_for_stars#one" = "{count} Star";
"lng_action_gift_for_stars#other" = "{count} Stars"; "lng_action_gift_for_stars#other" = "{count} Stars";
@ -2440,6 +2442,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_summary_button" = "Subscribe for {cost} per month"; "lng_premium_summary_button" = "Subscribe for {cost} per month";
"lng_premium_summary_new_badge" = "NEW"; "lng_premium_summary_new_badge" = "NEW";
"lng_soon_badge" = "Soon";
"lng_premium_success" = "You've successfully subscribed to Telegram Premium!"; "lng_premium_success" = "You've successfully subscribed to Telegram Premium!";
"lng_premium_unavailable" = "This feature requires subscription to **Telegram Premium**.\n\nUnfortunately, **Telegram Premium** is not available in your region."; "lng_premium_unavailable" = "This feature requires subscription to **Telegram Premium**.\n\nUnfortunately, **Telegram Premium** is not available in your region.";
@ -3279,7 +3282,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_sell_small#one" = "sell for {count} Star"; "lng_gift_sell_small#one" = "sell for {count} Star";
"lng_gift_sell_small#other" = "sell for {count} Stars"; "lng_gift_sell_small#other" = "sell for {count} Stars";
"lng_gift_upgrade_title" = "Upgrade Gift"; "lng_gift_upgrade_title" = "Upgrade Gift";
"lng_gift_upgrade_about" = "Turn your gift into a unique collectible that you can transfer or auction."; "lng_gift_upgrade_about" = "Turn your gift into a unique collectible\nthat you can transfer or auction.";
"lng_gift_upgrade_unique_title" = "Unique"; "lng_gift_upgrade_unique_title" = "Unique";
"lng_gift_upgrade_unique_about" = "Get a unique number, model, backdrop and symbol for your gift."; "lng_gift_upgrade_unique_about" = "Get a unique number, model, backdrop and symbol for your gift.";
"lng_gift_upgrade_transferable_title" = "Transferable"; "lng_gift_upgrade_transferable_title" = "Transferable";

View file

@ -94,7 +94,7 @@ constexpr auto kTransactionsLimit = 100;
const auto parsedGift = stargift const auto parsedGift = stargift
? FromTL(&peer->session(), *stargift) ? FromTL(&peer->session(), *stargift)
: std::optional<Data::StarGift>(); : std::optional<Data::StarGift>();
const auto giftStickerId = parsedGift ? parsedGift->stickerId : 0; const auto giftStickerId = parsedGift ? parsedGift->document->id : 0;
return Data::CreditsHistoryEntry{ return Data::CreditsHistoryEntry{
.id = qs(tl.data().vid()), .id = qs(tl.data().vid()),
.title = qs(tl.data().vtitle().value_or_empty()), .title = qs(tl.data().vtitle().value_or_empty()),

View file

@ -774,58 +774,58 @@ std::optional<Data::StarGift> FromTL(
.id = uint64(data.vid().v), .id = uint64(data.vid().v),
.stars = int64(data.vstars().v), .stars = int64(data.vstars().v),
.starsConverted = int64(data.vconvert_stars().v), .starsConverted = int64(data.vconvert_stars().v),
.stickerId = document->id, .starsUpgraded = int64(data.vupgrade_stars().value_or_empty()),
.document = document,
.limitedLeft = remaining.value_or_empty(), .limitedLeft = remaining.value_or_empty(),
.limitedCount = total.value_or_empty(), .limitedCount = total.value_or_empty(),
.firstSaleDate = data.vfirst_sale_date().value_or_empty(), .firstSaleDate = data.vfirst_sale_date().value_or_empty(),
.lastSaleDate = data.vlast_sale_date().value_or_empty(), .lastSaleDate = data.vlast_sale_date().value_or_empty(),
.upgradable = data.vupgrade_stars().has_value(),
.birthday = data.is_birthday(), .birthday = data.is_birthday(),
}); });
}, [&](const MTPDstarGiftUnique &data) { }, [&](const MTPDstarGiftUnique &data) {
const auto total = data.vavailability_total().v; const auto total = data.vavailability_total().v;
auto model = std::optional<Data::UniqueGiftModel>();
auto pattern = std::optional<Data::UniqueGiftPattern>();
for (const auto &attribute : data.vattributes().v) {
attribute.match([&](const MTPDstarGiftAttributeModel &data) {
model = FromTL(session, data);
}, [&](const MTPDstarGiftAttributePattern &data) {
pattern = FromTL(session, data);
}, [&](const MTPDstarGiftAttributeBackdrop &data) {
}, [&](const MTPDstarGiftAttributeOriginalDetails &data) {
});
}
if (!model
|| !model->document->sticker()
|| !pattern
|| !pattern->document->sticker()) {
return std::optional<Data::StarGift>();
}
auto result = Data::StarGift{ auto result = Data::StarGift{
.id = uint64(data.vid().v), .id = uint64(data.vid().v),
.unique = std::make_shared<Data::UniqueGift>(Data::UniqueGift{ .unique = std::make_shared<Data::UniqueGift>(Data::UniqueGift{
.title = qs(data.vtitle()), .title = qs(data.vtitle()),
.number = data.vnum().v, .number = data.vnum().v,
.ownerId = peerFromUser(UserId(data.vowner_id().v)), .ownerId = peerFromUser(UserId(data.vowner_id().v)),
.model = *model,
.pattern = *pattern,
}), }),
.document = model->document,
.limitedLeft = (total - data.vavailability_issued().v), .limitedLeft = (total - data.vavailability_issued().v),
.limitedCount = total, .limitedCount = total,
}; };
const auto unique = result.unique.get(); const auto unique = result.unique.get();
for (const auto &attribute : data.vattributes().v) { for (const auto &attribute : data.vattributes().v) {
attribute.match([&](const MTPDstarGiftAttributeModel &data) { attribute.match([&](const MTPDstarGiftAttributeModel &data) {
unique->model.name = qs(data.vname());
unique->model.rarityPermille = data.vrarity_permille().v;
result.stickerId = data.vdocument_id().v;
}, [&](const MTPDstarGiftAttributePattern &data) { }, [&](const MTPDstarGiftAttributePattern &data) {
unique->pattern.name = qs(data.vname());
unique->pattern.rarityPermille = data.vrarity_permille().v;
unique->pattern.documentId = data.vdocument_id().v;
}, [&](const MTPDstarGiftAttributeBackdrop &data) { }, [&](const MTPDstarGiftAttributeBackdrop &data) {
unique->backdrop.name = qs(data.vname()); unique->backdrop = FromTL(data);
unique->backdrop.rarityPermille = data.vrarity_permille().v;
unique->backdrop.centerColor = Ui::ColorFromSerialized(
data.vcenter_color());
unique->backdrop.edgeColor = Ui::ColorFromSerialized(
data.vedge_color());
unique->backdrop.patternColor = Ui::ColorFromSerialized(
data.vpattern_color());
unique->backdrop.textColor = Ui::ColorFromSerialized(
data.vtext_color());
}, [&](const MTPDstarGiftAttributeOriginalDetails &data) { }, [&](const MTPDstarGiftAttributeOriginalDetails &data) {
unique->originalDetails.date = data.vdate().v; unique->originalDetails = FromTL(session, data);
unique->originalDetails.senderId = peerFromUser(
UserId(data.vsender_id().value_or_empty()));
unique->originalDetails.recipientId = peerFromUser(
UserId(data.vrecipient_id().v));
unique->originalDetails.message = data.vmessage()
? Api::ParseTextWithEntities(session, *data.vmessage())
: TextWithEntities();
}); });
} }
return result.stickerId ? result : std::optional<Data::StarGift>(); return std::make_optional(result);
}); });
} }
@ -849,15 +849,70 @@ std::optional<Data::UserStarGift> FromTL(
} }
: TextWithEntities()), : TextWithEntities()),
.starsConverted = int64(data.vconvert_stars().value_or_empty()), .starsConverted = int64(data.vconvert_stars().value_or_empty()),
.starsUpgraded = int64(data.vupgrade_stars().value_or_empty()),
.fromId = (data.vfrom_id() .fromId = (data.vfrom_id()
? peerFromUser(data.vfrom_id()->v) ? peerFromUser(data.vfrom_id()->v)
: PeerId()), : PeerId()),
.messageId = data.vmsg_id().value_or_empty(), .messageId = data.vmsg_id().value_or_empty(),
.date = data.vdate().v, .date = data.vdate().v,
.upgradable = data.is_can_upgrade(),
.anonymous = data.is_name_hidden(), .anonymous = data.is_name_hidden(),
.hidden = data.is_unsaved(), .hidden = data.is_unsaved(),
.mine = to->isSelf(), .mine = to->isSelf(),
}; };
} }
Data::UniqueGiftModel FromTL(
not_null<Main::Session*> session,
const MTPDstarGiftAttributeModel &data) {
auto result = Data::UniqueGiftModel{
.document = session->data().processDocument(data.vdocument()),
};
result.name = qs(data.vname());
result.rarityPermille = data.vrarity_permille().v;
return result;
}
Data::UniqueGiftPattern FromTL(
not_null<Main::Session*> session,
const MTPDstarGiftAttributePattern &data) {
auto result = Data::UniqueGiftPattern{
.document = session->data().processDocument(data.vdocument()),
};
result.document->overrideEmojiUsesTextColor(true);
result.name = qs(data.vname());
result.rarityPermille = data.vrarity_permille().v;
return result;
}
Data::UniqueGiftBackdrop FromTL(const MTPDstarGiftAttributeBackdrop &data) {
auto result = Data::UniqueGiftBackdrop();
result.name = qs(data.vname());
result.rarityPermille = data.vrarity_permille().v;
result.centerColor = Ui::ColorFromSerialized(
data.vcenter_color());
result.edgeColor = Ui::ColorFromSerialized(
data.vedge_color());
result.patternColor = Ui::ColorFromSerialized(
data.vpattern_color());
result.textColor = Ui::ColorFromSerialized(
data.vtext_color());
return result;
}
Data::UniqueGiftOriginalDetails FromTL(
not_null<Main::Session*> session,
const MTPDstarGiftAttributeOriginalDetails &data) {
auto result = Data::UniqueGiftOriginalDetails();
result.date = data.vdate().v;
result.senderId = peerFromUser(
UserId(data.vsender_id().value_or_empty()));
result.recipientId = peerFromUser(
UserId(data.vrecipient_id().v));
result.message = data.vmessage()
? ParseTextWithEntities(session, *data.vmessage())
: TextWithEntities();
return result;
}
} // namespace Api } // namespace Api

View file

@ -263,4 +263,16 @@ enum class RequirePremiumState {
not_null<UserData*> to, not_null<UserData*> to,
const MTPuserStarGift &gift); const MTPuserStarGift &gift);
[[nodiscard]] Data::UniqueGiftModel FromTL(
not_null<Main::Session*> session,
const MTPDstarGiftAttributeModel &data);
[[nodiscard]] Data::UniqueGiftPattern FromTL(
not_null<Main::Session*> session,
const MTPDstarGiftAttributePattern &data);
[[nodiscard]] Data::UniqueGiftBackdrop FromTL(
const MTPDstarGiftAttributeBackdrop &data);
[[nodiscard]] Data::UniqueGiftOriginalDetails FromTL(
not_null<Main::Session*> session,
const MTPDstarGiftAttributeOriginalDetails &data);
} // namespace Api } // namespace Api

View file

@ -338,13 +338,58 @@ void AddTableRow(
: 0; : 0;
label->resizeToNaturalWidth(width - toggleSkip); label->resizeToNaturalWidth(width - toggleSkip);
label->moveToLeft(0, 0, width); label->moveToLeft(0, 0, width);
if (toggle) { toggle->moveToLeft(
toggle->moveToLeft( label->width() + st::normalFont->spacew,
label->width() + st::normalFont->spacew, (st::giveawayGiftCodeValue.style.font->ascent
(st::giveawayGiftCodeValue.style.font->ascent - st::starGiftSmallButton.style.font->ascent),
- st::starGiftSmallButton.style.font->ascent), width);
width); }, label->lifetime());
}
label->heightValue() | rpl::start_with_next([=](int height) {
raw->resize(
raw->width(),
height + st::giveawayGiftCodeValueMargin.bottom());
}, raw->lifetime());
label->setAttribute(Qt::WA_TransparentForMouseEvents);
return result;
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeNonUniqueStatusTableValue(
not_null<QWidget*> parent,
not_null<Window::SessionNavigation*> controller,
Fn<void()> startUpgrade) {
auto result = object_ptr<Ui::RpWidget>(parent);
const auto raw = result.data();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
tr::lng_gift_unique_status_non(),
st::giveawayGiftCodeValue,
st::defaultPopupMenu);
const auto upgrade = Ui::CreateChild<Ui::RoundButton>(
raw,
tr::lng_gift_unique_status_upgrade(),
st::starGiftSmallButton);
upgrade->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
upgrade->setClickedCallback(startUpgrade);
rpl::combine(
raw->widthValue(),
upgrade->widthValue()
) | rpl::start_with_next([=](int width, int toggleWidth) {
const auto toggleSkip = toggleWidth
? (st::normalFont->spacew + toggleWidth)
: 0;
label->resizeToNaturalWidth(width - toggleSkip);
label->moveToLeft(0, 0, width);
upgrade->moveToLeft(
label->width() + st::normalFont->spacew,
(st::giveawayGiftCodeValue.style.font->ascent
- st::starGiftSmallButton.style.font->ascent),
width);
}, label->lifetime()); }, label->lifetime());
label->heightValue() | rpl::start_with_next([=](int height) { label->heightValue() | rpl::start_with_next([=](int height) {
@ -1092,7 +1137,8 @@ void AddStarGiftTable(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry, const Data::CreditsHistoryEntry &entry,
Fn<void(bool)> toggleVisibility, Fn<void(bool)> toggleVisibility,
Fn<void()> convertToStars) { Fn<void()> convertToStars,
Fn<void()> startUpgrade) {
auto table = container->add( auto table = container->add(
object_ptr<Ui::TableLayout>( object_ptr<Ui::TableLayout>(
container, container,
@ -1178,6 +1224,16 @@ void AddStarGiftTable(
std::move(amount), std::move(amount),
Ui::Text::WithEntities))); Ui::Text::WithEntities)));
} }
if (!entry.uniqueGift && startUpgrade) {
AddTableRow(
table,
tr::lng_gift_unique_status(),
MakeNonUniqueStatusTableValue(
table,
controller,
std::move(startUpgrade)),
marginWithButton);
}
if (!entry.description.empty()) { if (!entry.description.empty()) {
const auto makeContext = [=](Fn<void()> update) { const auto makeContext = [=](Fn<void()> update) {
return Core::MarkedTextContext{ return Core::MarkedTextContext{

View file

@ -59,7 +59,8 @@ void AddStarGiftTable(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry, const Data::CreditsHistoryEntry &entry,
Fn<void(bool)> toggleVisibility, Fn<void(bool)> toggleVisibility,
Fn<void()> convertToStars); Fn<void()> convertToStars,
Fn<void()> startUpgrade);
void AddCreditsHistoryEntryTable( void AddCreditsHistoryEntryTable(
not_null<Window::SessionNavigation*> controller, not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,

View file

@ -463,7 +463,7 @@ void SendCreditsBox(
}), }),
session, session,
st::creditsBoxButtonLabel, st::creditsBoxButtonLabel,
box->getDelegate()->style().button.textFg->c); &box->getDelegate()->style().button.textFg);
const auto buttonWidth = st::boxWidth const auto buttonWidth = st::boxWidth
- rect::m::sum::h(stBox.buttonPadding); - rect::m::sum::h(stBox.buttonPadding);
@ -524,7 +524,7 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
rpl::producer<TextWithEntities> text, rpl::producer<TextWithEntities> text,
Fn<std::any(Fn<void()> update)> context, Fn<std::any(Fn<void()> update)> context,
const style::FlatLabel &st, const style::FlatLabel &st,
std::optional<QColor> textFg) { const style::color *textFg) {
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>( const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
button, button,
rpl::single(QString()), rpl::single(QString()),
@ -539,7 +539,10 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
context([=] { buttonLabel->update(); })); context([=] { buttonLabel->update(); }));
}, buttonLabel->lifetime()); }, buttonLabel->lifetime());
if (textFg) { if (textFg) {
buttonLabel->setTextColorOverride(textFg); buttonLabel->setTextColorOverride((*textFg)->c);
style::PaletteChanged() | rpl::start_with_next([=] {
buttonLabel->setTextColorOverride((*textFg)->c);
}, buttonLabel->lifetime());
} }
button->sizeValue( button->sizeValue(
) | rpl::start_with_next([=](const QSize &size) { ) | rpl::start_with_next([=](const QSize &size) {
@ -561,7 +564,7 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
rpl::producer<TextWithEntities> text, rpl::producer<TextWithEntities> text,
not_null<Main::Session*> session, not_null<Main::Session*> session,
const style::FlatLabel &st, const style::FlatLabel &st,
std::optional<QColor> textFg) { const style::color *textFg) {
return SetButtonMarkedLabel(button, text, [=](Fn<void()> update) { return SetButtonMarkedLabel(button, text, [=](Fn<void()> update) {
return Core::MarkedTextContext{ return Core::MarkedTextContext{
.session = session, .session = session,

View file

@ -43,14 +43,14 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
rpl::producer<TextWithEntities> text, rpl::producer<TextWithEntities> text,
Fn<std::any(Fn<void()> update)> context, Fn<std::any(Fn<void()> update)> context,
const style::FlatLabel &st, const style::FlatLabel &st,
std::optional<QColor> textFg = {}); const style::color *textFg = nullptr);
not_null<FlatLabel*> SetButtonMarkedLabel( not_null<FlatLabel*> SetButtonMarkedLabel(
not_null<RpWidget*> button, not_null<RpWidget*> button,
rpl::producer<TextWithEntities> text, rpl::producer<TextWithEntities> text,
not_null<Main::Session*> session, not_null<Main::Session*> session,
const style::FlatLabel &st, const style::FlatLabel &st,
std::optional<QColor> textFg = {}); const style::color *textFg = nullptr);
void SendStarGift( void SendStarGift(
not_null<Main::Session*> session, not_null<Main::Session*> session,

File diff suppressed because it is too large Load diff

View file

@ -7,12 +7,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
namespace Data {
struct UniqueGift;
} // namespace Data
namespace Window { namespace Window {
class SessionController; class SessionController;
} // namespace Window } // namespace Window
namespace Ui { namespace Ui {
class VerticalLayout;
void ChooseStarGiftRecipient( void ChooseStarGiftRecipient(
not_null<Window::SessionController*> controller); not_null<Window::SessionController*> controller);
@ -20,4 +26,17 @@ void ShowStarGiftBox(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<PeerData*> peer); not_null<PeerData*> peer);
void AddUniqueGiftCover(
not_null<VerticalLayout*> container,
rpl::producer<Data::UniqueGift> data,
rpl::producer<QString> subtitleOverride = nullptr);
void ShowStarGiftUpgradeBox(
not_null<Window::SessionController*> controller,
uint64 stargiftId,
not_null<UserData*> user,
MsgId itemId,
int stars,
Fn<void(bool)> ready);
} // namespace Ui } // namespace Ui

View file

@ -65,6 +65,7 @@ struct CreditsHistoryEntry final {
uint64 bareGiveawayMsgId = 0; uint64 bareGiveawayMsgId = 0;
uint64 bareGiftStickerId = 0; uint64 bareGiftStickerId = 0;
uint64 bareActorId = 0; uint64 bareActorId = 0;
uint64 stargiftId = 0;
std::shared_ptr<UniqueGift> uniqueGift; std::shared_ptr<UniqueGift> uniqueGift;
StarsAmount starrefAmount; StarsAmount starrefAmount;
int starrefCommission = 0; int starrefCommission = 0;
@ -76,6 +77,7 @@ struct CreditsHistoryEntry final {
int limitedCount = 0; int limitedCount = 0;
int limitedLeft = 0; int limitedLeft = 0;
int starsConverted = 0; int starsConverted = 0;
int starsUpgraded = 0;
int floodSkip = 0; int floodSkip = 0;
bool converted : 1 = false; bool converted : 1 = false;
bool anonymous : 1 = false; bool anonymous : 1 = false;
@ -83,6 +85,7 @@ struct CreditsHistoryEntry final {
bool savedToProfile : 1 = false; bool savedToProfile : 1 = false;
bool fromGiftsList : 1 = false; bool fromGiftsList : 1 = false;
bool soldOutInfo : 1 = false; bool soldOutInfo : 1 = false;
bool canUpgradeGift : 1 = false;
bool reaction : 1 = false; bool reaction : 1 = false;
bool refunded : 1 = false; bool refunded : 1 = false;
bool pending : 1 = false; bool pending : 1 = false;

View file

@ -136,20 +136,24 @@ enum class GiftType : uchar {
struct GiftCode { struct GiftCode {
QString slug; QString slug;
DocumentId stickerId = 0; uint64 stargiftId = 0;
DocumentData *document = nullptr;
std::shared_ptr<UniqueGift> unique; std::shared_ptr<UniqueGift> unique;
TextWithEntities message; TextWithEntities message;
ChannelData *channel = nullptr; ChannelData *channel = nullptr;
MsgId giveawayMsgId = 0; MsgId giveawayMsgId = 0;
int starsConverted = 0; int starsConverted = 0;
int starsUpgraded = 0;
int limitedCount = 0; int limitedCount = 0;
int limitedLeft = 0; int limitedLeft = 0;
int count = 0; int count = 0;
GiftType type = GiftType::Premium; GiftType type = GiftType::Premium;
bool viaGiveaway : 1 = false; bool viaGiveaway : 1 = false;
bool upgradable : 1 = false;
bool unclaimed : 1 = false; bool unclaimed : 1 = false;
bool anonymous : 1 = false; bool anonymous : 1 = false;
bool converted : 1 = false; bool converted : 1 = false;
bool upgraded : 1 = false;
bool saved : 1 = false; bool saved : 1 = false;
}; };

View file

@ -15,10 +15,11 @@ struct UniqueGiftAttribute {
}; };
struct UniqueGiftModel : UniqueGiftAttribute { struct UniqueGiftModel : UniqueGiftAttribute {
not_null<DocumentData*> document;
}; };
struct UniqueGiftPattern : UniqueGiftAttribute { struct UniqueGiftPattern : UniqueGiftAttribute {
DocumentId documentId = 0; not_null<DocumentData*> document;
}; };
struct UniqueGiftBackdrop : UniqueGiftAttribute { struct UniqueGiftBackdrop : UniqueGiftAttribute {
@ -50,11 +51,13 @@ struct StarGift {
std::shared_ptr<UniqueGift> unique; std::shared_ptr<UniqueGift> unique;
int64 stars = 0; int64 stars = 0;
int64 starsConverted = 0; int64 starsConverted = 0;
DocumentId stickerId = 0; int64 starsUpgraded = 0;
not_null<DocumentData*> document;
int limitedLeft = 0; int limitedLeft = 0;
int limitedCount = 0; int limitedCount = 0;
TimeId firstSaleDate = 0; TimeId firstSaleDate = 0;
TimeId lastSaleDate = 0; TimeId lastSaleDate = 0;
bool upgradable = false;
bool birthday = false; bool birthday = false;
friend inline bool operator==( friend inline bool operator==(
@ -66,9 +69,11 @@ struct UserStarGift {
StarGift info; StarGift info;
TextWithEntities message; TextWithEntities message;
int64 starsConverted = 0; int64 starsConverted = 0;
int64 starsUpgraded = 0;
PeerId fromId = 0; PeerId fromId = 0;
MsgId messageId = 0; MsgId messageId = 0;
TimeId date = 0; TimeId date = 0;
bool upgradable = false;
bool anonymous = false; bool anonymous = false;
bool hidden = false; bool hidden = false;
bool mine = false; bool mine = false;

View file

@ -1692,6 +1692,18 @@ ServiceAction ParseServiceAction(
.anonymous = data.is_name_hidden(), .anonymous = data.is_name_hidden(),
}; };
}); });
}, [&](const MTPDmessageActionStarGiftUnique &data) {
data.vgift().match([&](const MTPDstarGift &gift) {
result.content = ActionStarGift{
.giftId = uint64(gift.vid().v),
.stars = int64(gift.vstars().v),
.limited = gift.is_limited(),
};
}, [&](const MTPDstarGiftUnique &gift) {
result.content = ActionStarGift{
.giftId = uint64(gift.vid().v),
};
});
}, [](const MTPDmessageActionEmpty &data) {}); }, [](const MTPDmessageActionEmpty &data) {});
return result; return result;
} }

View file

@ -5424,6 +5424,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
lt_user, lt_user,
Ui::Text::Link(peer->shortName(), 1), // Link 1. Ui::Text::Link(peer->shortName(), 1), // Link 1.
Ui::Text::WithEntities); Ui::Text::WithEntities);
return result;
} }
const auto cost = TextWithEntities{ const auto cost = TextWithEntities{
tr::lng_action_gift_for_stars(tr::now, lt_count, stars), tr::lng_action_gift_for_stars(tr::now, lt_count, stars),
@ -5455,6 +5456,22 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
return result; return result;
}; };
auto prepareStarGiftUnique = [&](
const MTPDmessageActionStarGiftUnique &action) {
auto result = PreparedServiceText();
const auto isSelf = _from->isSelf();
const auto peer = isSelf ? _history->peer : _from;
result.links.push_back(peer->createOpenLink());
result.text = (isSelf
? tr::lng_action_gift_upgraded_mine
: tr::lng_action_gift_upgraded)(
tr::now,
lt_user,
Ui::Text::Link(peer->shortName(), 1), // Link 1.
Ui::Text::WithEntities);
return result;
};
setServiceText(action.match( setServiceText(action.match(
prepareChatAddUserText, prepareChatAddUserText,
prepareChatJoinedByLink, prepareChatJoinedByLink,
@ -5501,6 +5518,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
prepareGiftStars, prepareGiftStars,
prepareGiftPrize, prepareGiftPrize,
prepareStarGift, prepareStarGift,
prepareStarGiftUnique,
PrepareEmptyText<MTPDmessageActionRequestedPeerSentMe>, PrepareEmptyText<MTPDmessageActionRequestedPeerSentMe>,
PrepareErrorText<MTPDmessageActionEmpty>)); PrepareErrorText<MTPDmessageActionEmpty>));
@ -5639,18 +5657,44 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
} }
: TextWithEntities()), : TextWithEntities()),
.starsConverted = int(data.vconvert_stars().value_or_empty()), .starsConverted = int(data.vconvert_stars().value_or_empty()),
.starsUpgraded = int(data.vupgrade_stars().value_or_empty()),
.type = Data::GiftType::StarGift, .type = Data::GiftType::StarGift,
.upgradable = data.is_can_upgrade(),
.anonymous = data.is_name_hidden(), .anonymous = data.is_name_hidden(),
.converted = data.is_converted(), .converted = data.is_converted(),
.upgraded = data.is_upgraded(),
.saved = data.is_saved(), .saved = data.is_saved(),
}; };
if (auto gift = Api::FromTL(&history()->session(), data.vgift())) { if (auto gift = Api::FromTL(&history()->session(), data.vgift())) {
fields.stickerId = gift->stickerId; fields.stargiftId = gift->id;
fields.document = gift->document;
fields.limitedCount = gift->limitedCount; fields.limitedCount = gift->limitedCount;
fields.limitedLeft = gift->limitedLeft; fields.limitedLeft = gift->limitedLeft;
fields.count = gift->stars; fields.count = gift->stars;
fields.unique = gift->unique; fields.unique = gift->unique;
} }
_media = std::make_unique<Data::MediaGiftBox>(
this,
_from,
std::move(fields));
}, [&](const MTPDmessageActionStarGiftUnique &data) {
using Fields = Data::GiftCode;
auto fields = Fields{
.type = Data::GiftType::StarGift,
.saved = data.is_saved(),
};
if (auto gift = Api::FromTL(&history()->session(), data.vgift())) {
fields.stargiftId = gift->id;
fields.document = gift->document;
fields.limitedCount = gift->limitedCount;
fields.limitedLeft = gift->limitedLeft;
fields.count = gift->stars;
fields.unique = gift->unique;
}
_media = std::make_unique<Data::MediaGiftBox>(
this,
_from,
std::move(fields));
}, [](const auto &) { }, [](const auto &) {
}); });
} }

View file

@ -294,20 +294,13 @@ int PremiumGift::credits() const {
void PremiumGift::ensureStickerCreated() const { void PremiumGift::ensureStickerCreated() const {
if (_sticker) { if (_sticker) {
return; return;
} else if (const auto stickerId = _data.stickerId) { } else if (const auto document = _data.document) {
if (!_lifetime) { const auto sticker = document->sticker();
const auto owner = &_parent->history()->owner(); Assert(sticker != nullptr);
_lifetime = owner->customEmojiManager().resolve( _sticker.emplace(_parent, document, false, _parent);
stickerId _sticker->setPlayingOnce(true);
) | rpl::start_with_next([=](not_null<DocumentData*> document) { _sticker->initSize(st::msgServiceGiftBoxStickerSize);
const auto sticker = document->sticker(); _parent->repaint();
Assert(sticker != nullptr);
_sticker.emplace(_parent, document, false, _parent);
_sticker->setPlayingOnce(true);
_sticker->initSize(st::msgServiceGiftBoxStickerSize);
_parent->repaint();
});
}
return; return;
} }
const auto &session = _parent->history()->session(); const auto &session = _parent->history()->session();

View file

@ -60,7 +60,6 @@ private:
const not_null<Data::MediaGiftBox*> _gift; const not_null<Data::MediaGiftBox*> _gift;
const Data::GiftCode &_data; const Data::GiftCode &_data;
mutable std::optional<Sticker> _sticker; mutable std::optional<Sticker> _sticker;
mutable rpl::lifetime _lifetime;
}; };

View file

@ -452,16 +452,15 @@ not_null<StickerPremiumMark*> Delegate::hiddenMark() {
return _hiddenMark.get(); return _hiddenMark.get();
} }
DocumentId GiftStickerId( DocumentData *LookupGiftSticker(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const GiftDescriptor &descriptor) { const GiftDescriptor &descriptor) {
return v::match(descriptor, [&](GiftTypePremium data) { return v::match(descriptor, [&](GiftTypePremium data) {
auto &packs = session->giftBoxStickersPacks(); auto &packs = session->giftBoxStickersPacks();
packs.load(); packs.load();
const auto document = packs.lookup(data.months); return packs.lookup(data.months);
return document ? document->id : DocumentId();
}, [&](GiftTypeStars data) { }, [&](GiftTypeStars data) {
return data.info.stickerId; return data.info.document.get();
}); });
} }
@ -486,9 +485,7 @@ rpl::producer<not_null<DocumentData*>> GiftStickerValue(
return not_null(document); return not_null(document);
}) | rpl::type_erased(); }) | rpl::type_erased();
}, [&](GiftTypeStars data) { }, [&](GiftTypeStars data) {
return session->data().customEmojiManager().resolve( return rpl::single(data.info.document) | rpl::type_erased();
data.info.stickerId
) | rpl::map_error_to_done();
}); });
} }

View file

@ -132,7 +132,7 @@ private:
}; };
[[nodiscard]] DocumentId GiftStickerId( [[nodiscard]] DocumentData *LookupGiftSticker(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const GiftDescriptor &descriptor); const GiftDescriptor &descriptor);

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer_rpl.h" #include "base/timer_rpl.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "boxes/gift_premium_box.h" #include "boxes/gift_premium_box.h"
#include "boxes/star_gift_box.h"
#include "chat_helpers/stickers_gift_box_pack.h" #include "chat_helpers/stickers_gift_box_pack.h"
#include "chat_helpers/stickers_lottie.h" #include "chat_helpers/stickers_lottie.h"
#include "core/application.h" #include "core/application.h"
@ -174,7 +175,6 @@ void ToggleStarGiftSaved(
const auto weak = base::make_weak(window); const auto weak = base::make_weak(window);
api->request(MTPpayments_SaveStarGift( api->request(MTPpayments_SaveStarGift(
MTP_flags(save ? Flag(0) : Flag::f_unsave), MTP_flags(save ? Flag(0) : Flag::f_unsave),
sender->inputUser,
MTP_int(itemId.bare) MTP_int(itemId.bare)
)).done([=] { )).done([=] {
done(true); done(true);
@ -232,7 +232,6 @@ void ConvertStarGift(
const auto api = &window->session().api(); const auto api = &window->session().api();
const auto weak = base::make_weak(window); const auto weak = base::make_weak(window);
api->request(MTPpayments_ConvertStarGift( api->request(MTPpayments_ConvertStarGift(
sender->inputUser,
MTP_int(itemId) MTP_int(itemId)
)).done([=] { )).done([=] {
if (const auto strong = weak.get()) { if (const auto strong = weak.get()) {
@ -1329,14 +1328,36 @@ void ReceiptCreditsBox(
} }
}); });
}; };
const auto upgradeGuard = std::make_shared<bool>();
const auto upgrade = [=] {
if (const auto window = weakWindow.get()) {
const auto itemId = MsgId(e.bareMsgId);
if (*upgradeGuard) {
return;
}
*upgradeGuard = true;
using namespace Ui;
ShowStarGiftUpgradeBox(
window,
e.stargiftId,
starGiftSender,
itemId,
e.starsUpgraded,
[=](bool) { *upgradeGuard = false; });
}
};
const auto canToggle = canConvert || couldConvert || nonConvertible; const auto canToggle = canConvert || couldConvert || nonConvertible;
const auto canUpgrade = e.stargiftId
&& e.canUpgradeGift
&& !e.uniqueGift;
AddStarGiftTable( AddStarGiftTable(
controller, controller,
content, content,
e, e,
canToggle ? toggleVisibility : Fn<void(bool)>(), canToggle ? toggleVisibility : Fn<void(bool)>(),
canConvert ? convert : Fn<void()>()); canConvert ? convert : Fn<void()>(),
canUpgrade ? upgrade : Fn<void()>());
} else { } else {
AddCreditsHistoryEntryTable(controller, content, e); AddCreditsHistoryEntryTable(controller, content, e);
AddSubscriptionEntryTable(controller, content, s); AddSubscriptionEntryTable(controller, content, s);
@ -1584,16 +1605,19 @@ void UserStarGiftBox(
.credits = StarsAmount(data.info.stars), .credits = StarsAmount(data.info.stars),
.bareMsgId = uint64(data.messageId.bare), .bareMsgId = uint64(data.messageId.bare),
.barePeerId = data.fromId.value, .barePeerId = data.fromId.value,
.bareGiftStickerId = data.info.stickerId, .bareGiftStickerId = data.info.document->id,
.stargiftId = data.info.id,
.peerType = Data::CreditsHistoryEntry::PeerType::Peer, .peerType = Data::CreditsHistoryEntry::PeerType::Peer,
.limitedCount = data.info.limitedCount, .limitedCount = data.info.limitedCount,
.limitedLeft = data.info.limitedLeft, .limitedLeft = data.info.limitedLeft,
.starsConverted = int(data.info.starsConverted), .starsConverted = int(data.info.starsConverted),
.starsUpgraded = int(data.starsUpgraded),
.converted = false, .converted = false,
.anonymous = data.anonymous, .anonymous = data.anonymous,
.stargift = true, .stargift = true,
.savedToProfile = !data.hidden, .savedToProfile = !data.hidden,
.fromGiftsList = true, .fromGiftsList = true,
.canUpgradeGift = data.upgradable,
.in = data.mine, .in = data.mine,
.gift = true, .gift = true,
}, },
@ -1615,15 +1639,18 @@ void StarGiftViewBox(
.credits = StarsAmount(data.count), .credits = StarsAmount(data.count),
.bareMsgId = uint64(item->id.bare), .bareMsgId = uint64(item->id.bare),
.barePeerId = item->history()->peer->id.value, .barePeerId = item->history()->peer->id.value,
.bareGiftStickerId = data.stickerId, .bareGiftStickerId = data.document ? data.document->id : 0,
.stargiftId = data.stargiftId,
.peerType = Data::CreditsHistoryEntry::PeerType::Peer, .peerType = Data::CreditsHistoryEntry::PeerType::Peer,
.limitedCount = data.limitedCount, .limitedCount = data.limitedCount,
.limitedLeft = data.limitedLeft, .limitedLeft = data.limitedLeft,
.starsConverted = data.starsConverted, .starsConverted = data.starsConverted,
.starsUpgraded = data.starsUpgraded,
.converted = data.converted, .converted = data.converted,
.anonymous = data.anonymous, .anonymous = data.anonymous,
.stargift = true, .stargift = true,
.savedToProfile = data.saved, .savedToProfile = data.saved,
.canUpgradeGift = data.upgradable,
.in = true, .in = true,
.gift = true, .gift = true,
}, },

View file

@ -173,3 +173,15 @@ creditsHistoryEntriesList: PeerList(defaultPeerList) {
} }
subscriptionCreditsBadgePadding: margins(10px, 1px, 8px, 3px); subscriptionCreditsBadgePadding: margins(10px, 1px, 8px, 3px);
uniqueGiftModelTop: 20px;
uniqueGiftTitle: FlatLabel(boxTitle) {
align: align(top);
}
uniqueGiftTitleTop: 140px;
uniqueGiftSubtitle: FlatLabel(defaultFlatLabel) {
minWidth: 256px;
align: align(top);
}
uniqueGiftSubtitleTop: 170px;
uniqueGiftBottom: 20px;

View file

@ -25,6 +25,7 @@ not_null<Ui::RpWidget*> CreateNewBadge(
std::move(text), std::move(text),
st::settingsPremiumNewBadge), st::settingsPremiumNewBadge),
st::settingsPremiumNewBadgePadding); st::settingsPremiumNewBadgePadding);
badge->show();
badge->setAttribute(Qt::WA_TransparentForMouseEvents); badge->setAttribute(Qt::WA_TransparentForMouseEvents);
badge->paintRequest() | rpl::start_with_next([=] { badge->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(badge); auto p = QPainter(badge);