Improve checkout main page design.

This commit is contained in:
John Preston 2021-03-26 17:05:31 +04:00
parent 56031a6402
commit fafea73ea7
28 changed files with 448 additions and 140 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 884 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 711 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1865,24 +1865,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_payments_pay_amount" = "Pay {amount}"; "lng_payments_pay_amount" = "Pay {amount}";
"lng_payments_payment_method" = "Payment Method"; "lng_payments_payment_method" = "Payment Method";
"lng_payments_new_card" = "New Card..."; "lng_payments_new_card" = "New Card...";
"lng_payments_payment_method_ph" = "Enter your card details"; "lng_payments_shipping_address" = "Shipping Address";
"lng_payments_shipping_address" = "Shipping Information"; "lng_payments_receiver_information" = "Receiver";
"lng_payments_shipping_address_ph" = "Enter your shipping information"; "lng_payments_address_street1" = "Address 1";
"lng_payments_address_street2" = "Address 2";
"lng_payments_address_city" = "City";
"lng_payments_address_state" = "State";
"lng_payments_address_country" = "Country";
"lng_payments_address_postcode" = "Postcode";
"lng_payments_shipping_method" = "Shipping Method"; "lng_payments_shipping_method" = "Shipping Method";
"lng_payments_shipping_method_ph" = "Choose your shipping method";
"lng_payments_info_name" = "Name"; "lng_payments_info_name" = "Name";
"lng_payments_info_name_ph" = "Enter your name";
"lng_payments_info_email" = "Email"; "lng_payments_info_email" = "Email";
"lng_payments_info_email_ph" = "Enter your email";
"lng_payments_info_phone" = "Phone"; "lng_payments_info_phone" = "Phone";
"lng_payments_info_phone_ph" = "Enter your phone number";
"lng_payments_shipping_address_title" = "Shipping Address"; "lng_payments_shipping_address_title" = "Shipping Address";
"lng_payments_save_shipping_about" = "You can save your shipping information for future use."; "lng_payments_save_shipping_about" = "You can save your shipping information for future use.";
"lng_payments_payment_card" = "Payment Card"; "lng_payments_card_title" = "New Card";
"lng_payments_cardholder_title" = "Cardholder"; "lng_payments_card_number" = "Card Number";
"lng_payments_cardholder_about" = "Cardholder Name"; "lng_payments_card_holder" = "Cardholder name";
"lng_payments_billing_address" = "Billing Address"; "lng_payments_billing_address" = "Billing Information";
"lng_payments_zip_code" = "Zip Code"; "lng_payments_billing_country" = "Country";
"lng_payments_billing_zip_code" = "Zip Code";
"lng_payments_save_payment_about" = "You can save your payment information for future use."; "lng_payments_save_payment_about" = "You can save your payment information for future use.";
"lng_payments_save_information" = "Save Information"; "lng_payments_save_information" = "Save Information";

View File

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/storage_domain.h" #include "storage/storage_domain.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history.h" #include "history/history.h"
#include "data/data_user.h" // UserData::isBot.
#include "core/local_url_handlers.h" // TryConvertUrlToLocal. #include "core/local_url_handlers.h" // TryConvertUrlToLocal.
#include "core/file_utilities.h" // File::OpenUrl. #include "core/file_utilities.h" // File::OpenUrl.
#include "apiwrap.h" #include "apiwrap.h"
@ -105,6 +106,8 @@ void CheckoutProcess::handleFormUpdate(const FormUpdate &update) {
if (!_initialSilentValidation) { if (!_initialSilentValidation) {
showForm(); showForm();
} }
}, [&](const ThumbnailUpdated &data) {
_panel->updateFormThumbnail(data.thumbnail);
}, [&](const ValidateFinished &) { }, [&](const ValidateFinished &) {
if (_initialSilentValidation) { if (_initialSilentValidation) {
_initialSilentValidation = false; _initialSilentValidation = false;
@ -114,16 +117,16 @@ void CheckoutProcess::handleFormUpdate(const FormUpdate &update) {
_submitState = SubmitState::Validated; _submitState = SubmitState::Validated;
panelSubmit(); panelSubmit();
} }
}, [&](const PaymentMethodUpdate&) { }, [&](const PaymentMethodUpdate &) {
showForm(); showForm();
}, [&](const VerificationNeeded &info) { }, [&](const VerificationNeeded &data) {
if (!_panel->showWebview(info.url, false)) { if (!_panel->showWebview(data.url, false)) {
File::OpenUrl(info.url); File::OpenUrl(data.url);
panelCloseSure(); panelCloseSure();
} }
}, [&](const PaymentFinished &result) { }, [&](const PaymentFinished &data) {
const auto weak = base::make_weak(this); const auto weak = base::make_weak(this);
_session->api().applyUpdates(result.updates); _session->api().applyUpdates(data.updates);
if (weak) { if (weak) {
panelCloseSure(); panelCloseSure();
} }

View File

@ -9,10 +9,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "apiwrap.h" #include "data/data_media_types.h"
#include "data/data_user.h"
#include "data/data_photo.h"
#include "data/data_photo_media.h"
#include "data/data_file_origin.h"
#include "history/history_item.h"
#include "stripe/stripe_api_client.h" #include "stripe/stripe_api_client.h"
#include "stripe/stripe_error.h" #include "stripe/stripe_error.h"
#include "stripe/stripe_token.h" #include "stripe/stripe_token.h"
#include "ui/image/image.h"
#include "apiwrap.h"
#include "styles/style_payments.h" // paymentsThumbnailSize.
#include <QtCore/QJsonDocument> #include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject> #include <QtCore/QJsonObject>
@ -82,15 +90,110 @@ namespace {
Form::Form(not_null<Main::Session*> session, FullMsgId itemId) Form::Form(not_null<Main::Session*> session, FullMsgId itemId)
: _session(session) : _session(session)
, _api(&_session->mtp()) , _api(&_session->mtp())
, _msgId(itemId.msg) { , _msgId(itemId) {
fillInvoiceFromMessage();
requestForm(); requestForm();
} }
Form::~Form() = default; Form::~Form() = default;
void Form::fillInvoiceFromMessage() {
if (const auto item = _session->data().message(_msgId)) {
if (const auto media = item->media()) {
if (const auto invoice = media->invoice()) {
_invoice.cover = Ui::Cover{
.title = invoice->title,
.description = invoice->description,
};
if (const auto photo = invoice->photo) {
loadThumbnail(photo);
}
}
}
}
}
void Form::loadThumbnail(not_null<PhotoData*> photo) {
Expects(!_thumbnailLoadProcess);
auto view = photo->createMediaView();
if (auto good = prepareGoodThumbnail(view); !good.isNull()) {
_invoice.cover.thumbnail = std::move(good);
return;
}
_thumbnailLoadProcess = std::make_unique<ThumbnailLoadProcess>();
if (auto blurred = prepareBlurredThumbnail(view); !blurred.isNull()) {
_invoice.cover.thumbnail = std::move(blurred);
_thumbnailLoadProcess->blurredSet = true;
} else {
_invoice.cover.thumbnail = prepareEmptyThumbnail();
}
_thumbnailLoadProcess->view = std::move(view);
photo->load(Data::PhotoSize::Thumbnail, _msgId);
_session->downloaderTaskFinished(
) | rpl::start_with_next([=] {
const auto &view = _thumbnailLoadProcess->view;
if (auto good = prepareGoodThumbnail(view); !good.isNull()) {
_invoice.cover.thumbnail = std::move(good);
_thumbnailLoadProcess = nullptr;
} else if (_thumbnailLoadProcess->blurredSet) {
return;
} else if (auto blurred = prepareBlurredThumbnail(view)
; !blurred.isNull()) {
_invoice.cover.thumbnail = std::move(blurred);
_thumbnailLoadProcess->blurredSet = true;
} else {
return;
}
_updates.fire(ThumbnailUpdated{ _invoice.cover.thumbnail });
}, _thumbnailLoadProcess->lifetime);
}
QImage Form::prepareGoodThumbnail(
const std::shared_ptr<Data::PhotoMedia> &view) const {
using Size = Data::PhotoSize;
if (const auto large = view->image(Size::Large)) {
return prepareThumbnail(large);
} else if (const auto thumbnail = view->image(Size::Thumbnail)) {
return prepareThumbnail(thumbnail);
}
return QImage();
}
QImage Form::prepareBlurredThumbnail(
const std::shared_ptr<Data::PhotoMedia> &view) const {
if (const auto small = view->image(Data::PhotoSize::Small)) {
return prepareThumbnail(small, true);
} else if (const auto blurred = view->thumbnailInline()) {
return prepareThumbnail(blurred, true);
}
return QImage();
}
QImage Form::prepareThumbnail(
not_null<const Image*> image,
bool blurred) const {
auto result = image->original().scaled(
st::paymentsThumbnailSize * cIntRetinaFactor(),
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
Images::prepareRound(result, ImageRoundRadius::Large);
result.setDevicePixelRatio(cRetinaFactor());
return result;
}
QImage Form::prepareEmptyThumbnail() const {
auto result = QImage(
st::paymentsThumbnailSize * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(cRetinaFactor());
result.fill(Qt::transparent);
return result;
}
void Form::requestForm() { void Form::requestForm() {
_api.request(MTPpayments_GetPaymentForm( _api.request(MTPpayments_GetPaymentForm(
MTP_int(_msgId) MTP_int(_msgId.msg)
)).done([=](const MTPpayments_PaymentForm &result) { )).done([=](const MTPpayments_PaymentForm &result) {
result.match([&](const auto &data) { result.match([&](const auto &data) {
processForm(data); processForm(data);
@ -123,6 +226,8 @@ void Form::processForm(const MTPDpayments_paymentForm &data) {
void Form::processInvoice(const MTPDinvoice &data) { void Form::processInvoice(const MTPDinvoice &data) {
_invoice = Ui::Invoice{ _invoice = Ui::Invoice{
.cover = std::move(_invoice.cover),
.prices = ParsePrices(data.vprices()), .prices = ParsePrices(data.vprices()),
.currency = qs(data.vcurrency()), .currency = qs(data.vcurrency()),
@ -154,6 +259,11 @@ void Form::processDetails(const MTPDpayments_paymentForm &data) {
.canSaveCredentials = data.is_can_save_credentials(), .canSaveCredentials = data.is_can_save_credentials(),
.passwordMissing = data.is_password_missing(), .passwordMissing = data.is_password_missing(),
}; };
if (_details.botId) {
if (const auto bot = _session->data().userLoaded(_details.botId)) {
_invoice.cover.seller = bot->name;
}
}
} }
void Form::processSavedInformation(const MTPDpaymentRequestedInfo &data) { void Form::processSavedInformation(const MTPDpaymentRequestedInfo &data) {
@ -240,7 +350,7 @@ void Form::submit() {
| (_shippingOptions.selectedId.isEmpty() | (_shippingOptions.selectedId.isEmpty()
? Flag(0) ? Flag(0)
: Flag::f_shipping_option_id)), : Flag::f_shipping_option_id)),
MTP_int(_msgId), MTP_int(_msgId.msg),
MTP_string(_requestedInformationId), MTP_string(_requestedInformationId),
MTP_string(_shippingOptions.selectedId), MTP_string(_shippingOptions.selectedId),
MTP_inputPaymentCredentials( MTP_inputPaymentCredentials(
@ -267,7 +377,7 @@ void Form::validateInformation(const Ui::RequestedInformation &information) {
_validatedInformation = information; _validatedInformation = information;
_validateRequestId = _api.request(MTPpayments_ValidateRequestedInfo( _validateRequestId = _api.request(MTPpayments_ValidateRequestedInfo(
MTP_flags(0), // #TODO payments save information MTP_flags(0), // #TODO payments save information
MTP_int(_msgId), MTP_int(_msgId.msg),
Serialize(information) Serialize(information)
)).done([=](const MTPpayments_ValidatedRequestedInfo &result) { )).done([=](const MTPpayments_ValidatedRequestedInfo &result) {
_validateRequestId = 0; _validateRequestId = 0;

View File

@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/weak_ptr.h" #include "base/weak_ptr.h"
#include "mtproto/sender.h" #include "mtproto/sender.h"
class Image;
namespace Stripe { namespace Stripe {
class APIClient; class APIClient;
} // namespace Stripe } // namespace Stripe
@ -19,6 +21,10 @@ namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
namespace Data {
class PhotoMedia;
} // namespace Data
namespace Payments { namespace Payments {
struct FormDetails { struct FormDetails {
@ -38,6 +44,12 @@ struct FormDetails {
} }
}; };
struct ThumbnailLoadProcess {
std::shared_ptr<Data::PhotoMedia> view;
bool blurredSet = false;
rpl::lifetime lifetime;
};
struct SavedCredentials { struct SavedCredentials {
QString id; QString id;
QString title; QString title;
@ -88,6 +100,9 @@ struct PaymentMethod {
}; };
struct FormReady {}; struct FormReady {};
struct ThumbnailUpdated {
QImage thumbnail;
};
struct ValidateFinished {}; struct ValidateFinished {};
struct PaymentMethodUpdate {}; struct PaymentMethodUpdate {};
struct VerificationNeeded { struct VerificationNeeded {
@ -109,6 +124,7 @@ struct Error {
struct FormUpdate : std::variant< struct FormUpdate : std::variant<
FormReady, FormReady,
ThumbnailUpdated,
ValidateFinished, ValidateFinished,
PaymentMethodUpdate, PaymentMethodUpdate,
VerificationNeeded, VerificationNeeded,
@ -149,6 +165,18 @@ public:
void submit(); void submit();
private: private:
void fillInvoiceFromMessage();
void loadThumbnail(not_null<PhotoData*> photo);
[[nodiscard]] QImage prepareGoodThumbnail(
const std::shared_ptr<Data::PhotoMedia> &view) const;
[[nodiscard]] QImage prepareBlurredThumbnail(
const std::shared_ptr<Data::PhotoMedia> &view) const;
[[nodiscard]] QImage prepareThumbnail(
not_null<const Image*> image,
bool blurred = false) const;
[[nodiscard]] QImage prepareEmptyThumbnail() const;
void requestForm(); void requestForm();
void processForm(const MTPDpayments_paymentForm &data); void processForm(const MTPDpayments_paymentForm &data);
void processInvoice(const MTPDinvoice &data); void processInvoice(const MTPDinvoice &data);
@ -167,9 +195,10 @@ private:
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
MTP::Sender _api; MTP::Sender _api;
MsgId _msgId = 0; FullMsgId _msgId;
Ui::Invoice _invoice; Ui::Invoice _invoice;
std::unique_ptr<ThumbnailLoadProcess> _thumbnailLoadProcess;
FormDetails _details; FormDetails _details;
Ui::RequestedInformation _savedInformation; Ui::RequestedInformation _savedInformation;
PaymentMethod _paymentMethod; PaymentMethod _paymentMethod;

View File

@ -7,12 +7,52 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
using "ui/basic.style"; using "ui/basic.style";
using "passport/passport.style"; using "info/info.style";
paymentsFormPricePadding: margins(22px, 7px, 22px, 6px); paymentsPanelSubmit: RoundButton(defaultActiveButton) {
paymentsPanelSubmit: RoundButton(passportPasswordSubmit) {
width: 0px; width: 0px;
height: 49px; height: 49px;
padding: margins(0px, -3px, 0px, 0px); padding: margins(0px, -3px, 0px, 0px);
textTop: 16px; textTop: 16px;
} }
paymentsCoverPadding: margins(26px, 0px, 26px, 13px);
paymentsDescription: FlatLabel(defaultFlatLabel) {
minWidth: 160px;
textFg: windowFg;
}
paymentsTitle: FlatLabel(paymentsDescription) {
style: semiboldTextStyle;
}
paymentsSeller: FlatLabel(paymentsDescription) {
textFg: windowSubTextFg;
}
paymentsPriceLabel: paymentsDescription;
paymentsPriceAmount: defaultFlatLabel;
paymentsFullPriceLabel: paymentsTitle;
paymentsFullPriceAmount: FlatLabel(defaultFlatLabel) {
style: semiboldTextStyle;
}
paymentsTitleTop: 0px;
paymentsDescriptionTop: 3px;
paymentsSellerTop: 4px;
paymentsThumbnailSize: size(80px, 80px);
paymentsThumbnailSkip: 18px;
paymentsPricesTopSkip: 12px;
paymentsPricesBottomSkip: 13px;
paymentsPricePadding: margins(28px, 6px, 28px, 5px);
paymentsSectionsTopSkip: 11px;
paymentsSectionButton: SettingsButton(infoProfileButton) {
padding: margins(68px, 11px, 14px, 9px);
}
paymentsIconPaymentMethod: icon {{ "payments/payment_card", menuIconFg }};
paymentsIconShippingAddress: icon {{ "payments/payment_address", menuIconFg }};
paymentsIconName: icon {{ "payments/payment_name", menuIconFg }};
paymentsIconEmail: icon {{ "payments/payment_email", menuIconFg }};
paymentsIconPhone: icon {{ "payments/payment_phone", menuIconFg }};
paymentsIconShippingMethod: icon {{ "payments/payment_shipping", menuIconFg }};

View File

@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/ui/payments_form_summary.h" #include "payments/ui/payments_form_summary.h"
#include "payments/ui/payments_panel_delegate.h" #include "payments/ui/payments_panel_delegate.h"
#include "passport/ui/passport_form_row.h" #include "settings/settings_common.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
@ -22,7 +22,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Payments::Ui { namespace Payments::Ui {
using namespace ::Ui; using namespace ::Ui;
using namespace Passport::Ui;
class PanelDelegate; class PanelDelegate;
@ -45,16 +44,24 @@ FormSummary::FormSummary(
this, this,
tr::lng_payments_pay_amount( tr::lng_payments_pay_amount(
lt_amount, lt_amount,
rpl::single(computeTotalAmount())), rpl::single(formatAmount(computeTotalAmount()))),
st::paymentsPanelSubmit) { st::paymentsPanelSubmit) {
setupControls(); setupControls();
} }
QString FormSummary::computeAmount(int64 amount) const { void FormSummary::updateThumbnail(const QImage &thumbnail) {
return FillAmountAndCurrency(amount, _invoice.currency); _invoice.cover.thumbnail = thumbnail;
_thumbnails.fire_copy(thumbnail);
} }
QString FormSummary::computeTotalAmount() const { QString FormSummary::formatAmount(int64 amount) const {
const auto base = FillAmountAndCurrency(
std::abs(amount),
_invoice.currency);
return (amount > 0) ? base : (QString::fromUtf8("\xe2\x88\x92") + base);
}
int64 FormSummary::computeTotalAmount() const {
const auto total = ranges::accumulate( const auto total = ranges::accumulate(
_invoice.prices, _invoice.prices,
int64(0), int64(0),
@ -71,7 +78,7 @@ QString FormSummary::computeTotalAmount() const {
std::plus<>(), std::plus<>(),
&LabeledPrice::price) &LabeledPrice::price)
: int64(0); : int64(0);
return computeAmount(total + shipping); return total + shipping;
} }
void FormSummary::setupControls() { void FormSummary::setupControls() {
@ -92,22 +99,125 @@ void FormSummary::setupControls() {
_1 + _2 < _3)); _1 + _2 < _3));
} }
not_null<Ui::RpWidget*> FormSummary::setupContent() { void FormSummary::setupCover(not_null<VerticalLayout*> layout) {
const auto inner = _scroll->setOwnedWidget( struct State {
object_ptr<Ui::VerticalLayout>(this)); QImage thumbnail;
FlatLabel *title = nullptr;
FlatLabel *description = nullptr;
FlatLabel *seller = nullptr;
};
_scroll->widthValue( const auto cover = layout->add(object_ptr<RpWidget>(layout));
) | rpl::start_with_next([=](int width) { const auto state = cover->lifetime().make_state<State>();
inner->resizeToWidth(width); state->title = CreateChild<FlatLabel>(
}, inner->lifetime()); cover,
_invoice.cover.title,
st::paymentsTitle);
state->description = CreateChild<FlatLabel>(
cover,
_invoice.cover.description,
st::paymentsDescription);
state->seller = CreateChild<FlatLabel>(
cover,
_invoice.cover.seller,
st::paymentsSeller);
cover->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
if (state->thumbnail.isNull()) {
return;
}
const auto &padding = st::paymentsCoverPadding;
const auto thumbnailSkip = st::paymentsThumbnailSize.width()
+ st::paymentsThumbnailSkip;
const auto left = padding.left();
const auto top = padding.top();
const auto rect = QRect(
QPoint(left, top),
state->thumbnail.size() / state->thumbnail.devicePixelRatio());
if (rect.intersects(clip)) {
QPainter(cover).drawImage(rect, state->thumbnail);
}
}, cover->lifetime());
rpl::combine(
cover->widthValue(),
_thumbnails.events_starting_with_copy(_invoice.cover.thumbnail)
) | rpl::start_with_next([=](int width, QImage &&thumbnail) {
const auto &padding = st::paymentsCoverPadding;
const auto thumbnailSkip = st::paymentsThumbnailSize.width()
+ st::paymentsThumbnailSkip;
const auto left = padding.left()
+ (thumbnail.isNull() ? 0 : thumbnailSkip);
const auto available = width
- padding.left()
- padding.right()
- (thumbnail.isNull() ? 0 : thumbnailSkip);
state->title->resizeToNaturalWidth(available);
state->title->moveToLeft(
left,
padding.top() + st::paymentsTitleTop);
state->description->resizeToNaturalWidth(available);
state->description->moveToLeft(
left,
(state->title->y()
+ state->title->height()
+ st::paymentsDescriptionTop));
state->seller->resizeToNaturalWidth(available);
state->seller->moveToLeft(
left,
(state->description->y()
+ state->description->height()
+ st::paymentsSellerTop));
const auto thumbnailHeight = padding.top()
+ (thumbnail.isNull()
? 0
: int(thumbnail.height() / thumbnail.devicePixelRatio()))
+ padding.bottom();
const auto height = state->seller->y()
+ state->seller->height()
+ padding.bottom();
cover->resize(width, std::max(thumbnailHeight, height));
state->thumbnail = std::move(thumbnail);
cover->update();
}, cover->lifetime());
}
void FormSummary::setupPrices(not_null<VerticalLayout*> layout) {
Settings::AddSkip(layout, st::paymentsPricesTopSkip);
const auto add = [&](
const QString &label,
int64 amount,
bool full = false) {
const auto &st = full
? st::paymentsFullPriceAmount
: st::paymentsPriceAmount;
const auto right = CreateChild<FlatLabel>(
layout.get(),
formatAmount(amount),
st);
const auto &padding = st::paymentsPricePadding;
const auto left = layout->add(
object_ptr<FlatLabel>(
layout,
label,
(full
? st::paymentsFullPriceLabel
: st::paymentsPriceLabel)),
style::margins(
padding.left(),
padding.top(),
(padding.right()
+ right->naturalWidth()
+ 2 * st.style.font->spacew),
padding.bottom()));
rpl::combine(
left->topValue(),
layout->widthValue()
) | rpl::start_with_next([=](int top, int width) {
right->moveToRight(st::paymentsPricePadding.right(), top, width);
}, right->lifetime());
};
for (const auto &price : _invoice.prices) { for (const auto &price : _invoice.prices) {
inner->add( add(price.label, price.price);
object_ptr<Ui::FlatLabel>(
inner,
price.label + ": " + computeAmount(price.price),
st::passportFormPolicy),
st::paymentsFormPricePadding);
} }
const auto selected = ranges::find( const auto selected = ranges::find(
_options.list, _options.list,
@ -115,44 +225,35 @@ not_null<Ui::RpWidget*> FormSummary::setupContent() {
&ShippingOption::id); &ShippingOption::id);
if (selected != end(_options.list)) { if (selected != end(_options.list)) {
for (const auto &price : selected->prices) { for (const auto &price : selected->prices) {
inner->add( add(price.label, price.price);
object_ptr<Ui::FlatLabel>(
inner,
price.label + ": " + computeAmount(price.price),
st::passportFormPolicy),
st::paymentsFormPricePadding);
} }
} }
inner->add( add(tr::lng_payments_total_label(tr::now), computeTotalAmount(), true);
object_ptr<Ui::FlatLabel>( Settings::AddSkip(layout, st::paymentsPricesBottomSkip);
inner, }
"Total: " + computeTotalAmount(),
st::passportFormHeader),
st::passportFormHeaderPadding);
inner->add( void FormSummary::setupSections(not_null<VerticalLayout*> layout) {
object_ptr<Ui::BoxContentDivider>( Settings::AddSkip(layout, st::paymentsSectionsTopSkip);
inner,
st::passportFormDividerHeight),
{ 0, 0, 0, st::passportFormHeaderPadding.top() });
const auto method = inner->add(object_ptr<FormRow>(inner)); const auto add = [&](
method->addClickHandler([=] { rpl::producer<QString> title,
_delegate->panelEditPaymentMethod(); const QString &label,
}); const style::icon *icon,
method->updateContent( Fn<void()> handler) {
tr::lng_payments_payment_method(tr::now), Settings::AddButtonWithLabel(
(_method.ready layout,
? _method.title std::move(title),
: tr::lng_payments_payment_method_ph(tr::now)), rpl::single(label),
_method.ready, st::paymentsSectionButton,
false, icon
anim::type::instant); )->addClickHandler(std::move(handler));
};
add(
tr::lng_payments_payment_method(),
_method.title,
&st::paymentsIconPaymentMethod,
[=] { _delegate->panelEditPaymentMethod(); });
if (_invoice.isShippingAddressRequested) { if (_invoice.isShippingAddressRequested) {
const auto info = inner->add(object_ptr<FormRow>(inner));
info->addClickHandler([=] {
_delegate->panelEditShippingInformation();
});
auto list = QStringList(); auto list = QStringList();
const auto push = [&](const QString &value) { const auto push = [&](const QString &value) {
if (!value.isEmpty()) { if (!value.isEmpty()) {
@ -165,65 +266,61 @@ not_null<Ui::RpWidget*> FormSummary::setupContent() {
push(_information.shippingAddress.state); push(_information.shippingAddress.state);
push(_information.shippingAddress.countryIso2); push(_information.shippingAddress.countryIso2);
push(_information.shippingAddress.postcode); push(_information.shippingAddress.postcode);
info->updateContent( add(
tr::lng_payments_shipping_address(tr::now), tr::lng_payments_shipping_address(),
(list.isEmpty() list.join(", "),
? tr::lng_payments_shipping_address_ph(tr::now) &st::paymentsIconShippingAddress,
: list.join(", ")), [=] { _delegate->panelEditShippingInformation(); });
!list.isEmpty(),
false,
anim::type::instant);
} }
if (!_options.list.empty()) { if (!_options.list.empty()) {
const auto options = inner->add(object_ptr<FormRow>(inner)); const auto selected = ranges::find(
options->addClickHandler([=] { _options.list,
_delegate->panelChooseShippingOption(); _options.selectedId,
}); &ShippingOption::id);
options->updateContent( add(
tr::lng_payments_shipping_method(tr::now), tr::lng_payments_shipping_method(),
(selected != end(_options.list) (selected != end(_options.list)) ? selected->title : QString(),
? selected->title &st::paymentsIconShippingMethod,
: tr::lng_payments_shipping_method_ph(tr::now)), [=] { _delegate->panelChooseShippingOption(); });
(selected != end(_options.list)),
false,
anim::type::instant);
} }
if (_invoice.isNameRequested) { if (_invoice.isNameRequested) {
const auto name = inner->add(object_ptr<FormRow>(inner)); add(
name->addClickHandler([=] { _delegate->panelEditName(); }); tr::lng_payments_info_name(),
name->updateContent( _information.name,
tr::lng_payments_info_name(tr::now), &st::paymentsIconName,
(_information.name.isEmpty() [=] { _delegate->panelEditName(); });
? tr::lng_payments_info_name_ph(tr::now)
: _information.name),
!_information.name.isEmpty(),
false,
anim::type::instant);
} }
if (_invoice.isEmailRequested) { if (_invoice.isEmailRequested) {
const auto email = inner->add(object_ptr<FormRow>(inner)); add(
email->addClickHandler([=] { _delegate->panelEditEmail(); }); tr::lng_payments_info_email(),
email->updateContent( _information.email,
tr::lng_payments_info_email(tr::now), &st::paymentsIconEmail,
(_information.email.isEmpty() [=] { _delegate->panelEditEmail(); });
? tr::lng_payments_info_email_ph(tr::now)
: _information.email),
!_information.email.isEmpty(),
false,
anim::type::instant);
} }
if (_invoice.isPhoneRequested) { if (_invoice.isPhoneRequested) {
const auto phone = inner->add(object_ptr<FormRow>(inner)); add(
phone->addClickHandler([=] { _delegate->panelEditPhone(); }); tr::lng_payments_info_phone(),
phone->updateContent( _information.phone,
tr::lng_payments_info_phone(tr::now), &st::paymentsIconPhone,
(_information.phone.isEmpty() [=] { _delegate->panelEditPhone(); });
? tr::lng_payments_info_phone_ph(tr::now)
: _information.phone),
!_information.phone.isEmpty(),
false,
anim::type::instant);
} }
Settings::AddSkip(layout, st::paymentsSectionsTopSkip);
}
not_null<RpWidget*> FormSummary::setupContent() {
const auto inner = _scroll->setOwnedWidget(
object_ptr<VerticalLayout>(this));
_scroll->widthValue(
) | rpl::start_with_next([=](int width) {
inner->resizeToWidth(width);
}, inner->lifetime());
setupCover(inner);
Settings::AddDivider(inner);
setupPrices(inner);
Settings::AddDivider(inner);
setupSections(inner);
return inner; return inner;
} }

View File

@ -15,6 +15,7 @@ namespace Ui {
class ScrollArea; class ScrollArea;
class FadeShadow; class FadeShadow;
class RoundButton; class RoundButton;
class VerticalLayout;
} // namespace Ui } // namespace Ui
namespace Payments::Ui { namespace Payments::Ui {
@ -33,15 +34,20 @@ public:
const ShippingOptions &options, const ShippingOptions &options,
not_null<PanelDelegate*> delegate); not_null<PanelDelegate*> delegate);
void updateThumbnail(const QImage &thumbnail);
private: private:
void resizeEvent(QResizeEvent *e) override; void resizeEvent(QResizeEvent *e) override;
void setupControls(); void setupControls();
[[nodiscard]] not_null<Ui::RpWidget*> setupContent(); [[nodiscard]] not_null<Ui::RpWidget*> setupContent();
void setupCover(not_null<VerticalLayout*> layout);
void setupPrices(not_null<VerticalLayout*> layout);
void setupSections(not_null<VerticalLayout*> layout);
void updateControlsGeometry(); void updateControlsGeometry();
[[nodiscard]] QString computeAmount(int64 amount) const; [[nodiscard]] QString formatAmount(int64 amount) const;
[[nodiscard]] QString computeTotalAmount() const; [[nodiscard]] int64 computeTotalAmount() const;
const not_null<PanelDelegate*> _delegate; const not_null<PanelDelegate*> _delegate;
Invoice _invoice; Invoice _invoice;
@ -52,6 +58,7 @@ private:
object_ptr<FadeShadow> _topShadow; object_ptr<FadeShadow> _topShadow;
object_ptr<FadeShadow> _bottomShadow; object_ptr<FadeShadow> _bottomShadow;
object_ptr<RoundButton> _submit; object_ptr<RoundButton> _submit;
rpl::event_stream<QImage> _thumbnails;
}; };

View File

@ -52,17 +52,24 @@ void Panel::showForm(
const RequestedInformation &current, const RequestedInformation &current,
const PaymentMethodDetails &method, const PaymentMethodDetails &method,
const ShippingOptions &options) { const ShippingOptions &options) {
_widget->showInner( auto form = base::make_unique_q<FormSummary>(
base::make_unique_q<FormSummary>( _widget.get(),
_widget.get(), invoice,
invoice, current,
current, method,
method, options,
options, _delegate);
_delegate)); _weakFormSummary = form.get();
_widget->showInner(std::move(form));
_widget->setBackAllowed(false); _widget->setBackAllowed(false);
} }
void Panel::updateFormThumbnail(const QImage &thumbnail) {
if (_weakFormSummary) {
_weakFormSummary->updateThumbnail(thumbnail);
}
}
void Panel::showEditInformation( void Panel::showEditInformation(
const Invoice &invoice, const Invoice &invoice,
const RequestedInformation &current, const RequestedInformation &current,

View File

@ -28,6 +28,7 @@ struct RequestedInformation;
struct ShippingOptions; struct ShippingOptions;
enum class InformationField; enum class InformationField;
enum class CardField; enum class CardField;
class FormSummary;
class EditInformation; class EditInformation;
class EditCard; class EditCard;
struct PaymentMethodDetails; struct PaymentMethodDetails;
@ -45,6 +46,7 @@ public:
const RequestedInformation &current, const RequestedInformation &current,
const PaymentMethodDetails &method, const PaymentMethodDetails &method,
const ShippingOptions &options); const ShippingOptions &options);
void updateFormThumbnail(const QImage &thumbnail);
void showEditInformation( void showEditInformation(
const Invoice &invoice, const Invoice &invoice,
const RequestedInformation &current, const RequestedInformation &current,
@ -78,6 +80,7 @@ private:
const not_null<PanelDelegate*> _delegate; const not_null<PanelDelegate*> _delegate;
std::unique_ptr<SeparatePanel> _widget; std::unique_ptr<SeparatePanel> _widget;
std::unique_ptr<Webview::Window> _webview; std::unique_ptr<Webview::Window> _webview;
QPointer<FormSummary> _weakFormSummary;
QPointer<EditInformation> _weakEditInformation; QPointer<EditInformation> _weakEditInformation;
QPointer<EditCard> _weakEditCard; QPointer<EditCard> _weakEditCard;

View File

@ -14,7 +14,16 @@ struct LabeledPrice {
int64 price = 0; int64 price = 0;
}; };
struct Cover {
QString title;
QString description;
QString seller;
QImage thumbnail;
};
struct Invoice { struct Invoice {
Cover cover;
std::vector<LabeledPrice> prices; std::vector<LabeledPrice> prices;
QString currency; QString currency;