From b9b622669233c2fbcca4219bb33859a4d9980e40 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 17 Dec 2023 09:49:26 +0300 Subject: [PATCH] Added initial ability to gift premium to contacts from settings. --- Telegram/Resources/langs/lang.strings | 5 + Telegram/SourceFiles/api/api_premium.cpp | 9 +- .../SourceFiles/boxes/gift_premium_box.cpp | 222 +++++++++++++++++- Telegram/SourceFiles/boxes/gift_premium_box.h | 3 + .../SourceFiles/settings/settings_main.cpp | 17 ++ .../window/window_session_controller.cpp | 4 + .../window/window_session_controller.h | 1 + 7 files changed, 254 insertions(+), 7 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2d613ee7d..1fb6041dc 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -813,6 +813,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_manage_enabled_dictionary" = "Dictionary is enabled"; "lng_settings_manage_remove_dictionary" = "Remove Dictionary"; +"lng_settings_gift_premium" = "Premium Gifting"; +"lng_settings_gift_premium_users_confirm" = "Proceed"; +"lng_settings_gift_premium_users_error#one" = "You can select maximum {count} user."; +"lng_settings_gift_premium_users_error#other" = "You can select maximum {count} users."; + "lng_backgrounds_header" = "Choose Wallpaper"; "lng_theme_sure_keep" = "Keep this theme?"; "lng_theme_reverting#one" = "Reverting to the old theme in {count} second."; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 37307fe60..088c3cf87 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -342,15 +342,12 @@ PremiumGiftCodeOptions::PremiumGiftCodeOptions(not_null peer) rpl::producer PremiumGiftCodeOptions::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); - const auto channel = _peer->asChannel(); - if (!channel) { - return lifetime; - } using TLOption = MTPPremiumGiftCodeOption; _api.request(MTPpayments_GetPremiumGiftCodeOptions( - MTP_flags( - MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer), + MTP_flags(_peer->isChannel() + ? MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer + : MTPpayments_GetPremiumGiftCodeOptions::Flag(0)), _peer->input )).done([=](const MTPVector &result) { auto tlMapOptions = base::flat_map>(); diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 37ce14e12..72633a441 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/unixtime.h" #include "base/weak_ptr.h" +#include "boxes/peer_list_controllers.h" // ContactsBoxController. #include "boxes/peers/prepare_short_info_box.h" #include "data/data_boosts.h" #include "data/data_changes.h" @@ -24,6 +25,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "main/main_session.h" #include "mainwidget.h" +#include "payments/payments_checkout_process.h" +#include "payments/payments_form.h" #include "settings/settings_premium.h" #include "ui/basic_click_handlers.h" // UrlClickHandler::Open. #include "ui/boxes/boost_box.h" // StartFireworks. @@ -211,7 +214,7 @@ void GiftBox( auto raw = Settings::CreateSubscribeButton({ controller, box, - [] { return QString("gift"); }, + [] { return u"gift"_q; }, state->buttonText.events(), Ui::Premium::GiftGradientStops(), [=] { @@ -239,6 +242,117 @@ void GiftBox( }, box->lifetime()); } +void GiftsBox( + not_null box, + not_null controller, + std::vector> users, + not_null api) { + Expects(!users.empty()); + + const auto boxWidth = st::boxWideWidth; + box->setWidth(boxWidth); + box->setNoContentMargin(true); + const auto buttonsParent = box->verticalLayout().get(); + const auto session = &users.front()->session(); + + struct State { + rpl::event_stream buttonText; + }; + const auto state = box->lifetime().make_state(); + + const auto userpicPadding = st::premiumGiftUserpicPadding; + const auto top = box->addRow(object_ptr( + buttonsParent, + userpicPadding.top() + + userpicPadding.bottom() + + st::defaultUserpicButton.size.height())); + + // Header. + const auto &padding = st::premiumGiftAboutPadding; + const auto available = boxWidth - padding.left() - padding.right(); + + const auto close = Ui::CreateChild( + buttonsParent, + st::infoTopBarClose); + close->setClickedCallback([=] { box->closeBox(); }); + + buttonsParent->widthValue( + ) | rpl::start_with_next([=](int width) { + close->moveToRight(0, 0, width); + }, close->lifetime()); + + // List. + const auto options = api->options(users.size()); + const auto group = std::make_shared(); + const auto groupValueChangedCallback = [=](int value) { + Expects(value < options.size() && value >= 0); + auto text = tr::lng_premium_gift_button( + tr::now, + lt_cost, + options[value].costTotal); + state->buttonText.fire(std::move(text)); + }; + group->setChangedCallback(groupValueChangedCallback); + Ui::Premium::AddGiftOptions( + buttonsParent, + group, + options, + st::premiumGiftOption); + + // Footer. + auto terms = object_ptr( + box, + tr::lng_premium_gift_terms( + lt_link, + tr::lng_premium_gift_terms_link( + ) | rpl::map([=](const QString &t) { + return Ui::Text::Link(t, 1); + }), + Ui::Text::WithEntities), + st::premiumGiftTerms); + terms->setLink(1, std::make_shared([=] { + box->closeBox(); + Settings::ShowPremium(session, QString()); + })); + terms->resizeToWidth(available); + box->addRow( + object_ptr>(box, std::move(terms)), + st::premiumGiftTermsPadding); + + // Button. + const auto &stButton = st::premiumGiftBox; + box->setStyle(stButton); + auto raw = Settings::CreateSubscribeButton({ + controller, + box, + [] { return u"gift"_q; }, + state->buttonText.events(), + Ui::Premium::GiftGradientStops(), + }); + raw->setClickedCallback([=] { + auto invoice = api->invoice( + users.size(), + api->monthsFromPreset(group->value())); + invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{ users }; + + const auto done = [=](Payments::CheckoutResult result) { + box->uiShow()->showToast(tr::lng_share_done(tr::now)); + }; + + Payments::CheckoutProcess::Start(std::move(invoice), done); + }); + auto button = object_ptr::fromRaw(raw); + button->resizeToWidth(boxWidth + - stButton.buttonPadding.left() + - stButton.buttonPadding.right()); + box->setShowFinishedCallback([raw = button.data()]{ + raw->startGlareAnimation(); + }); + box->addButton(std::move(button)); + + groupValueChangedCallback(0); +} + [[nodiscard]] Data::GiftCodeLink MakeGiftCodeLink( not_null session, const QString &slug) { @@ -441,6 +555,112 @@ void GiftPremiumValidator::cancel() { _requestId = 0; } +void GiftPremiumValidator::showChoosePeerBox() { + if (_manyGiftsLifetime) { + return; + } + using namespace Api; + const auto api = _manyGiftsLifetime.make_state( + _controller->session().user()); + const auto show = _controller->uiShow(); + api->request( + ) | rpl::start_with_error_done([=](const QString &error) { + show->showToast(error); + }, [=] { + const auto maxAmount = *ranges::max_element(api->availablePresets()); + + class Controller final : public ContactsBoxController { + public: + Controller( + not_null session, + Fn checkErrorCallback) + : ContactsBoxController(session) + , _checkErrorCallback(std::move(checkErrorCallback)) { + } + + protected: + std::unique_ptr createRow( + not_null user) override { + return !user->isSelf() + ? ContactsBoxController::createRow(user) + : nullptr; + } + + void rowClicked(not_null row) override { + const auto checked = !row->checked(); + if (checked + && _checkErrorCallback + && _checkErrorCallback( + delegate()->peerListSelectedRowsCount())) { + return; + } + delegate()->peerListSetRowChecked(row, checked); + } + + private: + const Fn _checkErrorCallback; + + }; + auto initBox = [=](not_null peersBox) { + const auto ignoreClose = peersBox->lifetime().make_state(0); + + auto process = [=] { + const auto selected = peersBox->collectSelectedRows(); + const auto users = ranges::views::all( + selected + ) | ranges::views::transform([](not_null p) { + return p->asUser(); + }) | ranges::views::filter([](UserData *u) -> bool { + return u; + }) | ranges::to>>(); + if (!users.empty()) { + const auto giftBox = show->showBox( + Box(GiftsBox, _controller, users, api)); + giftBox->boxClosing( + ) | rpl::start_with_next([=] { + _manyGiftsLifetime.destroy(); + }, giftBox->lifetime()); + } + (*ignoreClose) = true; + peersBox->closeBox(); + }; + + peersBox->setTitle(tr::lng_premium_gift_title()); + peersBox->addButton( + tr::lng_settings_gift_premium_users_confirm(), + std::move(process)); + peersBox->addButton(tr::lng_cancel(), [=] { + peersBox->closeBox(); + }); + peersBox->boxClosing( + ) | rpl::start_with_next([=] { + if (!(*ignoreClose)) { + _manyGiftsLifetime.destroy(); + } + }, peersBox->lifetime()); + }; + + auto listController = std::make_unique( + &_controller->session(), + [=](int count) { + if (count <= maxAmount) { + return false; + } + show->showToast(tr::lng_settings_gift_premium_users_error( + tr::now, + lt_count, + maxAmount)); + return true; + }); + show->showBox( + Box( + std::move(listController), + std::move(initBox)), + Ui::LayerOption::KeepOther); + + }, _manyGiftsLifetime); +} + void GiftPremiumValidator::showBox(not_null user) { if (_requestId) { return; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h index c9fdc1b5f..5a0bfb659 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.h +++ b/Telegram/SourceFiles/boxes/gift_premium_box.h @@ -34,6 +34,7 @@ public: GiftPremiumValidator(not_null controller); void showBox(not_null user); + void showChoosePeerBox(); void cancel(); private: @@ -42,6 +43,8 @@ private: mtpRequestId _requestId = 0; + rpl::lifetime _manyGiftsLifetime; + }; [[nodiscard]] rpl::producer GiftDurationValue(int months); diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index 5e440a581..b6aa84b19 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" +#include "ui/new_badges.h" #include "ui/vertical_list.h" #include "info/profile/info_profile_badge.h" #include "info/profile/info_profile_emoji_status_panel.h" @@ -60,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/file_utilities.h" #include "core/application.h" #include "base/call_delayed.h" +#include "base/unixtime.h" #include "base/platform/base_platform_info.h" #include "styles/style_settings.h" #include "styles/style_boxes.h" @@ -417,6 +419,21 @@ void SetupPremium( controller->setPremiumRef("settings"); showOther(PremiumId()); }); + { + const auto button = AddButtonWithIcon( + container, + tr::lng_settings_gift_premium(), + st::settingsButton, + { .icon = &st::menuIconGiftPremium } + ); + button->addClickHandler([=] { + controller->showGiftPremiumsBox(); + }); + constexpr auto kNewExpiresAt = int(1735689600); + if (base::unixtime::now() < kNewExpiresAt) { + Ui::NewBadge::AddToRight(button); + } + } Ui::AddSkip(container); } diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 90ff70a2f..7e12d1945 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1216,6 +1216,10 @@ void SessionController::showGiftPremiumBox(UserData *user) { } } +void SessionController::showGiftPremiumsBox() { + _giftPremiumValidator.showChoosePeerBox(); +} + void SessionController::init() { if (session().supportMode()) { initSupportMode(); diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 33b3c44a5..885eb812e 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -380,6 +380,7 @@ public: void showEditPeerBox(PeerData *peer); void showGiftPremiumBox(UserData *user); + void showGiftPremiumsBox(); void enableGifPauseReason(GifPauseReason reason); void disableGifPauseReason(GifPauseReason reason);