/* 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 "settings/settings_credits.h" #include "api/api_credits.h" #include "core/click_handler_types.h" #include "data/data_user.h" #include "info/settings/info_settings_widget.h" // SectionCustomTopBarData. #include "lang/lang_keys.h" #include "main/main_session.h" #include "payments/payments_checkout_process.h" #include "payments/payments_form.h" #include "settings/settings_common_session.h" #include "ui/boxes/boost_box.h" // Ui::StartFireworks. #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_top_bar.h" #include "ui/image/image_prepare.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/wrap/fade_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" #include "styles/style_credits.h" #include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_settings.h" #include // XXH64. #include namespace Settings { namespace { using SectionCustomTopBarData = Info::Settings::SectionCustomTopBarData; [[nodiscard]] uint64 UniqueIdFromOption( const Data::CreditTopupOption &d) { const auto string = QString::number(d.credits) + d.product + d.currency + QString::number(d.amount); return XXH64(string.data(), string.size() * sizeof(ushort), 0); } [[nodiscard]] QImage GenerateStarForLightTopBar(QRectF rect) { const auto strokeWidth = 3; auto colorized = qs(Ui::Premium::ColorizedSvg( Ui::Premium::CreditsIconGradientStops())); colorized.replace( "stroke=\"none\"", "stroke=\"" + st::creditsStroke->c.name() + "\""); colorized.replace("stroke-width=\"1\"", "stroke-width=\"3\""); auto svg = QSvgRenderer(colorized.toUtf8()); svg.setViewBox(svg.viewBox() + Margins(strokeWidth)); const auto size = Size(st::settingsButton.height); auto frame = QImage( size * style::DevicePixelRatio(), QImage::Format_ARGB32_Premultiplied); frame.setDevicePixelRatio(style::DevicePixelRatio()); frame.fill(Qt::transparent); { auto q = QPainter(&frame); svg.render(&q, Rect(size)); } return frame; } class Credits : public Section { public: Credits( QWidget *parent, not_null controller); [[nodiscard]] rpl::producer title() override; [[nodiscard]] QPointer createPinnedToTop( not_null parent) override; void showFinished() override; [[nodiscard]] bool hasFlexibleTopBar() const override; void setStepDataReference(std::any &data) override; [[nodiscard]] rpl::producer<> sectionShowBack() override final; private: void setupContent(); void setupOptions(not_null container); const not_null _controller; QImage _star; base::unique_qptr> _back; base::unique_qptr _close; rpl::variable _backToggles; rpl::variable _wrap; Fn _setPaused; rpl::event_stream<> _showBack; rpl::event_stream<> _showFinished; rpl::variable _buttonText; }; Credits::Credits( QWidget *parent, not_null controller) : Section(parent) , _controller(controller) , _star(GenerateStarForLightTopBar({})) { setupContent(); } rpl::producer Credits::title() { return tr::lng_premium_summary_title(); } bool Credits::hasFlexibleTopBar() const { return true; } rpl::producer<> Credits::sectionShowBack() { return _showBack.events(); } void Credits::setStepDataReference(std::any &data) { const auto my = std::any_cast(&data); if (my) { _backToggles = std::move( my->backButtonEnables ) | rpl::map_to(true); _wrap = std::move(my->wrapValue); } } void Credits::setupOptions(not_null container) { const auto options = container->add( object_ptr>( container, object_ptr(container))); const auto content = options->entity(); Ui::AddSkip(content, st::settingsPremiumOptionsPadding.top()); const auto fill = [=](Data::CreditTopupOptions options) { while (content->count()) { delete content->widgetAt(0); } Ui::AddSubsectionTitle( content, tr::lng_credits_summary_options_subtitle()); for (const auto &option : options) { const auto button = content->add(object_ptr( content, tr::lng_credits_summary_options_credits( lt_count_decimal, rpl::single(option.credits) | tr::to_count()), st::creditsTopupButton)); const auto icon = Ui::CreateChild(button); icon->resize(Size(button->st().height)); icon->paintRequest( ) | rpl::start_with_next([=](const QRect &rect) { auto p = QPainter(icon); p.drawImage(0, 0, _star); }, icon->lifetime()); const auto price = Ui::CreateChild( button, Ui::FillAmountAndCurrency(option.amount, option.currency), st::creditsTopupPrice); button->sizeValue( ) | rpl::start_with_next([=](const QSize &size) { const auto &st = button->st(); price->moveToRight(st.padding.right(), st.padding.top()); icon->moveToLeft(st.iconLeft, st.padding.top()); }, button->lifetime()); button->setClickedCallback([=] { const auto invoice = Payments::InvoiceCredits{ .session = &_controller->session(), .randomId = UniqueIdFromOption(option), .credits = option.credits, .product = option.product, .currency = option.currency, .amount = option.amount, }; const auto weak = Ui::MakeWeak(button); const auto done = [=](Payments::CheckoutResult result) { if (const auto strong = weak.data()) { strong->window()->setFocus(); if (result == Payments::CheckoutResult::Paid) { Ui::StartFireworks(this); } } }; Payments::CheckoutProcess::Start(std::move(invoice), done); }); Ui::ToggleChildrenVisibility(button, true); } // Footer. { auto text = tr::lng_credits_summary_options_about( lt_link, tr::lng_credits_summary_options_about_link( ) | rpl::map([](const QString &t) { using namespace Ui::Text; return Link(t, u"https://telegram.org/tos"_q); }), Ui::Text::RichLangValue); Ui::AddSkip(content); Ui::AddDividerText(content, std::move(text)); } content->resizeToWidth(container->width()); }; using ApiOptions = Api::CreditsTopupOptions; const auto apiCredits = content->lifetime().make_state( _controller->session().user()); apiCredits->request( ) | rpl::start_with_error_done([=](const QString &error) { _controller->showToast(error); }, [=] { fill(apiCredits->options()); }, content->lifetime()); } void Credits::setupContent() { const auto content = Ui::CreateChild(this); setupOptions(content); Ui::ResizeFitChild(this, content); } QPointer Credits::createPinnedToTop( not_null parent) { const auto content = [&]() -> Ui::Premium::TopBarAbstract* { const auto weak = base::make_weak(_controller); const auto clickContextOther = [=] { return QVariant::fromValue(ClickHandlerContext{ .sessionWindow = weak, .botStartAutoSubmit = true, }); }; return Ui::CreateChild( parent.get(), st::creditsPremiumCover, Ui::Premium::TopBarDescriptor{ .clickContextOther = clickContextOther, .title = tr::lng_credits_summary_title(), .about = tr::lng_credits_summary_about( TextWithEntities::Simple), .light = true, .gradientStops = Ui::Premium::CreditsIconGradientStops(), }); }(); _setPaused = [=](bool paused) { content->setPaused(paused); }; _wrap.value( ) | rpl::start_with_next([=](Info::Wrap wrap) { content->setRoundEdges(wrap == Info::Wrap::Layer); }, content->lifetime()); content->setMaximumHeight(st::settingsPremiumTopHeight); content->setMinimumHeight(st::infoLayerTopBarHeight); content->resize(content->width(), content->maximumHeight()); content->additionalHeight( ) | rpl::start_with_next([=](int additionalHeight) { const auto wasMax = (content->height() == content->maximumHeight()); content->setMaximumHeight(st::settingsPremiumTopHeight + additionalHeight); if (wasMax) { content->resize(content->width(), content->maximumHeight()); } }, content->lifetime()); _wrap.value( ) | rpl::start_with_next([=](Info::Wrap wrap) { const auto isLayer = (wrap == Info::Wrap::Layer); _back = base::make_unique_q>( content, object_ptr( content, (isLayer ? st::infoTopBarBack : st::infoLayerTopBarBack)), st::infoTopBarScale); _back->setDuration(0); _back->toggleOn(isLayer ? _backToggles.value() | rpl::type_erased() : rpl::single(true)); _back->entity()->addClickHandler([=] { _showBack.fire({}); }); _back->toggledValue( ) | rpl::start_with_next([=](bool toggled) { const auto &st = isLayer ? st::infoLayerTopBar : st::infoTopBar; content->setTextPosition( toggled ? st.back.width : st.titlePosition.x(), st.titlePosition.y()); }, _back->lifetime()); if (!isLayer) { _close = nullptr; } else { _close = base::make_unique_q( content, st::infoTopBarClose); _close->addClickHandler([=] { _controller->parentController()->hideLayer(); _controller->parentController()->hideSpecialLayer(); }); content->widthValue( ) | rpl::start_with_next([=] { _close->moveToRight(0, 0); }, _close->lifetime()); } }, content->lifetime()); return Ui::MakeWeak(not_null{ content }); } void Credits::showFinished() { _showFinished.fire({}); } } // namespace template <> struct SectionFactory : AbstractSectionFactory { object_ptr create( not_null parent, not_null controller, not_null scroll, rpl::producer containerValue ) const final override { return object_ptr(parent, controller); } bool hasCustomTopBar() const final override { return true; } [[nodiscard]] static const std::shared_ptr &Instance() { static const auto result = std::make_shared(); return result; } }; Type CreditsId() { return Credits::Id(); } } // namespace Settings