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();
}
MTPInputSavedStarGift InputSavedStarGiftId(const Data::SavedStarGiftId &id) {
return id.isUser()
MTPInputSavedStarGift InputSavedStarGiftId(
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_inputSavedStarGiftChat(
id.chat()->input,

View file

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

View file

@ -3069,10 +3069,12 @@ void GiftResaleBox(
state->updated.events()
) | rpl::map([=] {
auto result = GiftsDescriptor();
const auto selfId = window->session().userPeerId();
for (const auto &gift : state->data.list) {
result.list.push_back(GiftTypeStars{
.info = gift,
.resale = true,
.mine = (gift.unique->ownerId == selfId),
});
}
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(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
const Data::UniqueGift &gift,
std::shared_ptr<Data::UniqueGift> unique,
Data::SavedStarGiftId savedId,
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) {
box->setTitle(tr::lng_gift_sell_title());
box->setStyle(st.box ? *st.box : st::upgradeGiftBox);
@ -4078,6 +4105,9 @@ void ShowUniqueGiftSellBox(
box->addTopButton(st.close ? *st.close : st::boxTitleClose, [=] {
box->closeBox();
});
const auto priceNow = unique->starsForResale;
const auto name = Data::UniqueGiftName(*unique);
const auto slug = unique->slug;
const auto session = &show->session();
AddSubsectionTitle(
@ -4171,24 +4201,7 @@ void ShowUniqueGiftSellBox(
return;
}
box->closeBox();
session->api().request(MTPpayments_UpdateStarGiftPrice(
(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();
UpdateGiftSellPrice(show, unique, savedId, count);
});
rpl::combine(
box->widthValue(),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -181,7 +181,14 @@ void InnerWidget::subscribeToUpdates() {
const auto savedId = [](const Entry &entry) {
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)) {
return;
}
@ -224,6 +231,13 @@ void InnerWidget::subscribeToUpdates() {
} else {
markUnpinned(i);
}
} else if (update.action == Action::ResaleChange) {
for (auto &view : _views) {
if (view.index == index) {
view.index = -1;
view.manageId = {};
}
}
} else {
return;
}

View file

@ -1052,21 +1052,7 @@ void FillUniqueGiftMenu(
const auto name = UniqueGiftName(*unique);
const auto confirm = [=](Fn<void()> close) {
close();
session->api().request(MTPpayments_UpdateStarGiftPrice(
(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();
Ui::UpdateGiftSellPrice(show, unique, savedId, 0);
};
show->show(Ui::MakeConfirmBox({
.text = tr::lng_gift_sell_unlist_sure(),
@ -1082,7 +1068,7 @@ void FillUniqueGiftMenu(
const auto style = st.giftWearBox
? *st.giftWearBox
: GiftWearBoxStyleOverride();
ShowUniqueGiftSellBox(show, owner, *unique, savedId, style);
ShowUniqueGiftSellBox(show, owner, unique, savedId, style);
}, st.resell ? st.resell : &st::menuIconTagSell);
}
}
@ -1177,6 +1163,8 @@ void GenericCreditsEntryBox(
&& !e.converted
&& starGiftSender;
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)) {
session->data().giftUpdates(
@ -1219,6 +1207,8 @@ void GenericCreditsEntryBox(
if (uniqueGift) {
box->setNoContentMargin(true);
//const auto canEditResale = (e.bareGiftOwnerId == selfPeerId);
//auto starsResale = rpl
AddUniqueGiftCover(content, rpl::single(*uniqueGift));
AddSkip(content, st::defaultVerticalListSkip * 2);
@ -1970,7 +1960,7 @@ void GenericCreditsEntryBox(
if (willBusy) {
state->confirmButtonBusy = true;
send();
} else if (uniqueGift && uniqueGift->starsForResale && !giftToSelf) {
} else if (canBuyResold) {
const auto to = e.bareGiftResaleRecipientId
? show->session().data().peer(
PeerId(e.bareGiftResaleRecipientId))
@ -1988,7 +1978,7 @@ void GenericCreditsEntryBox(
box->closeBox();
}
});
if (uniqueGift && uniqueGift->starsForResale && !giftToSelf) {
if (canBuyResold) {
button->setText(tr::lng_gift_buy_resale_button(
lt_cost,
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 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(
(confirmTextPlain
? v::text::take_plain(