diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0da7bbf62..4b6d36107 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2981,6 +2981,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_stars_incoming" = "Use Stars to unlock content and services on Telegram."; "lng_gift_until" = "Until"; +"lng_gift_premium_subtitle" = "Gift Premium"; +"lng_gift_premium_about" = "Give {name} access to exclusive features with Telegram Premium. {features}"; +"lng_gift_premium_features" = "See Features >"; +"lng_gift_premium_label" = "Premium"; +"lng_gift_stars_subtitle" = "Gift Stars"; +"lng_gift_stars_about" = "Give {name} gifts that can be kept on your profile or converted to Stars. {link}"; +"lng_gift_stars_link" = "What are Stars >"; +"lng_gift_stars_limited" = "limited"; +"lng_gift_stars_tabs_all" = "All Gifts"; +"lng_gift_stars_tabs_limited" = "Limited"; + "lng_accounts_limit_title" = "Limit Reached"; "lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account."; "lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts."; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 0cd7a308b..7c2934652 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -550,6 +550,24 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice( }; } +std::vector PremiumGiftCodeOptions::optionsForPeer() const { + auto result = std::vector(); + + if (!_optionsForOnePerson.currency.isEmpty()) { + const auto count = int(_optionsForOnePerson.months.size()); + result.reserve(count); + for (auto i = 0; i != count; ++i) { + Assert(i < _optionsForOnePerson.totalCosts.size()); + result.push_back({ + .cost = _optionsForOnePerson.totalCosts[i], + .currency = _optionsForOnePerson.currency, + .months = _optionsForOnePerson.months[i], + }); + } + } + return result; +} + Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) { const auto it = _subscriptionOptions.find(amount); if (it != end(_subscriptionOptions)) { diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index d78811ceb..d778b00d4 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -67,6 +67,12 @@ struct GiveawayInfo { } }; +struct GiftOptionData { + int64 cost = 0; + QString currency; + int months = 0; +}; + class Premium final { public: explicit Premium(not_null api); @@ -171,6 +177,7 @@ public: PremiumGiftCodeOptions(not_null peer); [[nodiscard]] rpl::producer request(); + [[nodiscard]] std::vector optionsForPeer() const; [[nodiscard]] Data::PremiumSubscriptionOptions options(int amount); [[nodiscard]] const std::vector &availablePresets() const; [[nodiscard]] int monthsFromPreset(int monthsIndex); @@ -206,7 +213,7 @@ private: base::flat_map _subscriptionOptions; struct { std::vector months; - std::vector totalCosts; + std::vector totalCosts; QString currency; } _optionsForOnePerson; diff --git a/Telegram/SourceFiles/boxes/gift_credits_box.cpp b/Telegram/SourceFiles/boxes/gift_credits_box.cpp index f0ecd05f6..77b44f6e0 100644 --- a/Telegram/SourceFiles/boxes/gift_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_credits_box.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/gift_credits_box.h" #include "api/api_credits.h" +#include "api/api_premium.h" #include "boxes/peer_list_controllers.h" #include "data/data_peer.h" #include "data/data_session.h" @@ -15,11 +16,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_custom_emoji.h" #include "lang/lang_keys.h" #include "main/session/session_show.h" +#include "main/main_session.h" #include "settings/settings_credits_graphics.h" +#include "settings/settings_credits.h" +#include "settings/settings_premium.h" #include "ui/controls/userpic_button.h" #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_stars_colored.h" +#include "ui/effects/ripple_animation.h" #include "ui/layers/generic_box.h" +#include "ui/painter.h" #include "ui/rect.h" #include "ui/text/text_utilities.h" #include "ui/vertical_list.h" @@ -33,39 +39,485 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_layers.h" #include "styles/style_premium.h" -namespace Ui { +#include "data/stickers/data_stickers.h" +#include "data/data_document.h" -void GiftCreditsBox( - not_null box, +namespace Ui { +namespace { + +constexpr auto kPriceTabAll = 0; +constexpr auto kPriceTabLimited = -1; + +struct GiftTypePremium { + int64 cost = 0; + QString currency; + int months = 0; + int discountPercent = 0; + + [[nodiscard]] friend inline bool operator==( + const GiftTypePremium &, + const GiftTypePremium &) = default; +}; + +struct GiftTypeStars { + DocumentData *document = nullptr; + int stars = 0; + bool limited = false; + + [[nodiscard]] friend inline bool operator==( + const GiftTypeStars&, + const GiftTypeStars&) = default; +}; + +struct GiftDescriptor : std::variant { + using variant::variant; + + [[nodiscard]] friend inline bool operator==( + const GiftDescriptor&, + const GiftDescriptor&) = default; +}; + +[[nodiscard]] rpl::producer> GiftsPremium( + not_null session, + not_null peer) { + struct Session { + std::vector last; + }; + static auto Map = base::flat_map, Session>(); + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + auto i = Map.find(session); + if (i == end(Map)) { + i = Map.emplace(session, Session()).first; + session->lifetime().add([=] { Map.remove(session); }); + } + if (!i->second.last.empty()) { + consumer.put_next_copy(i->second.last); + } + + using namespace Api; + const auto api = lifetime.make_state(peer); + api->request() | rpl::start_with_error_done([=](QString error) { + consumer.put_next({}); + }, [=] { + const auto &options = api->optionsForPeer(); + auto list = std::vector(); + list.reserve(options.size()); + auto minMonthsGift = GiftTypePremium(); + for (const auto &option : options) { + list.push_back({ + .cost = option.cost, + .currency = option.currency, + .months = option.months, + }); + if (!minMonthsGift.months + || option.months < minMonthsGift.months) { + minMonthsGift = list.back(); + } + } + for (auto &gift : list) { + if (gift.months > minMonthsGift.months + && gift.currency == minMonthsGift.currency) { + const auto costPerMonth = gift.cost / gift.months; + const auto maxCostPerMonth = minMonthsGift.cost + / minMonthsGift.months; + const auto costRatio = costPerMonth / maxCostPerMonth; + const auto discount = 1. - costRatio; + const auto discountPercent = 100 * discount; + const auto value = int(base::SafeRound(discountPercent)); + if (value > 0 && value < 100) { + gift.discountPercent = value; + } + } + } + Map[session].last = list; + consumer.put_next_copy(list); + }, lifetime); + + return lifetime; + }; +} + +[[nodiscard]] rpl::producer> GiftsStars( + not_null session, + not_null peer) { + //struct Session { + // std::vector last; + //}; + //static auto Map = base::flat_map, Session>(); + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + auto list = std::vector(); + + const auto add = [&](uint64 setId, int price, bool limited) { + auto &sets = session->data().stickers().setsRef(); + const auto i = sets.find(setId); + if (i != end(sets)) { + for (const auto document : i->second->stickers) { + price = document->isPremiumSticker() ? 1000 : price; + list.push_back({ + .document = document, + .stars = price, + .limited = limited, + }); + } + } + }; + add(Data::Stickers::CloudRecentSetId, 100, false); + add(Data::Stickers::RecentSetId, 250, false); + add(Data::Stickers::FavedSetId, 50, true); + + consumer.put_next(std::move(list)); + + return lifetime; + }; +} + +[[nodiscard]] Text::String TabTextForPrice( + not_null session, + int price) { + const auto simple = [](const QString &text) { + return Text::String(st::semiboldTextStyle, text); + }; + if (price == kPriceTabAll) { + return simple(tr::lng_gift_stars_tabs_all(tr::now)); + } else if (price == kPriceTabLimited) { + return simple(tr::lng_gift_stars_tabs_limited(tr::now)); + } + auto &manager = session->data().customEmojiManager(); + auto result = Text::String(); + const auto context = Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = [] {}, + }; + result.setMarkedText( + st::semiboldTextStyle, + manager.creditsEmoji().append(QString::number(price)), + kMarkupTextOptions, + context); + return result; +} + +struct GiftPriceTabs { + rpl::producer priceTab; + object_ptr widget; +}; +[[nodiscard]] GiftPriceTabs MakeGiftsPriceTabs( + not_null window, + not_null peer, + rpl::producer> gifts) { + auto widget = object_ptr((QWidget*)nullptr); + const auto raw = widget.data(); + + struct Button { + QRect geometry; + Text::String text; + int price = 0; + bool active = false; + }; + struct State { + rpl::variable> prices; + rpl::variable priceTab = kPriceTabAll; + std::vector