Add price tag in resale gift view.

This commit is contained in:
John Preston 2025-04-23 13:43:12 +04:00
parent 18f14b828c
commit 04a7f14c0e
5 changed files with 145 additions and 21 deletions

View file

@ -3596,6 +3596,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_sell_put" = "Put for Sale"; "lng_gift_sell_put" = "Put for Sale";
"lng_gift_sell_update" = "Update the Price"; "lng_gift_sell_update" = "Update the Price";
"lng_gift_sell_toast" = "{name} is now for sale!"; "lng_gift_sell_toast" = "{name} is now for sale!";
"lng_gift_sell_updated" = "Sale price for {name} was updated.";
"lng_gift_sell_removed" = "{name} is removed from sale."; "lng_gift_sell_removed" = "{name} is removed from sale.";
"lng_gift_menu_show" = "Show"; "lng_gift_menu_show" = "Show";
"lng_gift_menu_hide" = "Hide"; "lng_gift_menu_hide" = "Hide";

View file

@ -89,6 +89,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "window/section_widget.h" #include "window/section_widget.h"
@ -3643,12 +3644,87 @@ void ShowStarGiftBox(
}, i->second.lifetime); }, i->second.lifetime);
} }
void SetupResalePriceButton(
not_null<Ui::RpWidget*> parent,
rpl::producer<QColor> background,
rpl::producer<int> price,
Fn<void()> click) {
const auto resale = Ui::CreateChild<
Ui::FadeWrapScaled<Ui::AbstractButton>
>(parent, object_ptr<Ui::AbstractButton>(parent));
resale->move(0, 0);
const auto button = resale->entity();
const auto text = Ui::CreateChild<Ui::FlatLabel>(
button,
QString(),
st::uniqueGiftResalePrice);
text->setAttribute(Qt::WA_TransparentForMouseEvents);
text->sizeValue() | rpl::start_with_next([=](QSize size) {
const auto padding = st::uniqueGiftResalePadding;
const auto margin = st::uniqueGiftResaleMargin;
button->resize(size.grownBy(padding + margin));
text->move((margin + padding).left(), (margin + padding).top());
}, button->lifetime());
text->setTextColorOverride(QColor(255, 255, 255, 255));
std::move(price) | rpl::start_with_next([=](int value) {
if (value > 0) {
text->setMarkedText(
Ui::Text::IconEmoji(&st::starIconEmoji).append(
Lang::FormatCountDecimal(value)));
resale->toggle(true, anim::type::normal);
} else {
resale->toggle(false, anim::type::normal);
}
}, resale->lifetime());
resale->finishAnimating();
const auto bg = button->lifetime().make_state<rpl::variable<QColor>>(
std::move(background));
button->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(button);
auto hq = PainterHighQualityEnabler(p);
const auto inner = button->rect().marginsRemoved(
st::uniqueGiftResaleMargin);
const auto radius = inner.height() / 2.;
p.setPen(Qt::NoPen);
p.setBrush(bg->current());
p.drawRoundedRect(inner, radius, radius);
}, button->lifetime());
bg->changes() | rpl::start_with_next([=] {
button->update();
}, button->lifetime());
if (click) {
resale->entity()->setClickedCallback(std::move(click));
} else {
resale->setAttribute(Qt::WA_TransparentForMouseEvents);
}
}
void AddUniqueGiftCover( void AddUniqueGiftCover(
not_null<VerticalLayout*> container, not_null<VerticalLayout*> container,
rpl::producer<Data::UniqueGift> data, rpl::producer<Data::UniqueGift> data,
rpl::producer<QString> subtitleOverride) { rpl::producer<QString> subtitleOverride,
rpl::producer<int> resalePrice,
Fn<void()> resaleClick) {
const auto cover = container->add(object_ptr<RpWidget>(container)); const auto cover = container->add(object_ptr<RpWidget>(container));
if (resalePrice) {
auto background = rpl::duplicate(
data
) | rpl::map([=](const Data::UniqueGift &unique) {
return unique.backdrop.patternColor;
});
SetupResalePriceButton(
cover,
std::move(background),
std::move(resalePrice),
std::move(resaleClick));
}
const auto title = CreateChild<FlatLabel>( const auto title = CreateChild<FlatLabel>(
cover, cover,
rpl::duplicate( rpl::duplicate(
@ -4068,16 +4144,21 @@ void UpdateGiftSellPrice(
std::shared_ptr<Data::UniqueGift> unique, std::shared_ptr<Data::UniqueGift> unique,
Data::SavedStarGiftId savedId, Data::SavedStarGiftId savedId,
int price) { int price) {
const auto was = unique->starsForResale;
const auto session = &show->session(); const auto session = &show->session();
session->api().request(MTPpayments_UpdateStarGiftPrice( session->api().request(MTPpayments_UpdateStarGiftPrice(
Api::InputSavedStarGiftId(savedId, unique), Api::InputSavedStarGiftId(savedId, unique),
MTP_long(price) MTP_long(price)
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result); session->api().applyUpdates(result);
show->showToast(tr::lng_gift_sell_toast( show->showToast((!price
tr::now, ? tr::lng_gift_sell_removed
lt_name, : (was > 0)
Data::UniqueGiftName(*unique))); ? tr::lng_gift_sell_updated
: tr::lng_gift_sell_toast)(
tr::now,
lt_name,
Data::UniqueGiftName(*unique)));
unique->starsForResale = price; unique->starsForResale = price;
session->data().notifyGiftUpdate({ session->data().notifyGiftUpdate({
@ -4088,12 +4169,10 @@ void UpdateGiftSellPrice(
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
show->showToast(error.type()); show->showToast(error.type());
}).send(); }).send();
} }
void ShowUniqueGiftSellBox( void ShowUniqueGiftSellBox(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
std::shared_ptr<Data::UniqueGift> unique, std::shared_ptr<Data::UniqueGift> unique,
Data::SavedStarGiftId savedId, Data::SavedStarGiftId savedId,
Settings::GiftWearBoxStyleOverride st) { Settings::GiftWearBoxStyleOverride st) {
@ -4191,9 +4270,7 @@ void ShowUniqueGiftSellBox(
good ? st::windowSubTextFg->c : st::boxTextFgError->c); good ? st::windowSubTextFg->c : st::boxTextFgError->c);
}, details->lifetime()); }, details->lifetime());
const auto button = box->addButton(priceNow QObject::connect(field, &NumberInput::submitted, [=] {
? tr::lng_gift_sell_update()
: tr::lng_gift_sell_put(), [=] {
const auto count = field->getLastText().toInt(); const auto count = field->getLastText().toInt();
if (count < minimal) { if (count < minimal) {
field->showError(); field->showError();
@ -4203,6 +4280,9 @@ void ShowUniqueGiftSellBox(
box->closeBox(); box->closeBox();
UpdateGiftSellPrice(show, unique, savedId, count); UpdateGiftSellPrice(show, unique, savedId, count);
}); });
const auto button = box->addButton(priceNow
? tr::lng_gift_sell_update()
: tr::lng_gift_sell_put(), [=] { field->submitted({}); });
rpl::combine( rpl::combine(
box->widthValue(), box->widthValue(),
button->widthValue() button->widthValue()

View file

@ -57,7 +57,9 @@ void ShowStarGiftBox(
void AddUniqueGiftCover( void AddUniqueGiftCover(
not_null<VerticalLayout*> container, not_null<VerticalLayout*> container,
rpl::producer<Data::UniqueGift> data, rpl::producer<Data::UniqueGift> data,
rpl::producer<QString> subtitleOverride = nullptr); rpl::producer<QString> subtitleOverride = nullptr,
rpl::producer<int> resalePrice = nullptr,
Fn<void()> resaleClick = nullptr);
void AddWearGiftCover( void AddWearGiftCover(
not_null<VerticalLayout*> container, not_null<VerticalLayout*> container,
const Data::UniqueGift &data, const Data::UniqueGift &data,
@ -76,7 +78,6 @@ void UpdateGiftSellPrice(
int price); int price);
void ShowUniqueGiftSellBox( void ShowUniqueGiftSellBox(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
std::shared_ptr<Data::UniqueGift> unique, std::shared_ptr<Data::UniqueGift> unique,
Data::SavedStarGiftId savedId, Data::SavedStarGiftId savedId,
Settings::GiftWearBoxStyleOverride st); Settings::GiftWearBoxStyleOverride st);

View file

@ -904,6 +904,22 @@ void ProcessReceivedSubscriptions(
} }
} }
[[nodiscard]] bool CanResellGift(
not_null<Main::Session*> session,
const Data::CreditsHistoryEntry &e) {
const auto unique = e.uniqueGift.get();
const auto owner = unique
? session->data().peer(unique->ownerId).get()
: nullptr;
return !owner
? false
: owner->isSelf()
? e.in
: false;
// Currently we're not reselling channel gifts.
// (owner->isChannel() && owner->asChannel()->canTransferGifts());
}
void FillUniqueGiftMenu( void FillUniqueGiftMenu(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::PopupMenu*> menu, not_null<Ui::PopupMenu*> menu,
@ -1042,10 +1058,7 @@ void FillUniqueGiftMenu(
}, st.wear ? st.wear : &st::menuIconNftWear); }, st.wear ? st.wear : &st::menuIconNftWear);
} }
} }
const auto sell = owner->isSelf() if (CanResellGift(&show->session(), e)) {
? e.in
: (owner->isChannel() && owner->asChannel()->canTransferGifts());
if (sell) {
const auto resalePrice = unique->starsForResale; const auto resalePrice = unique->starsForResale;
if (resalePrice > 0) { if (resalePrice > 0) {
menu->addAction(tr::lng_gift_transfer_unlist(tr::now), [=] { menu->addAction(tr::lng_gift_transfer_unlist(tr::now), [=] {
@ -1068,7 +1081,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, unique, savedId, style);
}, st.resell ? st.resell : &st::menuIconTagSell); }, st.resell ? st.resell : &st::menuIconTagSell);
} }
} }
@ -1169,7 +1182,8 @@ void GenericCreditsEntryBox(
if (auto savedId = EntryToSavedStarGiftId(session, e)) { if (auto savedId = EntryToSavedStarGiftId(session, e)) {
session->data().giftUpdates( session->data().giftUpdates(
) | rpl::start_with_next([=](const Data::GiftUpdate &update) { ) | rpl::start_with_next([=](const Data::GiftUpdate &update) {
if (update.id == savedId) { if (update.id == savedId
&& update.action != Data::GiftUpdate::Action::ResaleChange) {
box->closeBox(); box->closeBox();
} }
}, box->lifetime()); }, box->lifetime());
@ -1207,9 +1221,32 @@ void GenericCreditsEntryBox(
if (uniqueGift) { if (uniqueGift) {
box->setNoContentMargin(true); box->setNoContentMargin(true);
//const auto canEditResale = (e.bareGiftOwnerId == selfPeerId); const auto slug = uniqueGift->slug;
//auto starsResale = rpl auto price = rpl::single(
AddUniqueGiftCover(content, rpl::single(*uniqueGift)); rpl::empty
) | rpl::then(session->data().giftUpdates(
) | rpl::filter([=](const Data::GiftUpdate &update) {
return (update.action == Data::GiftUpdate::Action::ResaleChange)
&& (update.slug == slug);
}) | rpl::to_empty) | rpl::map([unique = e.uniqueGift] {
return unique->starsForResale;
});
auto change = [=] {
const auto style = st.giftWearBox
? *st.giftWearBox
: GiftWearBoxStyleOverride();
ShowUniqueGiftSellBox(
show,
e.uniqueGift,
EntryToSavedStarGiftId(session, e),
style);
};
AddUniqueGiftCover(
content,
rpl::single(*uniqueGift),
{},
std::move(price),
CanResellGift(session, e) ? std::move(change) : Fn<void()>());
AddSkip(content, st::defaultVerticalListSkip * 2); AddSkip(content, st::defaultVerticalListSkip * 2);

View file

@ -221,6 +221,11 @@ uniqueGiftModelTop: 20px;
uniqueGiftTitle: FlatLabel(boxTitle) { uniqueGiftTitle: FlatLabel(boxTitle) {
align: align(top); align: align(top);
} }
uniqueGiftResalePrice: FlatLabel(defaultFlatLabel) {
style: semiboldTextStyle;
}
uniqueGiftResalePadding: margins(4px, 4px, 8px, 4px);
uniqueGiftResaleMargin: margins(10px, 10px, 10px, 10px);
uniqueGiftTitleTop: 140px; uniqueGiftTitleTop: 140px;
uniqueGiftSubtitle: FlatLabel(defaultFlatLabel) { uniqueGiftSubtitle: FlatLabel(defaultFlatLabel) {
minWidth: 256px; minWidth: 256px;