diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index bb98de17a8..5b9292722c 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -155,6 +155,7 @@ CheckoutProcess::CheckoutProcess( panelCancelEdit(); }, _panel->lifetime()); showForm(); + _panel->toggleProgress(true); if (mode == Mode::Payment) { _session->api().passwordState( @@ -180,7 +181,9 @@ not_null CheckoutProcess::panelDelegate() { } void CheckoutProcess::handleFormUpdate(const FormUpdate &update) { - v::match(update, [&](const FormReady &) { + v::match(update, [&](const ToggleProgress &data) { + _panel->toggleProgress(data.shown); + }, [&](const FormReady &) { performInitialSilentValidation(); if (!_initialSilentValidation) { showForm(); diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 23c9a5edce..4e7b407baf 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -170,6 +170,14 @@ void Form::fillInvoiceFromMessage() { } } +void Form::showProgress() { + _updates.fire(ToggleProgress{ true }); +} + +void Form::hideProgress() { + _updates.fire(ToggleProgress{ false }); +} + void Form::loadThumbnail(not_null photo) { Expects(!_thumbnailLoadProcess); @@ -251,29 +259,35 @@ QImage Form::prepareEmptyThumbnail() const { } void Form::requestForm() { + showProgress(); _api.request(MTPpayments_GetPaymentForm( MTP_flags(MTPpayments_GetPaymentForm::Flag::f_theme_params), _peer->input, MTP_int(_msgId), MTP_dataJSON(MTP_bytes(ThemeParams())) )).done([=](const MTPpayments_PaymentForm &result) { + hideProgress(); result.match([&](const auto &data) { processForm(data); }); }).fail([=](const MTP::Error &error) { + hideProgress(); _updates.fire(Error{ Error::Type::Form, error.type() }); }).send(); } void Form::requestReceipt() { + showProgress(); _api.request(MTPpayments_GetPaymentReceipt( _peer->input, MTP_int(_msgId) )).done([=](const MTPpayments_PaymentReceipt &result) { + hideProgress(); result.match([&](const auto &data) { processReceipt(data); }); }).fail([=](const MTP::Error &error) { + hideProgress(); _updates.fire(Error{ Error::Type::Form, error.type() }); }).send(); } @@ -551,6 +565,7 @@ void Form::submit() { } using Flag = MTPpayments_SendPaymentForm::Flag; + showProgress(); _api.request(MTPpayments_SendPaymentForm( MTP_flags((_requestedInformationId.isEmpty() ? Flag(0) @@ -576,12 +591,14 @@ void Form::submit() { MTP_bytes(password))), MTP_long(_invoice.tipsSelected) )).done([=](const MTPpayments_PaymentResult &result) { + hideProgress(); result.match([&](const MTPDpayments_paymentResult &data) { _updates.fire(PaymentFinished{ data.vupdates() }); }, [&](const MTPDpayments_paymentVerificationNeeded &data) { _updates.fire(VerificationNeeded{ qs(data.vurl()) }); }); }).fail([=](const MTP::Error &error) { + hideProgress(); _updates.fire(Error{ Error::Type::Send, error.type() }); }).send(); } @@ -612,6 +629,7 @@ void Form::validateInformation(const Ui::RequestedInformation &information) { if (_validatedInformation == information) { return; } + hideProgress(); _api.request(base::take(_validateRequestId)).cancel(); } _validatedInformation = information; @@ -625,6 +643,7 @@ void Form::validateInformation(const Ui::RequestedInformation &information) { Assert(!_invoice.isEmailRequested || !information.email.isEmpty()); Assert(!_invoice.isPhoneRequested || !information.phone.isEmpty()); + showProgress(); using Flag = MTPpayments_ValidateRequestedInfo::Flag; _validateRequestId = _api.request(MTPpayments_ValidateRequestedInfo( MTP_flags(information.save ? Flag::f_save : Flag(0)), @@ -632,6 +651,7 @@ void Form::validateInformation(const Ui::RequestedInformation &information) { MTP_int(_msgId), Serialize(information) )).done([=](const MTPpayments_ValidatedRequestedInfo &result) { + hideProgress(); _validateRequestId = 0; const auto oldSelectedId = _shippingOptions.selectedId; result.match([&](const MTPDpayments_validatedRequestedInfo &data) { @@ -654,6 +674,7 @@ void Form::validateInformation(const Ui::RequestedInformation &information) { } _updates.fire(ValidateFinished{}); }).fail([=](const MTP::Error &error) { + hideProgress(); _validateRequestId = 0; _updates.fire(Error{ Error::Type::Validate, error.type() }); }).send(); @@ -798,9 +819,11 @@ void Form::validateCard( .addressZip = details.addressZip, .addressCountry = details.addressCountry, }; + showProgress(); _stripe->createTokenWithCard(std::move(card), crl::guard(this, [=]( Stripe::Token token, Stripe::Error error) { + hideProgress(); _stripe = nullptr; if (error) { @@ -846,9 +869,11 @@ void Form::validateCard( .addressZip = details.addressZip, .addressCountry = details.addressCountry, }; + showProgress(); _smartglocal->createTokenWithCard(std::move(card), crl::guard(this, [=]( SmartGlocal::Token token, SmartGlocal::Error error) { + hideProgress(); _smartglocal = nullptr; if (error) { diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index 441b874d17..ffddf78396 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -116,6 +116,9 @@ struct PaymentMethod { Ui::PaymentMethodDetails ui; }; +struct ToggleProgress { + bool shown = true; +}; struct FormReady {}; struct ThumbnailUpdated { QImage thumbnail; @@ -157,6 +160,7 @@ struct Error { }; struct FormUpdate : std::variant< + ToggleProgress, FormReady, ThumbnailUpdated, ValidateFinished, @@ -209,6 +213,8 @@ public: private: void fillInvoiceFromMessage(); + void showProgress(); + void hideProgress(); void loadThumbnail(not_null photo); [[nodiscard]] QImage prepareGoodThumbnail( diff --git a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp index 633fb3cede..df6e0c7423 100644 --- a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp @@ -129,6 +129,10 @@ bool FormSummary::showCriticalError(const TextWithEntities &text) { return true; } +int FormSummary::contentHeight() const { + return _invoice ? _scroll->height() : _layout->height(); +} + void FormSummary::updateThumbnail(const QImage &thumbnail) { _invoice.cover.thumbnail = thumbnail; _thumbnails.fire_copy(thumbnail); diff --git a/Telegram/SourceFiles/payments/ui/payments_form_summary.h b/Telegram/SourceFiles/payments/ui/payments_form_summary.h index 6f3e9f154d..ed7ba7373a 100644 --- a/Telegram/SourceFiles/payments/ui/payments_form_summary.h +++ b/Telegram/SourceFiles/payments/ui/payments_form_summary.h @@ -40,6 +40,7 @@ public: [[nodiscard]] rpl::producer scrollTopValue() const; bool showCriticalError(const TextWithEntities &text); + [[nodiscard]] int contentHeight() const; private: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.cpp b/Telegram/SourceFiles/payments/ui/payments_panel.cpp index 49d8e586a7..ffca1721f5 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_panel.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/single_choice_box.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" +#include "ui/effects/radial_animation.h" #include "lang/lang_keys.h" #include "webview/webview_embed.h" #include "webview/webview_interface.h" @@ -25,6 +26,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_layers.h" namespace Payments::Ui { +namespace { + +constexpr auto kProgressDuration = crl::time(200); +constexpr auto kProgressOpacity = 0.3; + +} // namespace + +struct Panel::Progress { + Progress(QWidget *parent, Fn rect); + + RpWidget widget; + InfiniteRadialAnimation animation; + Animations::Simple shownAnimation; + bool shown = true; + rpl::lifetime geometryLifetime; +}; + +Panel::Progress::Progress(QWidget *parent, Fn rect) +: widget(parent) +, animation( + [=] { if (!anim::Disabled()) widget.update(rect()); }, + st::boxLoadingAnimation) { +} Panel::Panel(not_null delegate) : _delegate(delegate) @@ -45,6 +69,7 @@ Panel::Panel(not_null delegate) Panel::~Panel() { // Destroy _widget before _webview. + _progress = nullptr; _widget = nullptr; } @@ -52,6 +77,139 @@ void Panel::requestActivate() { _widget->showAndActivate(); } +void Panel::toggleProgress(bool shown) { + if (!_progress) { + if (!shown) { + return; + } + _progress = std::make_unique( + _widget.get(), + [=] { return progressRect(); }); + _progress->widget.paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + auto p = QPainter(&_progress->widget); + p.setOpacity( + _progress->shownAnimation.value(_progress->shown ? 1. : 0.)); + auto thickness = _webviewBottom + ? st::boxLoadingAnimation.thickness + : (st::boxLoadingAnimation.thickness * 2); + if (progressWithBackground()) { + auto color = st::windowBg->c; + color.setAlphaF(kProgressOpacity); + p.fillRect(clip, color); + } + const auto rect = progressRect().marginsRemoved( + { thickness, thickness, thickness, thickness }); + InfiniteRadialAnimation::Draw( + p, + _progress->animation.computeState(), + rect.topLeft(), + rect.size() - QSize(), + _progress->widget.width(), + st::boxLoadingAnimation.color, + thickness); + }, _progress->widget.lifetime()); + _progress->widget.show(); + _progress->animation.start(); + } else if (_progress->shown == shown) { + return; + } + const auto callback = [=] { + if (!_progress->shownAnimation.animating() && !_progress->shown) { + _progress = nullptr; + } else { + _progress->widget.update(); + } + }; + _progress->shown = shown; + _progress->shownAnimation.start( + callback, + shown ? 0. : 1., + shown ? 1. : 0., + kProgressDuration); + if (shown) { + setupProgressGeometry(); + } +} + +bool Panel::progressWithBackground() const { + return (_progress->widget.width() == _widget->innerGeometry().width()); +} + +QRect Panel::progressRect() const { + const auto rect = _progress->widget.rect(); + if (!progressWithBackground()) { + return rect; + } + const auto size = st::defaultBoxButton.height; + return QRect( + rect.x() + (rect.width() - size) / 2, + rect.y() + (rect.height() - size) / 2, + size, + size); +} + +void Panel::setupProgressGeometry() { + if (!_progress || !_progress->shown) { + return; + } + _progress->geometryLifetime.destroy(); + if (_webviewBottom) { + _webviewBottom->geometryValue( + ) | rpl::start_with_next([=](QRect bottom) { + const auto height = bottom.height(); + const auto size = height + - st::layerBox.buttonPadding.top() + - st::layerBox.buttonPadding.bottom(); + const auto inner = _widget->innerGeometry(); + const auto right = inner.x() + inner.width(); + const auto top = inner.y() + inner.height() - height; + // This doesn't work, because first we get the correct bottom + // geometry and after that we get the previous event (which + // triggered the 'fire' of correct geometry before getting here). + //const auto right = bottom.x() + bottom.width(); + //const auto top = bottom.y(); + _progress->widget.setGeometry( + right - size - st::layerBox.buttonPadding.right(), + top + st::layerBox.buttonPadding.top(), + size, + size); + }, _progress->geometryLifetime); + } else if (_weakFormSummary) { + _weakFormSummary->sizeValue( + ) | rpl::start_with_next([=](QSize form) { + const auto full = _widget->innerGeometry(); + const auto size = st::defaultBoxButton.height; + const auto inner = _weakFormSummary->contentHeight(); + const auto left = full.height() - inner; + if (left >= 2 * size) { + _progress->widget.setGeometry( + full.x() + (full.width() - size) / 2, + full.y() + inner + (left - size) / 2, + size, + size); + } else { + _progress->widget.setGeometry(full); + } + }, _progress->geometryLifetime); + } else if (_weakEditInformation) { + _weakEditInformation->geometryValue( + ) | rpl::start_with_next([=] { + _progress->widget.setGeometry(_widget->innerGeometry()); + }, _progress->geometryLifetime); + } else if (_weakEditCard) { + _weakEditCard->geometryValue( + ) | rpl::start_with_next([=] { + _progress->widget.setGeometry(_widget->innerGeometry()); + }, _progress->geometryLifetime); + } + _progress->widget.show(); + _progress->widget.raise(); + if (_progress->shown) { + _progress->widget.setFocus(); + } +} + void Panel::showForm( const Invoice &invoice, const RequestedInformation ¤t, @@ -83,6 +241,7 @@ void Panel::showForm( _widget->showInner(std::move(form)); _widget->setBackAllowed(false); _formScrollTop = _weakFormSummary->scrollTopValue(); + setupProgressGeometry(); } void Panel::updateFormThumbnail(const QImage &thumbnail) { @@ -106,6 +265,7 @@ void Panel::showEditInformation( _widget->showInner(std::move(edit)); _widget->setBackAllowed(true); _weakEditInformation->setFocusFast(field); + setupProgressGeometry(); } void Panel::showInformationError( @@ -278,6 +438,7 @@ bool Panel::showWebview( if (!_webview && !createWebview()) { return false; } + toggleProgress(true); _widget->destroyLayer(); _webview->navigate(url); _widget->setBackAllowed(allowBack); @@ -347,8 +508,15 @@ bool Panel::createWebview() { _delegate->panelWebviewMessage(message, save); }); - raw->setNavigationHandler([=](const QString &uri) { - return _delegate->panelWebviewNavigationAttempt(uri); + raw->setNavigationStartHandler([=](const QString &uri) { + if (!_delegate->panelWebviewNavigationAttempt(uri)) { + return false; + } + toggleProgress(true); + return true; + }); + raw->setNavigationDoneHandler([=](bool success) { + toggleProgress(false); }); raw->init(R"( @@ -361,6 +529,9 @@ postEvent: function(eventType, eventData) { };)"); _widget->showInner(std::move(container)); + + setupProgressGeometry(); + return true; } @@ -452,6 +623,7 @@ void Panel::showEditCard( _widget->showInner(std::move(edit)); _widget->setBackAllowed(true); _weakEditCard->setFocusFast(field); + setupProgressGeometry(); } void Panel::showCardError( @@ -494,6 +666,7 @@ void Panel::showToast(const TextWithEntities &text) { } void Panel::showCriticalError(const TextWithEntities &text) { + toggleProgress(false); if (!_weakFormSummary || !_weakFormSummary->showCriticalError(text)) { auto error = base::make_unique_q>( _widget.get(), diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.h b/Telegram/SourceFiles/payments/ui/payments_panel.h index 6f8defd089..d711d75bf0 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel.h @@ -43,6 +43,7 @@ public: ~Panel(); void requestActivate(); + void toggleProgress(bool shown); void showForm( const Invoice &invoice, @@ -86,16 +87,23 @@ public: [[nodiscard]] rpl::lifetime &lifetime(); private: + struct Progress; + bool createWebview(); void showWebviewError( const QString &text, const Webview::Available &information); void setTitle(rpl::producer title); + [[nodiscard]] bool progressWithBackground() const; + [[nodiscard]] QRect progressRect() const; + void setupProgressGeometry(); + const not_null _delegate; std::unique_ptr _widget; std::unique_ptr _webview; std::unique_ptr _webviewBottom; + std::unique_ptr _progress; QPointer _saveWebviewInformation; QPointer _weakFormSummary; rpl::variable _formScrollTop; diff --git a/Telegram/lib_webview b/Telegram/lib_webview index c1548226d4..fa6828443c 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit c1548226d49db23f68bbf35f34cc820171aed65c +Subproject commit fa6828443c71932de74ec2a0ffa5f3e8d3bc894c