Improve checkout information / card page design.

This commit is contained in:
John Preston 2021-03-26 19:23:12 +04:00
parent fafea73ea7
commit 47fdef1e38
13 changed files with 468 additions and 319 deletions

View File

@ -1866,7 +1866,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_shipping_address" = "Shipping Address"; "lng_payments_shipping_address" = "Shipping Address";
"lng_payments_receiver_information" = "Receiver";
"lng_payments_address_street1" = "Address 1"; "lng_payments_address_street1" = "Address 1";
"lng_payments_address_street2" = "Address 2"; "lng_payments_address_street2" = "Address 2";
"lng_payments_address_city" = "City"; "lng_payments_address_city" = "City";
@ -1878,7 +1877,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_payments_info_name" = "Name"; "lng_payments_info_name" = "Name";
"lng_payments_info_email" = "Email"; "lng_payments_info_email" = "Email";
"lng_payments_info_phone" = "Phone"; "lng_payments_info_phone" = "Phone";
"lng_payments_shipping_address_title" = "Shipping Address"; "lng_payments_shipping_address_title" = "Shipping Information";
"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_card_title" = "New Card"; "lng_payments_card_title" = "New Card";
"lng_payments_card_number" = "Card Number"; "lng_payments_card_number" = "Card Number";

View File

@ -87,6 +87,7 @@ CheckoutProcess::CheckoutProcess(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
showForm(); showForm();
}, _panel->lifetime()); }, _panel->lifetime());
showForm();
} }
CheckoutProcess::~CheckoutProcess() { CheckoutProcess::~CheckoutProcess() {
@ -156,7 +157,7 @@ void CheckoutProcess::handleError(const Error &error) {
showToast({ "Error: " + id }); showToast({ "Error: " + id });
} }
break; break;
case Error::Type::Validate: case Error::Type::Validate: {
if (_submitState == SubmitState::Validation) { if (_submitState == SubmitState::Validation) {
_submitState = SubmitState::None; _submitState = SubmitState::None;
} }
@ -189,7 +190,21 @@ void CheckoutProcess::handleError(const Error &error) {
} else { } else {
showToast({ "Error: " + id }); showToast({ "Error: " + id });
} }
break; } break;
case Error::Type::Stripe: {
using Field = Ui::CardField;
if (id == u"InvalidNumber"_q || id == u"IncorrectNumber"_q) {
showCardError(Field::Number);
} else if (id == u"InvalidCVC"_q || id == u"IncorrectCVC"_q) {
showCardError(Field::CVC);
} else if (id == u"InvalidExpiryMonth"_q
|| id == u"InvalidExpiryYear"_q
|| id == u"ExpiredCard"_q) {
showCardError(Field::ExpireDate);
} else {
showToast({ "Error: " + id });
}
} break;
case Error::Type::Send: case Error::Type::Send:
if (_submitState == SubmitState::Finishing) { if (_submitState == SubmitState::Finishing) {
_submitState = SubmitState::None; _submitState = SubmitState::None;
@ -375,6 +390,13 @@ void CheckoutProcess::showInformationError(Ui::InformationField field) {
field); field);
} }
void CheckoutProcess::showCardError(Ui::CardField field) {
if (_submitState != SubmitState::None) {
return;
}
_panel->showCardError(_form->paymentMethod().ui.native, field);
}
void CheckoutProcess::chooseShippingOption() { void CheckoutProcess::chooseShippingOption() {
_panel->chooseShippingOption(_form->shippingOptions()); _panel->chooseShippingOption(_form->shippingOptions());
} }

View File

@ -19,6 +19,7 @@ class Session;
namespace Payments::Ui { namespace Payments::Ui {
class Panel; class Panel;
enum class InformationField; enum class InformationField;
enum class CardField;
} // namespace Payments::Ui } // namespace Payments::Ui
namespace Payments { namespace Payments {
@ -58,6 +59,7 @@ private:
void showForm(); void showForm();
void showEditInformation(Ui::InformationField field); void showEditInformation(Ui::InformationField field);
void showInformationError(Ui::InformationField field); void showInformationError(Ui::InformationField field);
void showCardError(Ui::CardField field);
void chooseShippingOption(); void chooseShippingOption();
void editPaymentMethod(); void editPaymentMethod();

View File

@ -56,3 +56,14 @@ paymentsIconName: icon {{ "payments/payment_name", menuIconFg }};
paymentsIconEmail: icon {{ "payments/payment_email", menuIconFg }}; paymentsIconEmail: icon {{ "payments/payment_email", menuIconFg }};
paymentsIconPhone: icon {{ "payments/payment_phone", menuIconFg }}; paymentsIconPhone: icon {{ "payments/payment_phone", menuIconFg }};
paymentsIconShippingMethod: icon {{ "payments/payment_shipping", menuIconFg }}; paymentsIconShippingMethod: icon {{ "payments/payment_shipping", menuIconFg }};
paymentsField: defaultInputField;
paymentsFieldPadding: margins(28px, 0px, 28px, 2px);
paymentsExpireCvcSkip: 34px;
paymentsBillingInformationTitle: FlatLabel(defaultFlatLabel) {
style: semiboldTextStyle;
textFg: windowActiveTextFg;
minWidth: 240px;
}
paymentsBillingInformationTitlePadding: margins(28px, 26px, 28px, 1px);

View File

@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/ui/payments_edit_card.h" #include "payments/ui/payments_edit_card.h"
#include "payments/ui/payments_panel_delegate.h" #include "payments/ui/payments_panel_delegate.h"
#include "passport/ui/passport_details_row.h" #include "payments/ui/payments_field.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"
@ -52,16 +52,24 @@ EditCard::EditCard(
void EditCard::setFocus(CardField field) { void EditCard::setFocus(CardField field) {
_focusField = field; _focusField = field;
if (const auto control = controlForField(field)) { if (const auto control = lookupField(field)) {
_scroll->ensureWidgetVisible(control); _scroll->ensureWidgetVisible(control->widget());
control->setFocus();
}
}
void EditCard::setFocusFast(CardField field) {
_focusField = field;
if (const auto control = lookupField(field)) {
_scroll->ensureWidgetVisible(control->widget());
control->setFocusFast(); control->setFocusFast();
} }
} }
void EditCard::showError(CardField field) { void EditCard::showError(CardField field) {
if (const auto control = controlForField(field)) { if (const auto control = lookupField(field)) {
_scroll->ensureWidgetVisible(control); _scroll->ensureWidgetVisible(control->widget());
control->showError(QString()); control->showError();
} }
} }
@ -95,102 +103,69 @@ not_null<RpWidget*> EditCard::setupContent() {
const auto showBox = [=](object_ptr<BoxContent> box) { const auto showBox = [=](object_ptr<BoxContent> box) {
_delegate->panelShowBox(std::move(box)); _delegate->panelShowBox(std::move(box));
}; };
using Type = Passport::Ui::PanelDetailsType; const auto add = [&](FieldConfig &&config) {
auto maxLabelWidth = 0; auto result = std::make_unique<Field>(inner, std::move(config));
accumulate_max( inner->add(result->ownedWidget(), st::paymentsFieldPadding);
maxLabelWidth, return result;
Row::LabelWidth("Card Number")); };
accumulate_max( _number = add({
maxLabelWidth, .type = FieldType::CardNumber,
Row::LabelWidth("CVC")); .placeholder = tr::lng_payments_card_number(),
accumulate_max( .required = true,
maxLabelWidth, });
Row::LabelWidth("MM/YY"));
if (_native.needCardholderName) { if (_native.needCardholderName) {
accumulate_max( _name = add({
maxLabelWidth, .type = FieldType::CardNumber,
Row::LabelWidth("Cardholder Name")); .placeholder = tr::lng_payments_card_holder(),
.required = true,
});
}
auto container = inner->add(
object_ptr<FixedHeightWidget>(
inner,
_number->widget()->height()),
st::paymentsFieldPadding);
_expire = std::make_unique<Field>(container, FieldConfig{
.type = FieldType::CardExpireDate,
.placeholder = rpl::single(u"MM / YY"_q),
.required = true,
});
_cvc = std::make_unique<Field>(container, FieldConfig{
.type = FieldType::CardCVC,
.placeholder = rpl::single(u"CVC"_q),
.required = true,
});
container->widthValue(
) | rpl::start_with_next([=](int width) {
const auto left = (width - st::paymentsExpireCvcSkip) / 2;
const auto right = width - st::paymentsExpireCvcSkip - left;
_expire->widget()->resizeToWidth(left);
_cvc->widget()->resizeToWidth(right);
_expire->widget()->moveToLeft(0, 0, width);
_cvc->widget()->moveToRight(0, 0, width);
}, container->lifetime());
if (_native.needCountry || _native.needZip) {
inner->add(
object_ptr<Ui::FlatLabel>(
inner,
tr::lng_payments_billing_address(),
st::paymentsBillingInformationTitle),
st::paymentsBillingInformationTitlePadding);
} }
if (_native.needCountry) { if (_native.needCountry) {
accumulate_max( _country = add({
maxLabelWidth, .type = FieldType::Country,
Row::LabelWidth("Billing Country")); .placeholder = tr::lng_payments_billing_country(),
.required = true,
});
} }
if (_native.needZip) { if (_native.needZip) {
accumulate_max( _zip = add({
maxLabelWidth, .type = FieldType::Text,
Row::LabelWidth("Billing Zip")); .placeholder = tr::lng_payments_billing_zip_code(),
} .maxLength = kMaxPostcodeSize,
_number = inner->add( .required = true,
Row::Create( });
inner,
showBox,
QString(),
Type::Text,
"Card Number",
maxLabelWidth,
QString(),
QString(),
1024));
_cvc = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Text,
"CVC",
maxLabelWidth,
QString(),
QString(),
1024));
_expire = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Text,
"MM/YY",
maxLabelWidth,
QString(),
QString(),
1024));
if (_native.needCardholderName) {
_name = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Text,
"Cardholder Name",
maxLabelWidth,
QString(),
QString(),
1024));
}
if (_native.needCountry) {
_country = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Country,
"Billing Country",
maxLabelWidth,
QString(),
QString()));
}
if (_native.needZip) {
_zip = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Postcode,
"Billing Zip Code",
maxLabelWidth,
QString(),
QString(),
kMaxPostcodeSize));
} }
return inner; return inner;
} }
@ -200,7 +175,7 @@ void EditCard::resizeEvent(QResizeEvent *e) {
} }
void EditCard::focusInEvent(QFocusEvent *e) { void EditCard::focusInEvent(QFocusEvent *e) {
if (const auto control = controlForField(_focusField)) { if (const auto control = lookupField(_focusField)) {
control->setFocusFast(); control->setFocusFast();
} }
} }
@ -218,27 +193,27 @@ void EditCard::updateControlsGeometry() {
_scroll->updateBars(); _scroll->updateBars();
} }
auto EditCard::controlForField(CardField field) const -> Row* { auto EditCard::lookupField(CardField field) const -> Field* {
switch (field) { switch (field) {
case CardField::Number: return _number; case CardField::Number: return _number.get();
case CardField::CVC: return _cvc; case CardField::CVC: return _cvc.get();
case CardField::ExpireDate: return _expire; case CardField::ExpireDate: return _expire.get();
case CardField::Name: return _name; case CardField::Name: return _name.get();
case CardField::AddressCountry: return _country; case CardField::AddressCountry: return _country.get();
case CardField::AddressZip: return _zip; case CardField::AddressZip: return _zip.get();
} }
Unexpected("Unknown field in EditCard::controlForField."); Unexpected("Unknown field in EditCard::controlForField.");
} }
UncheckedCardDetails EditCard::collect() const { UncheckedCardDetails EditCard::collect() const {
return { return {
.number = _number ? _number->valueCurrent() : QString(), .number = _number ? _number->value() : QString(),
.cvc = _cvc ? _cvc->valueCurrent() : QString(), .cvc = _cvc ? _cvc->value() : QString(),
.expireYear = _expire ? ExtractYear(_expire->valueCurrent()) : 0, .expireYear = _expire ? ExtractYear(_expire->value()) : 0,
.expireMonth = _expire ? ExtractMonth(_expire->valueCurrent()) : 0, .expireMonth = _expire ? ExtractMonth(_expire->value()) : 0,
.cardholderName = _name ? _name->valueCurrent() : QString(), .cardholderName = _name ? _name->value() : QString(),
.addressCountry = _country ? _country->valueCurrent() : QString(), .addressCountry = _country ? _country->value() : QString(),
.addressZip = _zip ? _zip->valueCurrent() : QString(), .addressZip = _zip ? _zip->value() : QString(),
}; };
} }

View File

@ -17,15 +17,12 @@ class FadeShadow;
class RoundButton; class RoundButton;
} // namespace Ui } // namespace Ui
namespace Passport::Ui {
class PanelDetailsRow;
} // namespace Passport::Ui
namespace Payments::Ui { namespace Payments::Ui {
using namespace ::Ui; using namespace ::Ui;
class PanelDelegate; class PanelDelegate;
class Field;
class EditCard final : public RpWidget { class EditCard final : public RpWidget {
public: public:
@ -35,19 +32,18 @@ public:
CardField field, CardField field,
not_null<PanelDelegate*> delegate); not_null<PanelDelegate*> delegate);
void showError(CardField field);
void setFocus(CardField field); void setFocus(CardField field);
void setFocusFast(CardField field);
void showError(CardField field);
private: private:
using Row = Passport::Ui::PanelDetailsRow;
void resizeEvent(QResizeEvent *e) override; void resizeEvent(QResizeEvent *e) override;
void focusInEvent(QFocusEvent *e) override; void focusInEvent(QFocusEvent *e) override;
void setupControls(); void setupControls();
[[nodiscard]] not_null<Ui::RpWidget*> setupContent(); [[nodiscard]] not_null<Ui::RpWidget*> setupContent();
void updateControlsGeometry(); void updateControlsGeometry();
[[nodiscard]] Row *controlForField(CardField field) const; [[nodiscard]] Field *lookupField(CardField field) const;
[[nodiscard]] UncheckedCardDetails collect() const; [[nodiscard]] UncheckedCardDetails collect() const;
@ -59,12 +55,12 @@ private:
object_ptr<FadeShadow> _bottomShadow; object_ptr<FadeShadow> _bottomShadow;
object_ptr<RoundButton> _done; object_ptr<RoundButton> _done;
Row *_number = nullptr; std::unique_ptr<Field> _number;
Row *_cvc = nullptr; std::unique_ptr<Field> _cvc;
Row *_expire = nullptr; std::unique_ptr<Field> _expire;
Row *_name = nullptr; std::unique_ptr<Field> _name;
Row *_country = nullptr; std::unique_ptr<Field> _country;
Row *_zip = nullptr; std::unique_ptr<Field> _zip;
CardField _focusField = CardField::Number; CardField _focusField = CardField::Number;

View File

@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/ui/payments_edit_information.h" #include "payments/ui/payments_edit_information.h"
#include "payments/ui/payments_panel_delegate.h" #include "payments/ui/payments_panel_delegate.h"
#include "passport/ui/passport_details_row.h" #include "payments/ui/payments_field.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"
@ -48,18 +48,28 @@ EditInformation::EditInformation(
setupControls(); setupControls();
} }
EditInformation::~EditInformation() = default;
void EditInformation::setFocus(InformationField field) { void EditInformation::setFocus(InformationField field) {
_focusField = field; _focusField = field;
if (const auto control = controlForField(field)) { if (const auto control = lookupField(field)) {
_scroll->ensureWidgetVisible(control); _scroll->ensureWidgetVisible(control->widget());
control->setFocus();
}
}
void EditInformation::setFocusFast(InformationField field) {
_focusField = field;
if (const auto control = lookupField(field)) {
_scroll->ensureWidgetVisible(control->widget());
control->setFocusFast(); control->setFocusFast();
} }
} }
void EditInformation::showError(InformationField field) { void EditInformation::showError(InformationField field) {
if (const auto control = controlForField(field)) { if (const auto control = lookupField(field)) {
_scroll->ensureWidgetVisible(control); _scroll->ensureWidgetVisible(control->widget());
control->showError(QString()); control->showError();
} }
} }
@ -93,106 +103,44 @@ not_null<RpWidget*> EditInformation::setupContent() {
const auto showBox = [=](object_ptr<BoxContent> box) { const auto showBox = [=](object_ptr<BoxContent> box) {
_delegate->panelShowBox(std::move(box)); _delegate->panelShowBox(std::move(box));
}; };
using Type = Passport::Ui::PanelDetailsType; const auto add = [&](FieldConfig &&config) {
auto maxLabelWidth = 0; auto result = std::make_unique<Field>(inner, std::move(config));
inner->add(result->ownedWidget(), st::paymentsFieldPadding);
return result;
};
if (_invoice.isShippingAddressRequested) { if (_invoice.isShippingAddressRequested) {
accumulate_max( _street1 = add({
maxLabelWidth, .placeholder = tr::lng_payments_address_street1(),
Row::LabelWidth(tr::lng_passport_street(tr::now))); .value = _information.shippingAddress.address1,
accumulate_max( .maxLength = kMaxStreetSize,
maxLabelWidth, .required = true,
Row::LabelWidth(tr::lng_passport_city(tr::now))); });
accumulate_max( _street2 = add({
maxLabelWidth, .placeholder = tr::lng_payments_address_street2(),
Row::LabelWidth(tr::lng_passport_state(tr::now))); .value = _information.shippingAddress.address2,
accumulate_max( .maxLength = kMaxStreetSize,
maxLabelWidth, });
Row::LabelWidth(tr::lng_passport_country(tr::now))); _city = add({
accumulate_max( .placeholder = tr::lng_payments_address_city(),
maxLabelWidth, .value = _information.shippingAddress.city,
Row::LabelWidth(tr::lng_passport_postcode(tr::now))); .required = true,
} });
if (_invoice.isNameRequested) { _state = add({
accumulate_max( .placeholder = tr::lng_payments_address_state(),
maxLabelWidth, .value = _information.shippingAddress.state,
Row::LabelWidth(tr::lng_payments_info_name(tr::now))); });
} _country = add({
if (_invoice.isEmailRequested) { .type = FieldType::Country,
accumulate_max( .placeholder = tr::lng_payments_address_country(),
maxLabelWidth, .value = _information.shippingAddress.countryIso2,
Row::LabelWidth(tr::lng_payments_info_email(tr::now))); .required = true,
} });
if (_invoice.isPhoneRequested) { _postcode = add({
accumulate_max( .placeholder = tr::lng_payments_address_postcode(),
maxLabelWidth, .value = _information.shippingAddress.postcode,
Row::LabelWidth(tr::lng_payments_info_phone(tr::now))); .maxLength = kMaxPostcodeSize,
} .required = true,
if (_invoice.isShippingAddressRequested) { });
_street1 = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Text,
tr::lng_passport_street(tr::now),
maxLabelWidth,
_information.shippingAddress.address1,
QString(),
kMaxStreetSize));
_street2 = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Text,
tr::lng_passport_street(tr::now),
maxLabelWidth,
_information.shippingAddress.address2,
QString(),
kMaxStreetSize));
_city = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Text,
tr::lng_passport_city(tr::now),
maxLabelWidth,
_information.shippingAddress.city,
QString(),
kMaxStreetSize));
_state = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Text,
tr::lng_passport_state(tr::now),
maxLabelWidth,
_information.shippingAddress.state,
QString(),
kMaxStreetSize));
_country = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Country,
tr::lng_passport_country(tr::now),
maxLabelWidth,
_information.shippingAddress.countryIso2,
QString()));
_postcode = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Postcode,
tr::lng_passport_postcode(tr::now),
maxLabelWidth,
_information.shippingAddress.postcode,
QString(),
kMaxPostcodeSize));
//StreetValidate, // #TODO payments //StreetValidate, // #TODO payments
//CityValidate, //CityValidate,
//CountryValidate, //CountryValidate,
@ -200,43 +148,28 @@ not_null<RpWidget*> EditInformation::setupContent() {
//PostcodeValidate, //PostcodeValidate,
} }
if (_invoice.isNameRequested) { if (_invoice.isNameRequested) {
_name = inner->add( _name = add({
Row::Create( .placeholder = tr::lng_payments_info_name(),
inner, .value = _information.name,
showBox, .maxLength = kMaxNameSize,
QString(), .required = true,
Type::Text, });
tr::lng_payments_info_name(tr::now),
maxLabelWidth,
_information.name,
QString(),
kMaxNameSize));
} }
if (_invoice.isEmailRequested) { if (_invoice.isEmailRequested) {
_email = inner->add( _email = add({
Row::Create( .placeholder = tr::lng_payments_info_email(),
inner, .value = _information.email,
showBox, .maxLength = kMaxEmailSize,
QString(), .required = true,
Type::Text, });
tr::lng_payments_info_email(tr::now),
maxLabelWidth,
_information.email,
QString(),
kMaxEmailSize));
} }
if (_invoice.isPhoneRequested) { if (_invoice.isPhoneRequested) {
_phone = inner->add( _phone = add({
Row::Create( .placeholder = tr::lng_payments_info_phone(),
inner, .value = _information.phone,
showBox, .maxLength = kMaxPhoneSize,
QString(), .required = true,
Type::Text, });
tr::lng_payments_info_phone(tr::now),
maxLabelWidth,
_information.phone,
QString(),
kMaxPhoneSize));
} }
return inner; return inner;
} }
@ -246,8 +179,8 @@ void EditInformation::resizeEvent(QResizeEvent *e) {
} }
void EditInformation::focusInEvent(QFocusEvent *e) { void EditInformation::focusInEvent(QFocusEvent *e) {
if (const auto control = controlForField(_focusField)) { if (const auto control = lookupField(_focusField)) {
control->setFocusFast(); control->setFocus();
} }
} }
@ -264,32 +197,32 @@ void EditInformation::updateControlsGeometry() {
_scroll->updateBars(); _scroll->updateBars();
} }
auto EditInformation::controlForField(InformationField field) const -> Row* { auto EditInformation::lookupField(InformationField field) const -> Field* {
switch (field) { switch (field) {
case InformationField::ShippingStreet: return _street1; case InformationField::ShippingStreet: return _street1.get();
case InformationField::ShippingCity: return _city; case InformationField::ShippingCity: return _city.get();
case InformationField::ShippingState: return _state; case InformationField::ShippingState: return _state.get();
case InformationField::ShippingCountry: return _country; case InformationField::ShippingCountry: return _country.get();
case InformationField::ShippingPostcode: return _postcode; case InformationField::ShippingPostcode: return _postcode.get();
case InformationField::Name: return _name; case InformationField::Name: return _name.get();
case InformationField::Email: return _email; case InformationField::Email: return _email.get();
case InformationField::Phone: return _phone; case InformationField::Phone: return _phone.get();
} }
Unexpected("Unknown field in EditInformation::controlForField."); Unexpected("Unknown field in EditInformation::lookupField.");
} }
RequestedInformation EditInformation::collect() const { RequestedInformation EditInformation::collect() const {
return { return {
.name = _name ? _name->valueCurrent() : QString(), .name = _name ? _name->value() : QString(),
.phone = _phone ? _phone->valueCurrent() : QString(), .phone = _phone ? _phone->value() : QString(),
.email = _email ? _email->valueCurrent() : QString(), .email = _email ? _email->value() : QString(),
.shippingAddress = { .shippingAddress = {
.address1 = _street1 ? _street1->valueCurrent() : QString(), .address1 = _street1 ? _street1->value() : QString(),
.address2 = _street2 ? _street2->valueCurrent() : QString(), .address2 = _street2 ? _street2->value() : QString(),
.city = _city ? _city->valueCurrent() : QString(), .city = _city ? _city->value() : QString(),
.state = _state ? _state->valueCurrent() : QString(), .state = _state ? _state->value() : QString(),
.countryIso2 = _country ? _country->valueCurrent() : QString(), .countryIso2 = _country ? _country->value() : QString(),
.postcode = _postcode ? _postcode->valueCurrent() : QString(), .postcode = _postcode ? _postcode->value() : QString(),
}, },
}; };
} }

View File

@ -15,17 +15,16 @@ namespace Ui {
class ScrollArea; class ScrollArea;
class FadeShadow; class FadeShadow;
class RoundButton; class RoundButton;
class InputField;
class MaskedInputField;
} // namespace Ui } // namespace Ui
namespace Passport::Ui {
class PanelDetailsRow;
} // namespace Passport::Ui
namespace Payments::Ui { namespace Payments::Ui {
using namespace ::Ui; using namespace ::Ui;
class PanelDelegate; class PanelDelegate;
class Field;
class EditInformation final : public RpWidget { class EditInformation final : public RpWidget {
public: public:
@ -35,20 +34,20 @@ public:
const RequestedInformation &current, const RequestedInformation &current,
InformationField field, InformationField field,
not_null<PanelDelegate*> delegate); not_null<PanelDelegate*> delegate);
~EditInformation();
void showError(InformationField field);
void setFocus(InformationField field); void setFocus(InformationField field);
void setFocusFast(InformationField field);
void showError(InformationField field);
private: private:
using Row = Passport::Ui::PanelDetailsRow;
void resizeEvent(QResizeEvent *e) override; void resizeEvent(QResizeEvent *e) override;
void focusInEvent(QFocusEvent *e) override; void focusInEvent(QFocusEvent *e) override;
void setupControls(); void setupControls();
[[nodiscard]] not_null<Ui::RpWidget*> setupContent(); [[nodiscard]] not_null<Ui::RpWidget*> setupContent();
void updateControlsGeometry(); void updateControlsGeometry();
[[nodiscard]] Row *controlForField(InformationField field) const; [[nodiscard]] Field *lookupField(InformationField field) const;
[[nodiscard]] RequestedInformation collect() const; [[nodiscard]] RequestedInformation collect() const;
@ -61,15 +60,15 @@ private:
object_ptr<FadeShadow> _bottomShadow; object_ptr<FadeShadow> _bottomShadow;
object_ptr<RoundButton> _done; object_ptr<RoundButton> _done;
Row *_street1 = nullptr; std::unique_ptr<Field> _street1;
Row *_street2 = nullptr; std::unique_ptr<Field> _street2;
Row *_city = nullptr; std::unique_ptr<Field> _city;
Row *_state = nullptr; std::unique_ptr<Field> _state;
Row *_country = nullptr; std::unique_ptr<Field> _country;
Row *_postcode = nullptr; std::unique_ptr<Field> _postcode;
Row *_name = nullptr; std::unique_ptr<Field> _name;
Row *_email = nullptr; std::unique_ptr<Field> _email;
Row *_phone = nullptr; std::unique_ptr<Field> _phone;
InformationField _focusField = InformationField::ShippingStreet; InformationField _focusField = InformationField::ShippingStreet;

View File

@ -0,0 +1,140 @@
/*
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 "payments/ui/payments_field.h"
#include "ui/widgets/input_fields.h"
#include "styles/style_payments.h"
namespace Payments::Ui {
namespace {
[[nodiscard]] bool UseMaskedField(FieldType type) {
switch (type) {
case FieldType::Text:
case FieldType::Email:
return false;
case FieldType::CardNumber:
case FieldType::CardExpireDate:
case FieldType::CardCVC:
case FieldType::Country:
case FieldType::Phone:
return true;
}
Unexpected("FieldType in Payments::Ui::UseMaskedField.");
}
[[nodiscard]] base::unique_qptr<RpWidget> CreateWrap(
QWidget *parent,
FieldConfig &config) {
switch (config.type) {
case FieldType::Text:
case FieldType::Email:
return base::make_unique_q<InputField>(
parent,
st::paymentsField,
std::move(config.placeholder),
config.value);
case FieldType::CardNumber:
case FieldType::CardExpireDate:
case FieldType::CardCVC:
case FieldType::Country:
case FieldType::Phone:
return base::make_unique_q<RpWidget>(parent);
}
Unexpected("FieldType in Payments::Ui::CreateWrap.");
}
[[nodiscard]] InputField *LookupInputField(
not_null<RpWidget*> wrap,
FieldConfig &config) {
return UseMaskedField(config.type)
? nullptr
: static_cast<InputField*>(wrap.get());
}
[[nodiscard]] MaskedInputField *LookupMaskedField(
not_null<RpWidget*> wrap,
FieldConfig &config) {
if (!UseMaskedField(config.type)) {
return nullptr;
}
switch (config.type) {
case FieldType::Text:
case FieldType::Email:
return nullptr;
case FieldType::CardNumber:
case FieldType::CardExpireDate:
case FieldType::CardCVC:
case FieldType::Country:
case FieldType::Phone:
return CreateChild<MaskedInputField>(
wrap.get(),
st::paymentsField,
std::move(config.placeholder),
config.value);
}
Unexpected("FieldType in Payments::Ui::LookupMaskedField.");
}
} // namespace
Field::Field(QWidget *parent, FieldConfig &&config)
: _type(config.type)
, _wrap(CreateWrap(parent, config))
, _input(LookupInputField(_wrap.get(), config))
, _masked(LookupMaskedField(_wrap.get(), config)) {
if (_masked) {
_wrap->resize(_masked->size());
_wrap->widthValue(
) | rpl::start_with_next([=](int width) {
_masked->resize(width, _masked->height());
}, _masked->lifetime());
_masked->heightValue(
) | rpl::start_with_next([=](int height) {
_wrap->resize(_wrap->width(), height);
}, _masked->lifetime());
}
}
RpWidget *Field::widget() const {
return _wrap.get();
}
object_ptr<RpWidget> Field::ownedWidget() const {
return object_ptr<RpWidget>::fromRaw(_wrap.get());
}
[[nodiscard]] QString Field::value() const {
return _input ? _input->getLastText() : _masked->getLastText();
}
void Field::setFocus() {
if (_input) {
_input->setFocus();
} else {
_masked->setFocus();
}
}
void Field::setFocusFast() {
if (_input) {
_input->setFocusFast();
} else {
_masked->setFocusFast();
}
}
void Field::showError() {
if (_input) {
_input->showError();
} else {
_masked->showError();
}
}
} // namespace Payments::Ui

View File

@ -0,0 +1,62 @@
/*
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
*/
#pragma once
#include "base/object_ptr.h"
#include "base/unique_qptr.h"
namespace Ui {
class RpWidget;
class InputField;
class MaskedInputField;
} // namespace Ui
namespace Payments::Ui {
using namespace ::Ui;
enum class FieldType {
Text,
CardNumber,
CardExpireDate,
CardCVC,
Country,
Phone,
Email,
};
struct FieldConfig {
FieldType type = FieldType::Text;
rpl::producer<QString> placeholder;
QString value;
int maxLength = 0;
bool required = false;
};
class Field final {
public:
Field(QWidget *parent, FieldConfig &&config);
[[nodiscard]] RpWidget *widget() const;
[[nodiscard]] object_ptr<RpWidget> ownedWidget() const;
[[nodiscard]] QString value() const;
void setFocus();
void setFocusFast();
void showError();
private:
const FieldType _type = FieldType::Text;
const base::unique_qptr<RpWidget> _wrap;
InputField *_input = nullptr;
MaskedInputField *_masked = nullptr;
};
} // namespace Payments::Ui

View File

@ -58,7 +58,7 @@ QString FormSummary::formatAmount(int64 amount) const {
const auto base = FillAmountAndCurrency( const auto base = FillAmountAndCurrency(
std::abs(amount), std::abs(amount),
_invoice.currency); _invoice.currency);
return (amount > 0) ? base : (QString::fromUtf8("\xe2\x88\x92") + base); return (amount < 0) ? (QString::fromUtf8("\xe2\x88\x92") + base) : base;
} }
int64 FormSummary::computeTotalAmount() const { int64 FormSummary::computeTotalAmount() const {
@ -87,6 +87,9 @@ void FormSummary::setupControls() {
_submit->addClickHandler([=] { _submit->addClickHandler([=] {
_delegate->panelSubmit(); _delegate->panelSubmit();
}); });
if (!_invoice) {
_submit->hide();
}
using namespace rpl::mappers; using namespace rpl::mappers;
@ -317,10 +320,12 @@ not_null<RpWidget*> FormSummary::setupContent() {
}, inner->lifetime()); }, inner->lifetime());
setupCover(inner); setupCover(inner);
Settings::AddDivider(inner); if (_invoice) {
setupPrices(inner); Settings::AddDivider(inner);
Settings::AddDivider(inner); setupPrices(inner);
setupSections(inner); Settings::AddDivider(inner);
setupSections(inner);
}
return inner; return inner;
} }

View File

@ -23,7 +23,6 @@ namespace Payments::Ui {
Panel::Panel(not_null<PanelDelegate*> delegate) Panel::Panel(not_null<PanelDelegate*> delegate)
: _delegate(delegate) : _delegate(delegate)
, _widget(std::make_unique<SeparatePanel>()) { , _widget(std::make_unique<SeparatePanel>()) {
_widget->setTitle(tr::lng_payments_checkout_title());
_widget->setInnerSize(st::passportPanelSize); _widget->setInnerSize(st::passportPanelSize);
_widget->setWindowFlag(Qt::WindowStaysOnTopHint, false); _widget->setWindowFlag(Qt::WindowStaysOnTopHint, false);
@ -52,6 +51,7 @@ void Panel::showForm(
const RequestedInformation &current, const RequestedInformation &current,
const PaymentMethodDetails &method, const PaymentMethodDetails &method,
const ShippingOptions &options) { const ShippingOptions &options) {
_widget->setTitle(tr::lng_payments_checkout_title());
auto form = base::make_unique_q<FormSummary>( auto form = base::make_unique_q<FormSummary>(
_widget.get(), _widget.get(),
invoice, invoice,
@ -74,6 +74,7 @@ void Panel::showEditInformation(
const Invoice &invoice, const Invoice &invoice,
const RequestedInformation &current, const RequestedInformation &current,
InformationField field) { InformationField field) {
_widget->setTitle(tr::lng_payments_shipping_address_title());
auto edit = base::make_unique_q<EditInformation>( auto edit = base::make_unique_q<EditInformation>(
_widget.get(), _widget.get(),
invoice, invoice,
@ -83,7 +84,7 @@ void Panel::showEditInformation(
_weakEditInformation = edit.get(); _weakEditInformation = edit.get();
_widget->showInner(std::move(edit)); _widget->showInner(std::move(edit));
_widget->setBackAllowed(true); _widget->setBackAllowed(true);
_weakEditInformation->setFocus(field); _weakEditInformation->setFocusFast(field);
} }
void Panel::showInformationError( void Panel::showInformationError(
@ -125,6 +126,7 @@ void Panel::chooseShippingOption(const ShippingOptions &options) {
} }
void Panel::showEditPaymentMethod(const PaymentMethodDetails &method) { void Panel::showEditPaymentMethod(const PaymentMethodDetails &method) {
_widget->setTitle(tr::lng_payments_card_title());
if (method.native.supported) { if (method.native.supported) {
showEditCard(method.native, CardField::Number); showEditCard(method.native, CardField::Number);
} else if (!showWebview(method.url, true)) { } else if (!showWebview(method.url, true)) {
@ -221,7 +223,7 @@ void Panel::showEditCard(
_weakEditCard = edit.get(); _weakEditCard = edit.get();
_widget->showInner(std::move(edit)); _widget->showInner(std::move(edit));
_widget->setBackAllowed(true); _widget->setBackAllowed(true);
_weakEditCard->setFocus(field); _weakEditCard->setFocusFast(field);
} }
void Panel::showCardError( void Panel::showCardError(
@ -230,11 +232,12 @@ void Panel::showCardError(
if (_weakEditCard) { if (_weakEditCard) {
_weakEditCard->showError(field); _weakEditCard->showError(field);
} else { } else {
showEditCard(native, field); // We cancelled card edit already.
if (_weakEditCard //showEditCard(native, field);
&& field == CardField::AddressCountry) { //if (_weakEditCard
_weakEditCard->showError(field); // && field == CardField::AddressCountry) {
} // _weakEditCard->showError(field);
//}
} }
} }

View File

@ -75,6 +75,8 @@ PRIVATE
payments/ui/payments_edit_information.h payments/ui/payments_edit_information.h
payments/ui/payments_form_summary.cpp payments/ui/payments_form_summary.cpp
payments/ui/payments_form_summary.h payments/ui/payments_form_summary.h
payments/ui/payments_field.cpp
payments/ui/payments_field.h
payments/ui/payments_panel.cpp payments/ui/payments_panel.cpp
payments/ui/payments_panel.h payments/ui/payments_panel.h
payments/ui/payments_panel_data.h payments/ui/payments_panel_data.h