Allow transferring unique gifts from Send Gift.

This commit is contained in:
John Preston 2025-03-18 12:02:44 +04:00
parent c0fed4d2c3
commit 4fc026b13c
6 changed files with 208 additions and 64 deletions

View file

@ -3378,6 +3378,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_stars_limited" = "limited"; "lng_gift_stars_limited" = "limited";
"lng_gift_stars_sold_out" = "sold out"; "lng_gift_stars_sold_out" = "sold out";
"lng_gift_stars_tabs_all" = "All Gifts"; "lng_gift_stars_tabs_all" = "All Gifts";
"lng_gift_stars_tabs_my" = "My Gifts";
"lng_gift_stars_tabs_limited" = "Limited"; "lng_gift_stars_tabs_limited" = "Limited";
"lng_gift_stars_tabs_in_stock" = "In Stock"; "lng_gift_stars_tabs_in_stock" = "In Stock";
"lng_gift_send_title" = "Send a Gift"; "lng_gift_send_title" = "Send a Gift";
@ -3407,7 +3408,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_limited_of_one" = "unique"; "lng_gift_limited_of_one" = "unique";
"lng_gift_limited_of_count" = "1 of {amount}"; "lng_gift_limited_of_count" = "1 of {amount}";
"lng_gift_collectible_tag" = "gift"; "lng_gift_collectible_tag" = "gift";
"lng_gift_price_unique" = "Unique";
"lng_gift_view_unpack" = "Unpack"; "lng_gift_view_unpack" = "Unpack";
"lng_gift_anonymous_hint" = "Only you can see the sender's name."; "lng_gift_anonymous_hint" = "Only you can see the sender's name.";
"lng_gift_anonymous_hint_channel" = "Only admins of this channel can see the sender's name."; "lng_gift_anonymous_hint_channel" = "Only admins of this channel can see the sender's name.";

View file

@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_controllers.h" #include "boxes/peer_list_controllers.h"
#include "boxes/premium_preview_box.h" #include "boxes/premium_preview_box.h"
#include "boxes/send_credits_box.h" #include "boxes/send_credits_box.h"
#include "boxes/transfer_gift_box.h"
#include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/emoji_suggestions_widget.h"
#include "chat_helpers/message_field.h" #include "chat_helpers/message_field.h"
#include "chat_helpers/stickers_gift_box_pack.h" #include "chat_helpers/stickers_gift_box_pack.h"
@ -102,8 +103,10 @@ namespace Ui {
namespace { namespace {
constexpr auto kPriceTabAll = 0; constexpr auto kPriceTabAll = 0;
constexpr auto kPriceTabLimited = -2;
constexpr auto kPriceTabInStock = -1; constexpr auto kPriceTabInStock = -1;
constexpr auto kPriceTabLimited = -2;
constexpr auto kPriceTabMy = -3;
constexpr auto kMyGiftsPerPage = 50;
constexpr auto kGiftMessageLimit = 255; constexpr auto kGiftMessageLimit = 255;
constexpr auto kSentToastDuration = 3 * crl::time(1000); constexpr auto kSentToastDuration = 3 * crl::time(1000);
constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000); constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000);
@ -119,6 +122,11 @@ struct PremiumGiftsDescriptor {
std::shared_ptr<Api::PremiumGiftCodeOptions> api; std::shared_ptr<Api::PremiumGiftCodeOptions> api;
}; };
struct MyGiftsDescriptor {
std::vector<Data::SavedStarGift> list;
QString offset;
};
struct GiftsDescriptor { struct GiftsDescriptor {
std::vector<GiftDescriptor> list; std::vector<GiftDescriptor> list;
std::shared_ptr<Api::PremiumGiftCodeOptions> api; std::shared_ptr<Api::PremiumGiftCodeOptions> api;
@ -674,7 +682,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
} }
ranges::sort(list, ranges::less(), &GiftTypePremium::months); ranges::sort(list, ranges::less(), &GiftTypePremium::months);
auto &map = Map[session]; auto &map = Map[session];
if (map.last.list != list) { if (map.last.list != list || list.empty()) {
map.last = PremiumGiftsDescriptor{ map.last = PremiumGiftsDescriptor{
std::move(list), std::move(list),
api, api,
@ -748,7 +756,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
}); });
auto &map = Map[session]; auto &map = Map[session];
if (map.last != list) { if (map.last != list || list.empty()) {
map.last = list; map.last = list;
consumer.put_next(filtered(std::move(list))); consumer.put_next(filtered(std::move(list)));
} }
@ -758,6 +766,47 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
}; };
} }
[[nodiscard]] rpl::producer<MyGiftsDescriptor> UniqueGiftsSlice(
not_null<Main::Session*> session) {
return [=](auto consumer) {
using Flag = MTPpayments_GetSavedStarGifts::Flag;
const auto user = session->user();
const auto requestId = session->api().request(
MTPpayments_GetSavedStarGifts(
MTP_flags(Flag::f_exclude_limited | Flag::f_exclude_unlimited),
user->input,
MTP_string(QString()),
MTP_int(kMyGiftsPerPage)
)).done([=](const MTPpayments_SavedStarGifts &result) {
auto gifts = MyGiftsDescriptor();
const auto &data = result.data();
if (const auto next = data.vnext_offset()) {
gifts.offset = qs(*next);
}
const auto owner = &session->data();
owner->processUsers(data.vusers());
owner->processChats(data.vchats());
gifts.list.reserve(data.vgifts().v.size());
for (const auto &gift : data.vgifts().v) {
if (auto parsed = Api::FromTL(user, gift)) {
gifts.list.push_back(std::move(*parsed));
}
}
consumer.put_next(std::move(gifts));
consumer.put_done();
}).fail([=] {
consumer.put_next({});
consumer.put_done();
}).send();
auto lifetime = rpl::lifetime();
lifetime.add([=] { session->api().request(requestId).cancel(); });
return lifetime;
};
}
[[nodiscard]] Text::String TabTextForPrice( [[nodiscard]] Text::String TabTextForPrice(
not_null<Main::Session*> session, not_null<Main::Session*> session,
int price) { int price) {
@ -766,6 +815,8 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
}; };
if (price == kPriceTabAll) { if (price == kPriceTabAll) {
return simple(tr::lng_gift_stars_tabs_all(tr::now)); return simple(tr::lng_gift_stars_tabs_all(tr::now));
} else if (price == kPriceTabMy) {
return simple(tr::lng_gift_stars_tabs_my(tr::now));
} else if (price == kPriceTabLimited) { } else if (price == kPriceTabLimited) {
return simple(tr::lng_gift_stars_tabs_limited(tr::now)); return simple(tr::lng_gift_stars_tabs_limited(tr::now));
} else if (price == kPriceTabInStock) { } else if (price == kPriceTabInStock) {
@ -788,7 +839,8 @@ struct GiftPriceTabs {
[[nodiscard]] GiftPriceTabs MakeGiftsPriceTabs( [[nodiscard]] GiftPriceTabs MakeGiftsPriceTabs(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<PeerData*> peer, not_null<PeerData*> peer,
rpl::producer<std::vector<GiftTypeStars>> gifts) { rpl::producer<std::vector<GiftTypeStars>> gifts,
bool hasMyUnique) {
auto widget = object_ptr<RpWidget>((QWidget*)nullptr); auto widget = object_ptr<RpWidget>((QWidget*)nullptr);
const auto raw = widget.data(); const auto raw = widget.data();
@ -812,6 +864,13 @@ struct GiftPriceTabs {
int pressed = -1; int pressed = -1;
int active = -1; int active = -1;
}; };
const auto user = peer->asUser();
const auto disallowed = user
? user->disallowedGiftTypes()
: Api::DisallowedGiftType();
if (disallowed & Api::DisallowedGiftType::Unique) {
hasMyUnique = false;
}
const auto state = raw->lifetime().make_state<State>(); const auto state = raw->lifetime().make_state<State>();
const auto scroll = [=] { const auto scroll = [=] {
return QPoint(int(base::SafeRound(state->scroll)), 0); return QPoint(int(base::SafeRound(state->scroll)), 0);
@ -819,25 +878,14 @@ struct GiftPriceTabs {
state->prices = std::move( state->prices = std::move(
gifts gifts
) | rpl::map([](const std::vector<GiftTypeStars> &gifts) { ) | rpl::map([=](const std::vector<GiftTypeStars> &gifts) {
auto result = std::vector<int>(); auto result = std::vector<int>();
result.push_back(kPriceTabAll); result.push_back(kPriceTabAll);
auto special = 1;
auto same = true;
auto sameKey = 0;
auto hasNonSoldOut = false; auto hasNonSoldOut = false;
auto hasSoldOut = false; auto hasSoldOut = false;
auto hasLimited = false; auto hasLimited = false;
auto hasNonLimited = false;
for (const auto &gift : gifts) { for (const auto &gift : gifts) {
if (same) {
const auto key = gift.info.stars
* (gift.info.limitedCount ? -1 : 1);
if (!sameKey) {
sameKey = key;
} else if (sameKey != key) {
same = false;
}
}
if (IsSoldOut(gift.info)) { if (IsSoldOut(gift.info)) {
hasSoldOut = true; hasSoldOut = true;
} else { } else {
@ -845,19 +893,21 @@ struct GiftPriceTabs {
} }
if (gift.info.limitedCount) { if (gift.info.limitedCount) {
hasLimited = true; hasLimited = true;
} else {
hasNonLimited = true;
} }
if (!ranges::contains(result, gift.info.stars)) { if (!ranges::contains(result, gift.info.stars)) {
result.push_back(gift.info.stars); result.push_back(gift.info.stars);
} }
} }
if (same) { if (hasMyUnique && !gifts.empty()) {
return std::vector<int>(); result.push_back(kPriceTabMy);
} }
if (hasSoldOut && hasNonSoldOut) { if (hasSoldOut && hasNonSoldOut) {
result.insert(begin(result) + (special++), kPriceTabInStock); result.push_back(kPriceTabInStock);
} }
if (hasLimited) { if (hasLimited && hasNonLimited) {
result.insert(begin(result) + (special++), kPriceTabLimited); result.push_back(kPriceTabLimited);
} }
ranges::sort(begin(result) + 1, end(result)); ranges::sort(begin(result) + 1, end(result));
return result; return result;
@ -1460,6 +1510,13 @@ void SendGiftBox(
const auto limited = stars const auto limited = stars
&& (stars->info.limitedCount > stars->info.limitedLeft) && (stars->info.limitedCount > stars->info.limitedLeft)
&& (stars->info.limitedLeft > 0); && (stars->info.limitedLeft > 0);
const auto costToUpgrade = stars ? stars->info.starsToUpgrade : 0;
const auto user = peer->asUser();
const auto disallowed = user
? user->disallowedGiftTypes()
: Api::DisallowedGiftTypes();
const auto disallowLimited = !peer->isSelf()
&& (disallowed & Api::DisallowedGiftType::Limited);
box->setStyle(limited ? st::giftLimitedBox : st::giftBox); box->setStyle(limited ? st::giftLimitedBox : st::giftBox);
box->setWidth(st::boxWideWidth); box->setWidth(st::boxWideWidth);
box->setTitle(tr::lng_gift_send_title()); box->setTitle(tr::lng_gift_send_title());
@ -1479,6 +1536,7 @@ void SendGiftBox(
state->details = GiftDetails{ state->details = GiftDetails{
.descriptor = descriptor, .descriptor = descriptor,
.randomId = base::RandomValue<uint64>(), .randomId = base::RandomValue<uint64>(),
.upgraded = disallowLimited && (costToUpgrade > 0),
}; };
peer->updateFull(); peer->updateFull();
state->messageAllowed = peer->session().changes().peerFlagsValue( state->messageAllowed = peer->session().changes().peerFlagsValue(
@ -1577,13 +1635,13 @@ void SendGiftBox(
session, session,
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow }); { .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
if (stars) { if (stars) {
const auto cost = stars->info.starsToUpgrade; if (costToUpgrade > 0 && !peer->isSelf() && !disallowLimited) {
if (cost > 0 && !peer->isSelf()) {
const auto id = stars->info.id; const auto id = stars->info.id;
const auto showing = std::make_shared<bool>(); const auto showing = std::make_shared<bool>();
AddDivider(container); AddDivider(container);
AddSkip(container); AddSkip(container);
AddUpgradeButton(container, session, cost, peer, [=](bool on) { AddUpgradeButton(container, session, costToUpgrade, peer, [=](
bool on) {
auto now = state->details.current(); auto now = state->details.current();
now.upgraded = on; now.upgraded = on;
state->details = std::move(now); state->details = std::move(now);
@ -1597,7 +1655,7 @@ void SendGiftBox(
.stargiftId = id, .stargiftId = id,
.ready = [=](bool) { *showing = false; }, .ready = [=](bool) { *showing = false; },
.peer = peer, .peer = peer,
.cost = int(cost), .cost = int(costToUpgrade),
}); });
}); });
} else { } else {
@ -1813,7 +1871,18 @@ void SendGiftBox(
button->setClickedCallback([=] { button->setClickedCallback([=] {
const auto star = std::get_if<GiftTypeStars>(&descriptor); const auto star = std::get_if<GiftTypeStars>(&descriptor);
if (star && IsSoldOut(star->info)) { if (star && star->info.unique) {
const auto done = [=] {
window->session().credits().load(true);
window->showPeerHistory(peer);
};
ShowTransferToBox(
window,
peer,
star->info.unique,
star->transferId,
done);
} else if (star && IsSoldOut(star->info)) {
window->show(Box(SoldOutBox, window, *star)); window->show(Box(SoldOutBox, window, *star));
} else { } else {
window->show( window->show(
@ -1897,7 +1966,8 @@ void AddBlock(
[[nodiscard]] object_ptr<RpWidget> MakeStarsGifts( [[nodiscard]] object_ptr<RpWidget> MakeStarsGifts(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<PeerData*> peer) { not_null<PeerData*> peer,
MyGiftsDescriptor my) {
auto result = object_ptr<VerticalLayout>((QWidget*)nullptr); auto result = object_ptr<VerticalLayout>((QWidget*)nullptr);
struct State { struct State {
@ -1908,21 +1978,36 @@ void AddBlock(
state->gifts = GiftsStars(&window->session(), peer); state->gifts = GiftsStars(&window->session(), peer);
auto tabs = MakeGiftsPriceTabs(window, peer, state->gifts.value()); auto tabs = MakeGiftsPriceTabs(
window,
peer,
state->gifts.value(),
!my.list.empty());
state->priceTab = std::move(tabs.priceTab); state->priceTab = std::move(tabs.priceTab);
result->add(std::move(tabs.widget)); result->add(std::move(tabs.widget));
result->add(MakeGiftsList(window, peer, rpl::combine( result->add(MakeGiftsList(window, peer, rpl::combine(
state->gifts.value(), state->gifts.value(),
state->priceTab.value() state->priceTab.value()
) | rpl::map([=](std::vector<GiftTypeStars> &&gifts, int price) { ) | rpl::map([=](std::vector<GiftTypeStars> &&gifts, int price) {
gifts.erase(ranges::remove_if(gifts, [&](const GiftTypeStars &gift) { if (price == kPriceTabMy) {
return (price == kPriceTabLimited) gifts.clear();
? (!gift.info.limitedCount) for (const auto &gift : my.list) {
: (price == kPriceTabInStock) gifts.push_back({
? IsSoldOut(gift.info) .transferId = gift.manageId,
: (price && gift.info.stars != price); .info = gift.info,
}), end(gifts)); .mine = true,
});
}
} else {
const auto pred = [&](const GiftTypeStars &gift) {
return (price == kPriceTabLimited)
? (!gift.info.limitedCount)
: (price == kPriceTabInStock)
? IsSoldOut(gift.info)
: (price && gift.info.stars != price);
};
gifts.erase(ranges::remove_if(gifts, pred), end(gifts));
}
return GiftsDescriptor{ return GiftsDescriptor{
gifts | ranges::to<std::vector<GiftDescriptor>>(), gifts | ranges::to<std::vector<GiftDescriptor>>(),
}; };
@ -1934,7 +2019,8 @@ void AddBlock(
void GiftBox( void GiftBox(
not_null<GenericBox*> box, not_null<GenericBox*> box,
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<PeerData*> peer) { not_null<PeerData*> peer,
MyGiftsDescriptor my) {
box->setWidth(st::boxWideWidth); box->setWidth(st::boxWideWidth);
box->setStyle(st::creditsGiftBox); box->setStyle(st::creditsGiftBox);
box->setNoContentMargin(true); box->setNoContentMargin(true);
@ -2030,7 +2116,7 @@ void GiftBox(
tr::lng_gift_stars_link() | Text::ToLink(), tr::lng_gift_stars_link() | Text::ToLink(),
Text::WithEntities)), Text::WithEntities)),
.aboutFilter = starsClickHandlerFilter, .aboutFilter = starsClickHandlerFilter,
.content = MakeStarsGifts(window, peer), .content = MakeStarsGifts(window, peer, std::move(my)),
}); });
} }
} }
@ -2224,9 +2310,25 @@ void ShowStarGiftBox(
not_null<PeerData*> peer) { not_null<PeerData*> peer) {
struct Session { struct Session {
PeerData *peer = nullptr; PeerData *peer = nullptr;
MyGiftsDescriptor my;
bool premiumGiftsReady = false; bool premiumGiftsReady = false;
bool starsGiftsReady = false; bool starsGiftsReady = false;
bool fullReady = false;
bool myReady = false;
bool hasPremium = false;
bool hasUpgradable = false;
bool hasLimited = false;
bool hasUnlimited = false;
rpl::lifetime lifetime; rpl::lifetime lifetime;
[[nodiscard]] bool ready() const {
return premiumGiftsReady
&& starsGiftsReady
&& fullReady
&& myReady;
}
}; };
static auto Map = base::flat_map<not_null<Main::Session*>, Session>(); static auto Map = base::flat_map<not_null<Main::Session*>, Session>();
@ -2241,8 +2343,13 @@ void ShowStarGiftBox(
i->second = Session{ .peer = peer }; i->second = Session{ .peer = peer };
const auto weak = base::make_weak(controller); const auto weak = base::make_weak(controller);
const auto show = [=] { const auto checkReady = [=] {
Map[session] = Session(); auto &entry = Map[session];
if (!entry.ready()) {
return;
}
auto was = std::move(entry);
entry = Session();
if (const auto strong = weak.get()) { if (const auto strong = weak.get()) {
if (const auto user = peer->asUser()) { if (const auto user = peer->asUser()) {
using Type = Api::DisallowedGiftType; using Type = Api::DisallowedGiftType;
@ -2252,48 +2359,75 @@ void ShowStarGiftBox(
const auto limited = (disallowedTypes & Type::Limited); const auto limited = (disallowedTypes & Type::Limited);
const auto unlimited = (disallowedTypes & Type::Unlimited); const auto unlimited = (disallowedTypes & Type::Unlimited);
const auto unique = (disallowedTypes & Type::Unique); const auto unique = (disallowedTypes & Type::Unique);
if (premium && limited && unlimited && unique) { if ((unique || (!was.hasUpgradable && was.my.list.empty()))
strong->showToast(tr::lng_edit_privacy_gifts_restricted(tr::now)); && (premium || !was.hasPremium)
&& (limited || !was.hasLimited)
&& (unlimited || !was.hasUnlimited)) {
strong->showToast(
tr::lng_edit_privacy_gifts_restricted(tr::now));
return; return;
} }
} }
strong->show(Box(GiftBox, strong, peer)); strong->show(Box(GiftBox, strong, peer, std::move(was.my)));
} }
}; };
base::timer_once(
kGiftsPreloadTimeout
) | rpl::start_with_next(show, i->second.lifetime);
const auto user = peer->asUser(); const auto user = peer->asUser();
if (user && !user->isSelf()) { if (user && !user->isSelf()) {
GiftsPremium( GiftsPremium(
session, session,
peer peer
) | rpl::start_with_next([=](PremiumGiftsDescriptor &&gifts) { ) | rpl::start_with_next([=](PremiumGiftsDescriptor &&gifts) {
if (!gifts.list.empty()) { auto &entry = Map[session];
auto &entry = Map[session]; entry.premiumGiftsReady = true;
entry.premiumGiftsReady = true; entry.hasPremium = !gifts.list.empty();
if (entry.starsGiftsReady) { checkReady();
show();
}
}
}, i->second.lifetime); }, i->second.lifetime);
} else { } else {
i->second.hasPremium = false;
i->second.premiumGiftsReady = true; i->second.premiumGiftsReady = true;
} }
if (peer->isFullLoaded()) {
i->second.fullReady = true;
} else {
peer->updateFull();
peer->session().changes().peerUpdates(
peer,
Data::PeerUpdate::Flag::FullInfo
) | rpl::take(1) | rpl::start_with_next([=] {
auto &entry = Map[session];
entry.fullReady = true;
checkReady();
}, i->second.lifetime);
}
GiftsStars( GiftsStars(
session, session,
peer peer
) | rpl::start_with_next([=](std::vector<GiftTypeStars> &&gifts) { ) | rpl::start_with_next([=](std::vector<GiftTypeStars> &&gifts) {
if (!gifts.empty()) { auto &entry = Map[session];
auto &entry = Map[session]; entry.starsGiftsReady = true;
entry.starsGiftsReady = true; for (const auto &gift : gifts) {
if (entry.premiumGiftsReady) { if (gift.info.limitedCount) {
show(); entry.hasLimited = true;
if (gift.info.starsToUpgrade) {
entry.hasUpgradable = true;
}
} else {
entry.hasUnlimited = true;
} }
} }
checkReady();
}, i->second.lifetime);
UniqueGiftsSlice(
session
) | rpl::start_with_next([=](MyGiftsDescriptor &&gifts) {
auto &entry = Map[session];
entry.my = std::move(gifts);
entry.myReady = true;
checkReady();
}, i->second.lifetime); }, i->second.lifetime);
} }

View file

@ -463,6 +463,8 @@ void TransferGift(
std::move(formDone)); std::move(formDone));
} }
} // namespace
void ShowTransferToBox( void ShowTransferToBox(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<PeerData*> peer, not_null<PeerData*> peer,
@ -539,8 +541,6 @@ void ShowTransferToBox(
})); }));
} }
} // namespace
void ShowTransferGiftBox( void ShowTransferGiftBox(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift, std::shared_ptr<Data::UniqueGift> gift,

View file

@ -16,6 +16,13 @@ struct UniqueGift;
class SavedStarGiftId; class SavedStarGiftId;
} // namespace Data } // namespace Data
void ShowTransferToBox(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
std::shared_ptr<Data::UniqueGift> gift,
Data::SavedStarGiftId savedId,
Fn<void()> closeParentBox);
void ShowTransferGiftBox( void ShowTransferGiftBox(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift, std::shared_ptr<Data::UniqueGift> gift,

View file

@ -142,7 +142,9 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
_price.setMarkedText( _price.setMarkedText(
st::semiboldTextStyle, st::semiboldTextStyle,
(unique (unique
? tr::lng_gift_price_unique(tr::now, Ui::Text::WithEntities) ? tr::lng_gift_transfer_button(
tr::now,
Ui::Text::WithEntities)
: _delegate->star().append( : _delegate->star().append(
' ' + Lang::FormatCountDecimal(data.info.stars))), ' ' + Lang::FormatCountDecimal(data.info.stars))),
kMarkupTextOptions, kMarkupTextOptions,

View file

@ -60,6 +60,7 @@ struct GiftTypePremium {
}; };
struct GiftTypeStars { struct GiftTypeStars {
Data::SavedStarGiftId transferId;
Data::StarGift info; Data::StarGift info;
PeerData *from = nullptr; PeerData *from = nullptr;
TimeId date = 0; TimeId date = 0;