Allow buying resold gifts.

This commit is contained in:
John Preston 2025-04-22 17:18:12 +04:00
parent 0a92e12a62
commit 18f14b828c
11 changed files with 122 additions and 63 deletions

View file

@ -528,8 +528,12 @@ void EditCreditsSubscription(
)).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send(); )).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send();
} }
MTPInputSavedStarGift InputSavedStarGiftId(const Data::SavedStarGiftId &id) { MTPInputSavedStarGift InputSavedStarGiftId(
return id.isUser() const Data::SavedStarGiftId &id,
const std::shared_ptr<Data::UniqueGift> &unique) {
return (!id && unique)
? MTP_inputSavedStarGiftSlug(MTP_string(unique->slug))
: id.isUser()
? MTP_inputSavedStarGiftUser(MTP_int(id.userMessageId().bare)) ? MTP_inputSavedStarGiftUser(MTP_int(id.userMessageId().bare))
: MTP_inputSavedStarGiftChat( : MTP_inputSavedStarGiftChat(
id.chat()->input, id.chat()->input,

View file

@ -122,6 +122,7 @@ void EditCreditsSubscription(
Fn<void(QString)> fail); Fn<void(QString)> fail);
[[nodiscard]] MTPInputSavedStarGift InputSavedStarGiftId( [[nodiscard]] MTPInputSavedStarGift InputSavedStarGiftId(
const Data::SavedStarGiftId &id); const Data::SavedStarGiftId &id,
const std::shared_ptr<Data::UniqueGift> &unique = nullptr);
} // namespace Api } // namespace Api

View file

@ -3069,10 +3069,12 @@ void GiftResaleBox(
state->updated.events() state->updated.events()
) | rpl::map([=] { ) | rpl::map([=] {
auto result = GiftsDescriptor(); auto result = GiftsDescriptor();
const auto selfId = window->session().userPeerId();
for (const auto &gift : state->data.list) { for (const auto &gift : state->data.list) {
result.list.push_back(GiftTypeStars{ result.list.push_back(GiftTypeStars{
.info = gift, .info = gift,
.resale = true, .resale = true,
.mine = (gift.unique->ownerId == selfId),
}); });
} }
return result; return result;
@ -4061,15 +4063,40 @@ void ShowUniqueGiftWearBox(
})); }));
} }
void UpdateGiftSellPrice(
std::shared_ptr<ChatHelpers::Show> show,
std::shared_ptr<Data::UniqueGift> unique,
Data::SavedStarGiftId savedId,
int price) {
const auto session = &show->session();
session->api().request(MTPpayments_UpdateStarGiftPrice(
Api::InputSavedStarGiftId(savedId, unique),
MTP_long(price)
)).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result);
show->showToast(tr::lng_gift_sell_toast(
tr::now,
lt_name,
Data::UniqueGiftName(*unique)));
unique->starsForResale = price;
session->data().notifyGiftUpdate({
.id = savedId,
.slug = unique->slug,
.action = Data::GiftUpdate::Action::ResaleChange,
});
}).fail([=](const MTP::Error &error) {
show->showToast(error.type());
}).send();
}
void ShowUniqueGiftSellBox( void ShowUniqueGiftSellBox(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer, not_null<PeerData*> peer,
const Data::UniqueGift &gift, std::shared_ptr<Data::UniqueGift> unique,
Data::SavedStarGiftId savedId, Data::SavedStarGiftId savedId,
Settings::GiftWearBoxStyleOverride st) { Settings::GiftWearBoxStyleOverride st) {
const auto priceNow = gift.starsForResale;
const auto name = Data::UniqueGiftName(gift);
const auto slug = gift.slug;
show->show(Box([=](not_null<Ui::GenericBox*> box) { show->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setTitle(tr::lng_gift_sell_title()); box->setTitle(tr::lng_gift_sell_title());
box->setStyle(st.box ? *st.box : st::upgradeGiftBox); box->setStyle(st.box ? *st.box : st::upgradeGiftBox);
@ -4078,6 +4105,9 @@ void ShowUniqueGiftSellBox(
box->addTopButton(st.close ? *st.close : st::boxTitleClose, [=] { box->addTopButton(st.close ? *st.close : st::boxTitleClose, [=] {
box->closeBox(); box->closeBox();
}); });
const auto priceNow = unique->starsForResale;
const auto name = Data::UniqueGiftName(*unique);
const auto slug = unique->slug;
const auto session = &show->session(); const auto session = &show->session();
AddSubsectionTitle( AddSubsectionTitle(
@ -4171,24 +4201,7 @@ void ShowUniqueGiftSellBox(
return; return;
} }
box->closeBox(); box->closeBox();
session->api().request(MTPpayments_UpdateStarGiftPrice( UpdateGiftSellPrice(show, unique, savedId, count);
(savedId
? Api::InputSavedStarGiftId(savedId)
: MTP_inputSavedStarGiftSlug(MTP_string(slug))),
MTP_long(count)
)).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result);
show->showToast(tr::lng_gift_sell_toast(
tr::now,
lt_name,
name));
session->data().notifyGiftUpdate({
});
}).fail([=](const MTP::Error &error) {
show->showToast(error.type());
}).send();
}); });
rpl::combine( rpl::combine(
box->widthValue(), box->widthValue(),

View file

@ -69,10 +69,15 @@ void ShowUniqueGiftWearBox(
const Data::UniqueGift &gift, const Data::UniqueGift &gift,
Settings::GiftWearBoxStyleOverride st); Settings::GiftWearBoxStyleOverride st);
void UpdateGiftSellPrice(
std::shared_ptr<ChatHelpers::Show> show,
std::shared_ptr<Data::UniqueGift> unique,
Data::SavedStarGiftId savedId,
int price);
void ShowUniqueGiftSellBox( void ShowUniqueGiftSellBox(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer, not_null<PeerData*> peer,
const Data::UniqueGift &gift, std::shared_ptr<Data::UniqueGift> unique,
Data::SavedStarGiftId savedId, Data::SavedStarGiftId savedId,
Settings::GiftWearBoxStyleOverride st); Settings::GiftWearBoxStyleOverride st);

View file

@ -443,7 +443,7 @@ void TransferGift(
}; };
if (gift->starsForTransfer <= 0) { if (gift->starsForTransfer <= 0) {
session->api().request(MTPpayments_TransferStarGift( session->api().request(MTPpayments_TransferStarGift(
Api::InputSavedStarGiftId(savedId), Api::InputSavedStarGiftId(savedId, gift),
to->input to->input
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result); session->api().applyUpdates(result);
@ -459,7 +459,7 @@ void TransferGift(
Ui::RequestStarsFormAndSubmit( Ui::RequestStarsFormAndSubmit(
window->uiShow(), window->uiShow(),
MTP_inputInvoiceStarGiftTransfer( MTP_inputInvoiceStarGiftTransfer(
Api::InputSavedStarGiftId(savedId), Api::InputSavedStarGiftId(savedId, gift),
to->input), to->input),
std::move(formDone)); std::move(formDone));
} }

View file

@ -89,9 +89,11 @@ struct GiftUpdate {
Delete, Delete,
Pin, Pin,
Unpin, Unpin,
ResaleChange,
}; };
Data::SavedStarGiftId id; Data::SavedStarGiftId id;
QString slug;
Action action = {}; Action action = {};
}; };

View file

@ -55,10 +55,14 @@ std::strong_ordering operator<=>(const GiftBadge &a, const GiftBadge &b) {
if (result3 != std::strong_ordering::equal) { if (result3 != std::strong_ordering::equal) {
return result3; return result3;
} }
const auto result4 = (a.fg.rgb() <=> b.fg.rgb()); const auto result4 = (a.border.rgb() <=> b.border.rgb());
if (result4 != std::strong_ordering::equal) { if (result4 != std::strong_ordering::equal) {
return result4; return result4;
} }
const auto result5 = (a.fg.rgb() <=> b.fg.rgb());
if (result5 != std::strong_ordering::equal) {
return result5;
}
return a.gradient <=> b.gradient; return a.gradient <=> b.gradient;
} }
@ -80,14 +84,22 @@ void GiftButton::unsubscribe() {
} }
void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) { void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
if (_descriptor == descriptor) { const auto unique = v::is<GiftTypeStars>(descriptor)
? v::get<GiftTypeStars>(descriptor).info.unique.get()
: nullptr;
const auto resalePrice = unique ? unique->starsForResale : 0;
if (_descriptor == descriptor && _resalePrice == resalePrice) {
return; return;
} }
auto player = base::take(_player); auto player = base::take(_player);
const auto starsType = Ui::Premium::MiniStars::Type::SlowStars; const auto starsType = Ui::Premium::MiniStars::Type::SlowStars;
_mediaLifetime.destroy(); _mediaLifetime.destroy();
_descriptor = descriptor;
unsubscribe(); unsubscribe();
_descriptor = descriptor;
_resalePrice = resalePrice;
const auto resale = (_resalePrice > 0);
_small = (mode != Mode::Full);
v::match(descriptor, [&](const GiftTypePremium &data) { v::match(descriptor, [&](const GiftTypePremium &data) {
const auto months = data.months; const auto months = data.months;
_text = Ui::Text::String(st::giftBoxGiftHeight / 4); _text = Ui::Text::String(st::giftBoxGiftHeight / 4);
@ -125,7 +137,6 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
{ 1., st::windowActiveTextFg->c }, { 1., st::windowActiveTextFg->c },
}); });
}, [&](const GiftTypeStars &data) { }, [&](const GiftTypeStars &data) {
const auto unique = data.info.unique.get();
const auto soldOut = data.info.limitedCount const auto soldOut = data.info.limitedCount
&& !data.userpic && !data.userpic
&& !data.info.limitedLeft; && !data.info.limitedLeft;
@ -134,7 +145,7 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
: data.from : data.from
? Ui::MakeUserpicThumbnail(data.from) ? Ui::MakeUserpicThumbnail(data.from)
: Ui::MakeHiddenAuthorThumbnail(); : Ui::MakeHiddenAuthorThumbnail();
if (mode == Mode::Minimal) { if (_small && !resale) {
_price = {}; _price = {};
_stars.reset(); _stars.reset();
return; return;
@ -149,6 +160,9 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
? unique->starsForResale ? unique->starsForResale
: data.info.starsResellMin) : data.info.starsResellMin)
).append(data.info.resellCount > 1 ? "+" : "") ).append(data.info.resellCount > 1 ? "+" : "")
: (_small && unique && unique->starsForResale)
? _delegate->monostar().append(' ').append(
Lang::FormatCountDecimal(unique->starsForResale))
: unique : unique
? tr::lng_gift_transfer_button( ? tr::lng_gift_transfer_button(
tr::now, tr::now,
@ -186,9 +200,8 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
_uniquePatternEmoji = nullptr; _uniquePatternEmoji = nullptr;
_uniquePatternCache.clear(); _uniquePatternCache.clear();
if (mode != Mode::Full) { if (_small && !resale) {
_button = QRect(); _button = QRect();
_small = true;
return; return;
} }
const auto buttonw = _price.maxWidth(); const auto buttonw = _price.maxWidth();
@ -403,6 +416,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
const auto unique = v::is<GiftTypeStars>(_descriptor) const auto unique = v::is<GiftTypeStars>(_descriptor)
? v::get<GiftTypeStars>(_descriptor).info.unique.get() ? v::get<GiftTypeStars>(_descriptor).info.unique.get()
: nullptr; : nullptr;
const auto onsale = (unique && unique->starsForResale && _small);
const auto hidden = v::is<GiftTypeStars>(_descriptor) const auto hidden = v::is<GiftTypeStars>(_descriptor)
&& v::get<GiftTypeStars>(_descriptor).hidden;; && v::get<GiftTypeStars>(_descriptor).hidden;;
const auto extend = currentExtend(); const auto extend = currentExtend();
@ -502,7 +516,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
&& !data.userpic && !data.userpic
&& !data.info.limitedLeft; && !data.info.limitedLeft;
return GiftBadge{ return GiftBadge{
.text = ((unique && data.resale && _small) .text = (onsale
? tr::lng_gift_stars_on_sale(tr::now) ? tr::lng_gift_stars_on_sale(tr::now)
: (unique && (data.resale || pinned)) : (unique && (data.resale || pinned))
? ('#' + QString::number(unique->number)) ? ('#' + QString::number(unique->number))
@ -520,7 +534,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
(((count % 1000) && (count < 10'000)) (((count % 1000) && (count < 10'000))
? Lang::FormatCountDecimal(count) ? Lang::FormatCountDecimal(count)
: Lang::FormatCountToShort(count).string))), : Lang::FormatCountToShort(count).string))),
.bg1 = ((unique && data.resale && _small) .bg1 = (onsale
? st::boxTextFgGood->c ? st::boxTextFgGood->c
: unique : unique
? unique->backdrop.edgeColor ? unique->backdrop.edgeColor
@ -529,12 +543,15 @@ void GiftButton::paintEvent(QPaintEvent *e) {
: soldOut : soldOut
? st::attentionButtonFg->c ? st::attentionButtonFg->c
: st::windowActiveTextFg->c), : st::windowActiveTextFg->c),
.bg2 = ((unique && data.resale && _small) .bg2 = (onsale
? QColor(0, 0, 0, 0) ? QColor(0, 0, 0, 0)
: unique : unique
? unique->backdrop.patternColor ? unique->backdrop.patternColor
: QColor(0, 0, 0, 0)), : QColor(0, 0, 0, 0)),
.fg = ((unique && data.resale && _small) .border = (onsale
? QColor(255, 255, 255)
: QColor(0, 0, 0, 0)),
.fg = (onsale
? st::windowBg->c ? st::windowBg->c
: unique : unique
? QColor(255, 255, 255) ? QColor(255, 255, 255)
@ -577,7 +594,9 @@ void GiftButton::paintEvent(QPaintEvent *e) {
}); });
if (!_button.isEmpty()) { if (!_button.isEmpty()) {
p.setBrush(unique p.setBrush(onsale
? QBrush(unique->backdrop.patternColor)
: unique
? QBrush(QColor(255, 255, 255, .2 * 255)) ? QBrush(QColor(255, 255, 255, .2 * 255))
: premium : premium
? st::lightButtonBgOver ? st::lightButtonBgOver
@ -585,11 +604,13 @@ void GiftButton::paintEvent(QPaintEvent *e) {
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
if (!unique && !premium) { if (!unique && !premium) {
p.setOpacity(0.12); p.setOpacity(0.12);
} else if (onsale) {
p.setOpacity(0.8);
} }
const auto geometry = _button; const auto geometry = _button;
const auto radius = geometry.height() / 2.; const auto radius = geometry.height() / 2.;
p.drawRoundedRect(geometry, radius, radius); p.drawRoundedRect(geometry, radius, radius);
if (!premium) { if (!premium || onsale) {
p.setOpacity(1.); p.setOpacity(1.);
} }
if (_stars) { if (_stars) {
@ -816,6 +837,7 @@ QImage ValidateRotatedBadge(const GiftBadge &badge, int added) {
const auto ratio = style::DevicePixelRatio(); const auto ratio = style::DevicePixelRatio();
const auto multiplier = ratio * 3; const auto multiplier = ratio * 3;
const auto size = (twidth + font->height * 2); const auto size = (twidth + font->height * 2);
const auto height = font->height + st::lineWidth;
const auto textpos = QPoint(size - skip, added); const auto textpos = QPoint(size - skip, added);
auto image = QImage( auto image = QImage(
QSize(size, size) * multiplier, QSize(size, size) * multiplier,
@ -846,12 +868,16 @@ QImage ValidateRotatedBadge(const GiftBadge &badge, int added) {
{ {
auto p = QPainter(&result); auto p = QPainter(&result);
auto hq = PainterHighQualityEnabler(p); auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.save(); p.save();
p.translate(textpos); p.translate(textpos);
p.rotate(45.); p.rotate(45.);
const auto rect = QRect(-5 * twidth, 0, twidth * 12, font->height); const auto rect = QRect(-5 * twidth, 0, twidth * 12, height);
if (badge.border.alpha() > 0) {
p.setPen(badge.border);
} else {
p.setPen(Qt::NoPen);
}
if (badge.gradient) { if (badge.gradient) {
const auto skip = font->height / M_SQRT2; const auto skip = font->height / M_SQRT2;
auto gradient = QLinearGradient( auto gradient = QLinearGradient(

View file

@ -88,6 +88,7 @@ struct GiftBadge {
QString text; QString text;
QColor bg1; QColor bg1;
QColor bg2 = QColor(0, 0, 0, 0); QColor bg2 = QColor(0, 0, 0, 0);
QColor border = QColor(0, 0, 0, 0);
QColor fg; QColor fg;
bool gradient = false; bool gradient = false;
bool small = false; bool small = false;
@ -174,6 +175,7 @@ private:
base::flat_map<float64, QImage> _uniquePatternCache; base::flat_map<float64, QImage> _uniquePatternCache;
std::optional<Ui::Premium::ColoredMiniStars> _stars; std::optional<Ui::Premium::ColoredMiniStars> _stars;
Ui::Animations::Simple _selectedAnimation; Ui::Animations::Simple _selectedAnimation;
int _resalePrice = 0;
bool _subscribed = false; bool _subscribed = false;
bool _patterned = false; bool _patterned = false;
bool _selected = false; bool _selected = false;

View file

@ -181,7 +181,14 @@ void InnerWidget::subscribeToUpdates() {
const auto savedId = [](const Entry &entry) { const auto savedId = [](const Entry &entry) {
return entry.gift.manageId; return entry.gift.manageId;
}; };
const auto i = ranges::find(_entries, update.id, savedId); const auto bySlug = [](const Entry &entry) {
return entry.gift.info.unique
? entry.gift.info.unique->slug
: QString();
};
const auto i = update.id
? ranges::find(_entries, update.id, savedId)
: ranges::find(_entries, update.slug, bySlug);
if (i == end(_entries)) { if (i == end(_entries)) {
return; return;
} }
@ -224,6 +231,13 @@ void InnerWidget::subscribeToUpdates() {
} else { } else {
markUnpinned(i); markUnpinned(i);
} }
} else if (update.action == Action::ResaleChange) {
for (auto &view : _views) {
if (view.index == index) {
view.index = -1;
view.manageId = {};
}
}
} else { } else {
return; return;
} }

View file

@ -1052,21 +1052,7 @@ void FillUniqueGiftMenu(
const auto name = UniqueGiftName(*unique); const auto name = UniqueGiftName(*unique);
const auto confirm = [=](Fn<void()> close) { const auto confirm = [=](Fn<void()> close) {
close(); close();
session->api().request(MTPpayments_UpdateStarGiftPrice( Ui::UpdateGiftSellPrice(show, unique, savedId, 0);
(savedId
? Api::InputSavedStarGiftId(savedId)
: MTP_inputSavedStarGiftSlug(
MTP_string(unique->slug))),
MTP_long(0)
)).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result);
show->showToast(tr::lng_gift_sell_removed(
tr::now,
lt_name,
name));
}).fail([=](const MTP::Error &error) {
show->showToast(error.type());
}).send();
}; };
show->show(Ui::MakeConfirmBox({ show->show(Ui::MakeConfirmBox({
.text = tr::lng_gift_sell_unlist_sure(), .text = tr::lng_gift_sell_unlist_sure(),
@ -1082,7 +1068,7 @@ void FillUniqueGiftMenu(
const auto style = st.giftWearBox const auto style = st.giftWearBox
? *st.giftWearBox ? *st.giftWearBox
: GiftWearBoxStyleOverride(); : GiftWearBoxStyleOverride();
ShowUniqueGiftSellBox(show, owner, *unique, savedId, style); ShowUniqueGiftSellBox(show, owner, unique, savedId, style);
}, st.resell ? st.resell : &st::menuIconTagSell); }, st.resell ? st.resell : &st::menuIconTagSell);
} }
} }
@ -1177,6 +1163,8 @@ void GenericCreditsEntryBox(
&& !e.converted && !e.converted
&& starGiftSender; && starGiftSender;
const auto canConvert = forConvert && !timeExceeded; const auto canConvert = forConvert && !timeExceeded;
const auto inResale = uniqueGift && (uniqueGift->starsForResale > 0);
const auto canBuyResold = inResale && (e.bareGiftOwnerId != selfPeerId);
if (auto savedId = EntryToSavedStarGiftId(session, e)) { if (auto savedId = EntryToSavedStarGiftId(session, e)) {
session->data().giftUpdates( session->data().giftUpdates(
@ -1219,6 +1207,8 @@ void GenericCreditsEntryBox(
if (uniqueGift) { if (uniqueGift) {
box->setNoContentMargin(true); box->setNoContentMargin(true);
//const auto canEditResale = (e.bareGiftOwnerId == selfPeerId);
//auto starsResale = rpl
AddUniqueGiftCover(content, rpl::single(*uniqueGift)); AddUniqueGiftCover(content, rpl::single(*uniqueGift));
AddSkip(content, st::defaultVerticalListSkip * 2); AddSkip(content, st::defaultVerticalListSkip * 2);
@ -1970,7 +1960,7 @@ void GenericCreditsEntryBox(
if (willBusy) { if (willBusy) {
state->confirmButtonBusy = true; state->confirmButtonBusy = true;
send(); send();
} else if (uniqueGift && uniqueGift->starsForResale && !giftToSelf) { } else if (canBuyResold) {
const auto to = e.bareGiftResaleRecipientId const auto to = e.bareGiftResaleRecipientId
? show->session().data().peer( ? show->session().data().peer(
PeerId(e.bareGiftResaleRecipientId)) PeerId(e.bareGiftResaleRecipientId))
@ -1988,7 +1978,7 @@ void GenericCreditsEntryBox(
box->closeBox(); box->closeBox();
} }
}); });
if (uniqueGift && uniqueGift->starsForResale && !giftToSelf) { if (canBuyResold) {
button->setText(tr::lng_gift_buy_resale_button( button->setText(tr::lng_gift_buy_resale_button(
lt_cost, lt_cost,
rpl::single( rpl::single(

View file

@ -58,7 +58,9 @@ void ConfirmBox(not_null<Ui::GenericBox*> box, ConfirmBoxArgs &&args) {
}; };
const auto &defaultButtonStyle = box->getDelegate()->style().button; const auto &defaultButtonStyle = box->getDelegate()->style().button;
const auto confirmTextPlain = v::text::is_plain(args.confirmText); const auto confirmTextPlain = v::is_null(args.confirmText)
|| v::is<rpl::producer<QString>>(args.confirmText)
|| v::is<QString>(args.confirmText);
const auto confirmButton = box->addButton( const auto confirmButton = box->addButton(
(confirmTextPlain (confirmTextPlain
? v::text::take_plain( ? v::text::take_plain(