diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 6e888b2cf..9ab04c9ad 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1947,6 +1947,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_invite_members#other" = "{count} members, among them:"; "lng_channel_invite_private" = "This channel is private.\nPlease join it to continue viewing its content."; +"lng_channel_invite_subscription_button" = "Subscribe"; +"lng_channel_invite_subscription_title" = "Subscribe to the Channel"; +"lng_channel_invite_subscription_about" = "Do you want to subscribe for {channel} for {price} per month?"; +"lng_channel_invite_subscription_terms" = "By subscribing you agree to the {link}."; + "lng_group_invite_create" = "Create an invite link"; "lng_group_invite_about_new" = "Your previous link will be deactivated and we'll generate a new invite link for you."; "lng_group_invite_copied" = "Invite link copied to clipboard."; diff --git a/Telegram/SourceFiles/api/api_chat_invite.cpp b/Telegram/SourceFiles/api/api_chat_invite.cpp index dd06d2abc..36c58d67f 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.cpp +++ b/Telegram/SourceFiles/api/api_chat_invite.cpp @@ -8,26 +8,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_invite.h" #include "apiwrap.h" -#include "window/window_session_controller.h" +#include "boxes/premium_limits_box.h" +#include "core/application.h" +#include "data/data_channel.h" +#include "data/data_file_origin.h" +#include "data/data_forum.h" +#include "data/data_photo.h" +#include "data/data_photo_media.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "info/channel_statistics/boosts/giveaway/boost_badge.h" #include "info/profile/info_profile_badge.h" #include "lang/lang_keys.h" #include "main/main_session.h" -#include "ui/empty_userpic.h" -#include "ui/painter.h" -#include "core/application.h" -#include "data/data_session.h" -#include "data/data_photo.h" -#include "data/data_photo_media.h" -#include "data/data_channel.h" -#include "data/data_forum.h" -#include "data/data_user.h" -#include "data/data_file_origin.h" #include "ui/boxes/confirm_box.h" +#include "ui/controls/userpic_button.h" +#include "ui/effects/premium_graphics.h" +#include "ui/effects/premium_stars_colored.h" +#include "ui/empty_userpic.h" +#include "ui/layers/generic_box.h" +#include "ui/painter.h" +#include "ui/rect.h" +#include "ui/text/text_utilities.h" #include "ui/toast/toast.h" -#include "boxes/premium_limits_box.h" +#include "ui/vertical_list.h" +#include "window/window_session_controller.h" #include "styles/style_boxes.h" +#include "styles/style_chat_helpers.h" #include "styles/style_info.h" #include "styles/style_layers.h" +#include "styles/style_premium.h" namespace Api { @@ -101,6 +111,188 @@ void SubmitChatInvite( }).send(); } +void ConfirmSubscriptionBox( + not_null box, + not_null session, + const QString &hash, + const MTPDchatInvite *data) { + box->setWidth(st::boxWideWidth); + const auto amount = data->vsubscription_pricing()->data().vamount().v; + const auto formId = data->vsubscription_form_id()->v; + const auto name = qs(data->vtitle()); + const auto maybePhoto = session->data().processPhoto(data->vphoto()); + const auto photo = maybePhoto->isNull() ? nullptr : maybePhoto.get(); + + struct State final { + std::shared_ptr photoMedia; + std::unique_ptr photoEmpty; + + std::optional api; + Ui::RpWidget* saveButton = nullptr; + rpl::variable loading; + }; + const auto state = box->lifetime().make_state(); + + const auto content = box->verticalLayout(); + + Ui::AddSkip(content, st::confirmInvitePhotoTop); + const auto userpicWrap = content->add( + object_ptr>( + content, + object_ptr(content))); + const auto userpic = userpicWrap->entity(); + const auto photoSize = st::confirmInvitePhotoSize; + userpic->resize(Size(photoSize)); + const auto options = Images::Option::RoundCircle; + userpic->paintRequest( + ) | rpl::start_with_next([=, small = Data::PhotoSize::Small] { + auto p = QPainter(userpic); + if (state->photoMedia) { + if (const auto image = state->photoMedia->image(small)) { + p.drawPixmap( + 0, + 0, + image->pix(Size(photoSize), { .options = options })); + } + } else if (state->photoEmpty) { + state->photoEmpty->paintCircle( + p, + 0, + 0, + userpic->width(), + photoSize); + } + }, userpicWrap->lifetime()); + userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents); + if (photo) { + state->photoMedia = photo->createMediaView(); + state->photoMedia->wanted(Data::PhotoSize::Small, Data::FileOrigin()); + if (!state->photoMedia->image(Data::PhotoSize::Small)) { + session->downloaderTaskFinished( + ) | rpl::start_with_next([=] { + userpic->update(); + }, userpicWrap->entity()->lifetime()); + } + } else { + state->photoEmpty = std::make_unique( + Ui::EmptyUserpic::UserpicColor(0), + name); + } + Ui::AddSkip(content); + Ui::AddSkip(content); + + { + const auto widget = Ui::CreateChild(content); + using ColoredMiniStars = Ui::Premium::ColoredMiniStars; + const auto stars = widget->lifetime().make_state( + widget, + false, + Ui::Premium::MiniStars::Type::BiStars); + stars->setColorOverride(Ui::Premium::CreditsIconGradientStops()); + widget->resize( + st::boxWideWidth - photoSize, + photoSize * 2); + content->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + widget->moveToLeft(photoSize / 2, 0); + const auto starsRect = Rect(widget->size()); + stars->setPosition(starsRect.topLeft()); + stars->setSize(starsRect.size()); + widget->lower(); + }, widget->lifetime()); + widget->paintRequest( + ) | rpl::start_with_next([=](const QRect &r) { + auto p = QPainter(widget); + p.fillRect(r, Qt::transparent); + stars->paint(p); + }, widget->lifetime()); + } + + box->addRow( + object_ptr>( + box, + object_ptr( + box, + tr::lng_channel_invite_subscription_title(), + st::inviteLinkSubscribeBoxTitle))); + box->addRow( + object_ptr>( + box, + object_ptr( + box, + tr::lng_channel_invite_subscription_about( + lt_channel, + rpl::single(Ui::Text::Bold(name)), + lt_price, + tr::lng_credits_summary_options_credits( + lt_count, + rpl::single(amount) | tr::to_count(), + Ui::Text::Bold), + Ui::Text::WithEntities), + st::inviteLinkSubscribeBoxAbout))); + Ui::AddSkip(content); + const auto terms = box->addRow( + object_ptr>( + box, + object_ptr( + box, + tr::lng_channel_invite_subscription_terms( + lt_link, + rpl::combine( + tr::lng_paid_react_agree_link(), + tr::lng_group_invite_subscription_about_url() + ) | rpl::map([](const QString &text, const QString &url) { + return Ui::Text::Link(text, url); + }), + Ui::Text::RichLangValue), + st::inviteLinkSubscribeBoxTerms))); + + auto confirmText = tr::lng_channel_invite_subscription_button(); + const auto weak = Ui::MakeWeak(box); + state->saveButton = box->addButton(std::move(confirmText), [=] { + if (state->api) { + return; + } + state->api.emplace(&session->mtp()); + state->loading.force_assign(true); + + const auto buttonWidth = state->saveButton + ? state->saveButton->width() + : 0; + state->api->request( + MTPpayments_SendStarsForm( + MTP_flags(0), + MTP_long(formId), + MTP_inputInvoiceChatInviteSubscription(MTP_string(hash))) + ).done([=](auto result) { + if (weak) { + state->api = std::nullopt; + box->closeBox(); + } + }).fail([=, show = box->uiShow()](const MTP::Error &error) { + const auto id = error.type(); + if (weak) { + state->api = std::nullopt; + } + show->showToast(id); + }).send(); + if (state->saveButton) { + state->saveButton->resizeToWidth(buttonWidth); + } + }); + if (const auto saveButton = state->saveButton) { + using namespace Info::Statistics; + const auto loadingAnimation = InfiniteRadialAnimationWidget( + saveButton, + saveButton->height() / 2, + &st::editStickerSetNameLoading); + AddChildToWidgetCenter(saveButton, loadingAnimation); + loadingAnimation->showOn( + state->loading.value() | rpl::map(rpl::mappers::_1)); + } + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + } // namespace void CheckChatInvite( @@ -125,11 +317,23 @@ void CheckChatInvite( }; result.match([=](const MTPDchatInvite &data) { const auto isGroup = !data.is_broadcast(); - const auto box = strong->show(Box( - session, - data, - invitePeekChannel, - [=] { SubmitChatInvite(weak, session, hash, isGroup); })); + const auto hasPricing = !!data.vsubscription_pricing(); + if (hasPricing && !data.vsubscription_form_id()) { + strong->uiShow()->showToast( + tr::lng_confirm_phone_link_invalid(tr::now)); + return; + } + const auto box = hasPricing + ? strong->show(Box( + ConfirmSubscriptionBox, + session, + hash, + &data)) + : strong->show(Box( + session, + data, + invitePeekChannel, + [=] { SubmitChatInvite(weak, session, hash, isGroup); })); if (invitePeekChannel) { box->boxClosing( ) | rpl::filter([=] { diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index db1cd8b19..bf1920738 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -1011,6 +1011,27 @@ inviteLinkCreditsField: InputField(defaultInputField) { duration: 100; } +inviteLinkSubscribeBoxTitle: FlatLabel(defaultFlatLabel) { + minWidth: 300px; + textFg: windowBoldFg; + align: align(top); + style: TextStyle(defaultTextStyle) { + font: font(17px semibold); + } +} +inviteLinkSubscribeBoxAbout: FlatLabel(defaultFlatLabel) { + minWidth: 300px; + align: align(top); +} +inviteLinkSubscribeBoxTerms: FlatLabel(defaultFlatLabel) { + minWidth: 300px; + align: align(top); + textFg: windowSubTextFg; + style: TextStyle(defaultTextStyle) { + font: font(11px); + } +} + usernamesReorderIcon: icon {{ "stickers_reorder", dialogsMenuIconFg }}; infoAboutGigagroup: FlatLabel(defaultFlatLabel) {