From 18f14b828c44171bc40e0ef297b09a48dadf3928 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 22 Apr 2025 17:18:12 +0400 Subject: [PATCH] Allow buying resold gifts. --- Telegram/SourceFiles/api/api_credits.cpp | 8 ++- Telegram/SourceFiles/api/api_credits.h | 3 +- Telegram/SourceFiles/boxes/star_gift_box.cpp | 57 ++++++++++++------- Telegram/SourceFiles/boxes/star_gift_box.h | 7 ++- .../SourceFiles/boxes/transfer_gift_box.cpp | 4 +- Telegram/SourceFiles/data/data_session.h | 2 + .../peer_gifts/info_peer_gifts_common.cpp | 56 +++++++++++++----- .../info/peer_gifts/info_peer_gifts_common.h | 2 + .../peer_gifts/info_peer_gifts_widget.cpp | 16 +++++- .../settings/settings_credits_graphics.cpp | 26 +++------ Telegram/SourceFiles/ui/boxes/confirm_box.cpp | 4 +- 11 files changed, 122 insertions(+), 63 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 2c06385f6d..103554a5b4 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -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 &unique) { + return (!id && unique) + ? MTP_inputSavedStarGiftSlug(MTP_string(unique->slug)) + : id.isUser() ? MTP_inputSavedStarGiftUser(MTP_int(id.userMessageId().bare)) : MTP_inputSavedStarGiftChat( id.chat()->input, diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h index c9c87b2e8e..559e84711e 100644 --- a/Telegram/SourceFiles/api/api_credits.h +++ b/Telegram/SourceFiles/api/api_credits.h @@ -122,6 +122,7 @@ void EditCreditsSubscription( Fn fail); [[nodiscard]] MTPInputSavedStarGift InputSavedStarGiftId( - const Data::SavedStarGiftId &id); + const Data::SavedStarGiftId &id, + const std::shared_ptr &unique = nullptr); } // namespace Api diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index 7279a8fead..348deb1258 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -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 show, + std::shared_ptr 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 show, not_null peer, - const Data::UniqueGift &gift, + std::shared_ptr 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 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(), diff --git a/Telegram/SourceFiles/boxes/star_gift_box.h b/Telegram/SourceFiles/boxes/star_gift_box.h index 8612197b7e..ca9a28af5b 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.h +++ b/Telegram/SourceFiles/boxes/star_gift_box.h @@ -69,10 +69,15 @@ void ShowUniqueGiftWearBox( const Data::UniqueGift &gift, Settings::GiftWearBoxStyleOverride st); +void UpdateGiftSellPrice( + std::shared_ptr show, + std::shared_ptr unique, + Data::SavedStarGiftId savedId, + int price); void ShowUniqueGiftSellBox( std::shared_ptr show, not_null peer, - const Data::UniqueGift &gift, + std::shared_ptr unique, Data::SavedStarGiftId savedId, Settings::GiftWearBoxStyleOverride st); diff --git a/Telegram/SourceFiles/boxes/transfer_gift_box.cpp b/Telegram/SourceFiles/boxes/transfer_gift_box.cpp index 735add7b71..0eb7150c08 100644 --- a/Telegram/SourceFiles/boxes/transfer_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/transfer_gift_box.cpp @@ -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)); } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 36818359f3..8ba4f0924b 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -89,9 +89,11 @@ struct GiftUpdate { Delete, Pin, Unpin, + ResaleChange, }; Data::SavedStarGiftId id; + QString slug; Action action = {}; }; diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp index ad95a55a9c..2acc8d6d5a 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp @@ -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(descriptor) + ? v::get(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(_descriptor) ? v::get(_descriptor).info.unique.get() : nullptr; + const auto onsale = (unique && unique->starsForResale && _small); const auto hidden = v::is(_descriptor) && v::get(_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( diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h index d374787383..7c8b006dda 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h @@ -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 _uniquePatternCache; std::optional _stars; Ui::Animations::Simple _selectedAnimation; + int _resalePrice = 0; bool _subscribed = false; bool _patterned = false; bool _selected = false; diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp index 88745c6b92..f591c74bac 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp @@ -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; } diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 0738bca33f..686ebfe887 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -1052,21 +1052,7 @@ void FillUniqueGiftMenu( const auto name = UniqueGiftName(*unique); const auto confirm = [=](Fn 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( diff --git a/Telegram/SourceFiles/ui/boxes/confirm_box.cpp b/Telegram/SourceFiles/ui/boxes/confirm_box.cpp index 512b11a893..0c06fb5262 100644 --- a/Telegram/SourceFiles/ui/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/confirm_box.cpp @@ -58,7 +58,9 @@ void ConfirmBox(not_null 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>(args.confirmText) + || v::is(args.confirmText); const auto confirmButton = box->addButton( (confirmTextPlain ? v::text::take_plain(