From e629460942abd97bd31684b97826136d3971d492 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 18 Apr 2025 14:56:08 +0400 Subject: [PATCH] Implement filtering of resale gifts. --- Telegram/Resources/langs/lang.strings | 16 + Telegram/SourceFiles/api/api_premium.cpp | 3 +- Telegram/SourceFiles/boxes/star_gift_box.cpp | 725 ++++++++++++++++++ Telegram/SourceFiles/data/data_star_gift.h | 2 + .../SourceFiles/menu/gift_resale_filter.cpp | 147 ++++ .../SourceFiles/menu/gift_resale_filter.h | 55 ++ Telegram/SourceFiles/ui/effects/credits.style | 15 + Telegram/cmake/td_ui.cmake | 2 + 8 files changed, 964 insertions(+), 1 deletion(-) create mode 100644 Telegram/SourceFiles/menu/gift_resale_filter.cpp create mode 100644 Telegram/SourceFiles/menu/gift_resale_filter.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0176d9484e..816b61c8e9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3590,6 +3590,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_wear_end_toast" = "You took off {name}"; "lng_gift_many_pinned_title" = "Too Many Pinned Gifts"; "lng_gift_many_pinned_choose" = "Select a gift to unpin below"; +"lng_gift_resale_price" = "Price"; +"lng_gift_resale_number" = "Number"; +"lng_gift_resale_date" = "Date"; +"lng_gift_resale_sort_price" = "Sort by Price"; +"lng_gift_resale_sort_date" = "Sort by Date"; +"lng_gift_resale_sort_number" = "Sort by Number"; +"lng_gift_resale_filter_all" = "Select All"; +"lng_gift_resale_model" = "Model"; +"lng_gift_resale_models#one" = "{count} Model"; +"lng_gift_resale_models#other" = "{count} Models"; +"lng_gift_resale_backdrop" = "Backdrop"; +"lng_gift_resale_backdrops#one" = "{count} Backdrop"; +"lng_gift_resale_backdrops#other" = "{count} Backdrops"; +"lng_gift_resale_symbol" = "Symbol"; +"lng_gift_resale_symbols#one" = "{count} Symbol"; +"lng_gift_resale_symbols#other" = "{count} Symbols"; "lng_accounts_limit_title" = "Limit Reached"; "lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account."; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 63b9323c6f..7be23befea 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -812,6 +812,7 @@ std::optional FromTL( .starsToUpgrade = int64(data.vupgrade_stars().value_or_empty()), .starsResellMin = int64(resellPrice), .document = document, + .resellTitle = qs(data.vtitle().value_or_empty()), .resellCount = int(data.vavailability_resale().value_or_empty()), .limitedLeft = remaining.value_or_empty(), .limitedCount = total.value_or_empty(), @@ -939,7 +940,7 @@ Data::UniqueGiftPattern FromTL( } Data::UniqueGiftBackdrop FromTL(const MTPDstarGiftAttributeBackdrop &data) { - auto result = Data::UniqueGiftBackdrop(); + auto result = Data::UniqueGiftBackdrop{ .id = data.vbackdrop_id().v }; result.name = qs(data.vname()); result.rarityPermille = data.vrarity_permille().v; result.centerColor = Ui::ColorFromSerialized( diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index bda9b868e5..63749d168d 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -57,6 +57,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lottie/lottie_single_player.h" #include "main/main_app_config.h" #include "main/main_session.h" +#include "menu/gift_resale_filter.h" #include "payments/payments_form.h" #include "payments/payments_checkout_process.h" #include "payments/payments_non_panel_process.h" @@ -117,6 +118,8 @@ constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000); constexpr auto kCrossfadeDuration = crl::time(400); constexpr auto kUpgradeDoneToastDuration = 4 * crl::time(1000); constexpr auto kGiftsPreloadTimeout = 3 * crl::time(1000); +constexpr auto kResaleGiftsPerPage = 50; +constexpr auto kFiltersCount = 4; using namespace HistoryView; using namespace Info::PeerGifts; @@ -128,6 +131,20 @@ enum class PickType { }; using PickCallback = Fn, PickType)>; +enum class AttributeIdType { + Model, + Pattern, + Backdrop, +}; + +struct AttributeId { + uint64 value = 0; + AttributeIdType type = AttributeIdType::Model; + + friend inline auto operator<=>(AttributeId, AttributeId) = default; + friend inline bool operator==(AttributeId, AttributeId) = default; +}; + struct PremiumGiftsDescriptor { std::vector list; std::shared_ptr api; @@ -138,6 +155,53 @@ struct MyGiftsDescriptor { QString offset; }; +struct ModelCount { + Data::UniqueGiftModel model; + int count = 0; +}; + +struct BackdropCount { + Data::UniqueGiftBackdrop backdrop; + int count = 0; +}; + +struct PatternCount { + Data::UniqueGiftPattern pattern; + int count = 0; +}; + +enum class ResaleSort { + Date, + Price, + Number, +}; + +struct ResaleGiftsDescriptor { + uint64 giftId = 0; + QString title; + QString offset; + std::vector list; + std::vector models; + std::vector backdrops; + std::vector patterns; + uint64 attributesHash = 0; + int count = 0; + ResaleSort sort = ResaleSort::Date; +}; + +struct ResaleFilter { + uint64 attributesHash = 0; + base::flat_set attributes; + ResaleSort sort = ResaleSort::Date; + + friend inline auto operator<=>( + const ResaleFilter &, + const ResaleFilter &) = default; + friend inline bool operator==( + const ResaleFilter &, + const ResaleFilter &) = default; +}; + struct GiftsDescriptor { std::vector list; std::shared_ptr api; @@ -267,6 +331,53 @@ private: }; +[[nodiscard]] AttributeId FromTL(const MTPStarGiftAttributeId &id) { + return id.match([&](const MTPDstarGiftAttributeIdBackdrop &data) { + return AttributeId{ + .value = uint64(uint32(data.vbackdrop_id().v)), + .type = AttributeIdType::Backdrop, + }; + }, [&](const MTPDstarGiftAttributeIdModel &data) { + return AttributeId{ + .value = data.vdocument_id().v, + .type = AttributeIdType::Model, + }; + }, [&](const MTPDstarGiftAttributeIdPattern &data) { + return AttributeId{ + .value = data.vdocument_id().v, + .type = AttributeIdType::Pattern, + }; + }); +} + +[[nodiscard]] MTPStarGiftAttributeId AttributeToTL(AttributeId id) { + switch (id.type) { + case AttributeIdType::Backdrop: + return MTP_starGiftAttributeIdBackdrop( + MTP_int(int32(uint32(id.value)))); + case AttributeIdType::Model: + return MTP_starGiftAttributeIdModel(MTP_long(id.value)); + case AttributeIdType::Pattern: + return MTP_starGiftAttributeIdPattern(MTP_long(id.value)); + } + Unexpected("Invalid attribute id type"); +} + +[[nodiscard]] AttributeId IdFor(const Data::UniqueGiftBackdrop &value) { + return { + .value = uint64(uint32(value.id)), + .type = AttributeIdType::Backdrop, + }; +} + +[[nodiscard]] AttributeId IdFor(const Data::UniqueGiftModel &value) { + return { .value = value.document->id, .type = AttributeIdType::Model }; +} + +[[nodiscard]] AttributeId IdFor(const Data::UniqueGiftPattern &value) { + return { .value = value.document->id, .type = AttributeIdType::Pattern }; +} + [[nodiscard]] bool SortForBirthday(not_null peer) { const auto user = peer->asUser(); if (!user) { @@ -948,6 +1059,405 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { return result; } +[[nodiscard]] Text::String ResaleTabText(QString text) { + auto result = Text::String(); + result.setMarkedText( + st::semiboldTextStyle, + TextWithEntities{ text }.append( + Ui::Text::IconEmoji(&st::giftBoxResaleTabsDropdown)), + kMarkupTextOptions); + return result; +} + +[[nodiscard]] Text::String SortModeText(ResaleSort mode) { + if (mode == ResaleSort::Number) { + return ResaleTabText(tr::lng_gift_resale_number(tr::now)); + } else if (mode == ResaleSort::Price) { + return ResaleTabText(tr::lng_gift_resale_price(tr::now)); + } + return ResaleTabText(tr::lng_gift_resale_date(tr::now)); +} + +struct ResaleTabs { + rpl::producer filter; + object_ptr widget; +}; +[[nodiscard]] ResaleTabs MakeResaleTabs( + not_null window, + not_null peer, + const ResaleGiftsDescriptor &info, + rpl::producer filter) { + auto widget = object_ptr((QWidget*)nullptr); + const auto raw = widget.data(); + + struct Button { + QRect geometry; + Text::String text; + }; + struct State { + rpl::variable filter; + rpl::variable fullWidth; + std::vector