/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_star_gift.h" #include "api/api_premium.h" #include "apiwrap.h" #include "data/data_document.h" #include "data/data_session.h" #include "data/data_user.h" #include "lang/lang_keys.h" #include "lang/lang_tag.h" #include "main/main_session.h" #include "ui/controls/ton_common.h" #include "ui/text/text_utilities.h" #include "styles/style_credits.h" namespace Data { namespace { constexpr auto kMyGiftsPerPage = 50; constexpr auto kResaleGiftsPerPage = 50; constexpr auto kCraftGiftsPerPage = 50; [[nodiscard]] MTPStarGiftAttributeId AttributeToTL(GiftAttributeId id) { switch (id.type) { case GiftAttributeIdType::Backdrop: return MTP_starGiftAttributeIdBackdrop( MTP_int(int32(uint32(id.value)))); case GiftAttributeIdType::Model: return MTP_starGiftAttributeIdModel(MTP_long(id.value)); case GiftAttributeIdType::Pattern: return MTP_starGiftAttributeIdPattern(MTP_long(id.value)); } Unexpected("Invalid attribute id type"); } [[nodiscard]] GiftAttributeId FromTL(const MTPStarGiftAttributeId &id) { return id.match([&](const MTPDstarGiftAttributeIdBackdrop &data) { return GiftAttributeId{ .value = uint64(uint32(data.vbackdrop_id().v)), .type = GiftAttributeIdType::Backdrop, }; }, [&](const MTPDstarGiftAttributeIdModel &data) { return GiftAttributeId{ .value = data.vdocument_id().v, .type = GiftAttributeIdType::Model, }; }, [&](const MTPDstarGiftAttributeIdPattern &data) { return GiftAttributeId{ .value = data.vdocument_id().v, .type = GiftAttributeIdType::Pattern, }; }); } } // namespace QString UniqueGiftName(const UniqueGift &gift) { return UniqueGiftName(gift.title, gift.number); } QString UniqueGiftName(const QString &title, int number) { return title + u" #"_q + Lang::FormatCountDecimal(number); } QString UniqueGiftRarityText(UniqueGiftRarity type) { switch (type) { case UniqueGiftRarity::Uncommon: return tr::lng_gift_uncommon_tag(tr::now); case UniqueGiftRarity::Rare: return tr::lng_gift_rare_tag(tr::now); case UniqueGiftRarity::Epic: return tr::lng_gift_epic_tag(tr::now); case UniqueGiftRarity::Legendary: return tr::lng_gift_legendary_tag(tr::now); } return QString(); } UniqueGiftRarityColors UniqueGiftRarityBadgeColors(UniqueGiftRarity type) { constexpr auto kBgOpacity = 0.15; const auto base = [&] { switch (type) { case UniqueGiftRarity::Uncommon: return QColor(0x40, 0xA9, 0x20); case UniqueGiftRarity::Rare: return QColor(0x11, 0xAA, 0xBE); case UniqueGiftRarity::Epic: return QColor(0x95, 0x5C, 0xDB); case UniqueGiftRarity::Legendary: return QColor(0xBF, 0x76, 0x00); } Unexpected("Invalid rarity type."); }(); auto bg = base; bg.setAlphaF(kBgOpacity); return { bg, base }; } QString UniqueGiftAttributeText(const UniqueGiftAttribute &attribute) { const auto type = attribute.rarityType(); if (type != UniqueGiftRarity::Default) { return UniqueGiftRarityText(type); } const auto permille = attribute.rarityPermille(); return (permille > 0) ? (QString::number(permille / 10.) + '%') : u"< 0.1%"_q; } bool UniqueGiftAttributeHasSpecialRarity(const UniqueGiftAttribute &attribute) { return attribute.rarityType() != UniqueGiftRarity::Default; } CreditsAmount UniqueGiftResaleStars(const UniqueGift &gift) { return CreditsAmount(gift.starsForResale); } CreditsAmount UniqueGiftResaleTon(const UniqueGift &gift) { return CreditsAmount( gift.nanoTonForResale / Ui::kNanosInOne, gift.nanoTonForResale % Ui::kNanosInOne, CreditsType::Ton); } CreditsAmount UniqueGiftResaleAsked(const UniqueGift &gift) { return gift.onlyAcceptTon ? UniqueGiftResaleTon(gift) : UniqueGiftResaleStars(gift); } TextWithEntities FormatGiftResaleStars(const UniqueGift &gift) { return Ui::Text::IconEmoji( &st::starIconEmoji ).append(Lang::FormatCountDecimal(gift.starsForResale)); } TextWithEntities FormatGiftResaleTon(const UniqueGift &gift) { return Ui::Text::IconEmoji( &st::tonIconEmoji ).append(Lang::FormatCreditsAmountDecimal(UniqueGiftResaleTon(gift))); } TextWithEntities FormatGiftResaleAsked(const UniqueGift &gift) { return gift.onlyAcceptTon ? FormatGiftResaleTon(gift) : FormatGiftResaleStars(gift); } GiftAttributeId IdFor(const UniqueGiftBackdrop &value) { return { .value = uint64(uint32(value.id)), .type = GiftAttributeIdType::Backdrop, }; } GiftAttributeId IdFor(const UniqueGiftModel &value) { return { .value = value.document->id, .type = GiftAttributeIdType::Model, }; } GiftAttributeId IdFor(const UniqueGiftPattern &value) { return { .value = value.document->id, .type = GiftAttributeIdType::Pattern, }; } rpl::producer MyUniqueGiftsSlice( not_null session, MyUniqueType type, QString offset) { return [=](auto consumer) { using Flag = MTPpayments_GetSavedStarGifts::Flag; const auto user = session->user(); const auto requestId = session->api().request( MTPpayments_GetSavedStarGifts( MTP_flags(Flag::f_exclude_upgradable | Flag::f_exclude_unupgradable | Flag::f_exclude_unlimited | ((type == MyUniqueType::OnlyOwned) ? Flag::f_exclude_hosted : Flag())), user->input(), MTP_int(0), // collection_id MTP_string(offset), MTP_int(kMyGiftsPerPage) )).done([=](const MTPpayments_SavedStarGifts &result) { auto gifts = MyGiftsDescriptor(); const auto &data = result.data(); if (const auto next = data.vnext_offset()) { gifts.offset = qs(*next); } const auto owner = &session->data(); owner->processUsers(data.vusers()); owner->processChats(data.vchats()); gifts.list.reserve(data.vgifts().v.size()); for (const auto &gift : data.vgifts().v) { if (auto parsed = Api::FromTL(user, gift)) { gifts.list.push_back(std::move(*parsed)); } } consumer.put_next(std::move(gifts)); consumer.put_done(); }).fail([=] { consumer.put_next({}); consumer.put_done(); }).send(); auto lifetime = rpl::lifetime(); lifetime.add([=] { session->api().request(requestId).cancel(); }); return lifetime; }; } rpl::producer ResaleGiftsSlice( not_null session, uint64 giftId, ResaleGiftsFilter filter, QString offset) { return [=](auto consumer) { using Flag = MTPpayments_GetResaleStarGifts::Flag; const auto requestId = session->api().request( MTPpayments_GetResaleStarGifts( MTP_flags(Flag::f_attributes_hash | ((filter.sort == ResaleGiftsSort::Price) ? Flag::f_sort_by_price : (filter.sort == ResaleGiftsSort::Number) ? Flag::f_sort_by_num : Flag()) | (filter.attributes.empty() ? Flag() : Flag::f_attributes) | (filter.forCraft ? Flag::f_for_craft : Flag())), MTP_long(filter.attributesHash), MTP_long(giftId), MTP_vector_from_range(filter.attributes | ranges::views::transform(AttributeToTL)), MTP_string(offset), MTP_int(kResaleGiftsPerPage) )).done([=](const MTPpayments_ResaleStarGifts &result) { const auto &data = result.data(); session->data().processUsers(data.vusers()); session->data().processChats(data.vchats()); auto info = ResaleGiftsDescriptor{ .giftId = giftId, .offset = qs(data.vnext_offset().value_or_empty()), .count = data.vcount().v, }; const auto &list = data.vgifts().v; info.list.reserve(list.size()); for (const auto &entry : list) { if (auto gift = Api::FromTL(session, entry)) { info.list.push_back(std::move(*gift)); } } info.attributesHash = data.vattributes_hash().value_or_empty(); const auto &attributes = data.vattributes() ? data.vattributes()->v : QVector(); const auto &counters = data.vcounters() ? data.vcounters()->v : QVector(); auto counts = base::flat_map(); counts.reserve(counters.size()); for (const auto &counter : counters) { const auto &data = counter.data(); counts.emplace(FromTL(data.vattribute()), data.vcount().v); } const auto count = [&](GiftAttributeId id) { const auto i = counts.find(id); return i != end(counts) ? i->second : 0; }; info.models.reserve(attributes.size()); info.patterns.reserve(attributes.size()); info.backdrops.reserve(attributes.size()); for (const auto &attribute : attributes) { attribute.match([&](const MTPDstarGiftAttributeModel &data) { const auto parsed = Api::FromTL(session, data); info.models.push_back({ parsed, count(IdFor(parsed)) }); }, [&](const MTPDstarGiftAttributePattern &data) { const auto parsed = Api::FromTL(session, data); info.patterns.push_back({ parsed, count(IdFor(parsed)) }); }, [&](const MTPDstarGiftAttributeBackdrop &data) { const auto parsed = Api::FromTL(data); info.backdrops.push_back({ parsed, count(IdFor(parsed)) }); }, [](const MTPDstarGiftAttributeOriginalDetails &data) { }); } consumer.put_next(std::move(info)); consumer.put_done(); }).fail([=](const MTP::Error &error) { consumer.put_next({}); consumer.put_done(); }).send(); auto lifetime = rpl::lifetime(); lifetime.add([=] { session->api().request(requestId).cancel(); }); return lifetime; }; } rpl::producer CraftGiftsSlice( not_null session, uint64 giftId, QString offset) { return [=](auto consumer) { const auto requestId = session->api().request( MTPpayments_GetCraftStarGifts( MTP_long(giftId), MTP_string(offset), MTP_int(kCraftGiftsPerPage)) ).done([=](const MTPpayments_SavedStarGifts &result) { const auto user = session->user(); const auto owner = &session->data(); const auto &data = result.data(); owner->processUsers(data.vusers()); owner->processChats(data.vchats()); auto info = CraftGiftsDescriptor{ .giftId = giftId, .offset = qs(data.vnext_offset().value_or_empty()), .count = data.vcount().v, }; info.list.reserve(data.vgifts().v.size()); for (const auto &gift : data.vgifts().v) { if (auto parsed = Api::FromTL(user, gift)) { info.list.push_back(std::move(*parsed)); } } consumer.put_next(std::move(info)); consumer.put_done(); }).fail([=](const MTP::Error &error) { consumer.put_next({}); consumer.put_done(); }).send(); auto lifetime = rpl::lifetime(); lifetime.add([=] { session->api().request(requestId).cancel(); }); return lifetime; }; } } // namespace Data