Improve checkout main page design.
BIN
Telegram/Resources/icons/payments/payment_address.png
Normal file
After Width: | Height: | Size: 802 B |
BIN
Telegram/Resources/icons/payments/payment_address@2x.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/payments/payment_address@3x.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/Resources/icons/payments/payment_card.png
Normal file
After Width: | Height: | Size: 357 B |
BIN
Telegram/Resources/icons/payments/payment_card@2x.png
Normal file
After Width: | Height: | Size: 560 B |
BIN
Telegram/Resources/icons/payments/payment_card@3x.png
Normal file
After Width: | Height: | Size: 985 B |
BIN
Telegram/Resources/icons/payments/payment_email.png
Normal file
After Width: | Height: | Size: 949 B |
BIN
Telegram/Resources/icons/payments/payment_email@2x.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
Telegram/Resources/icons/payments/payment_email@3x.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
Telegram/Resources/icons/payments/payment_name.png
Normal file
After Width: | Height: | Size: 473 B |
BIN
Telegram/Resources/icons/payments/payment_name@2x.png
Normal file
After Width: | Height: | Size: 884 B |
BIN
Telegram/Resources/icons/payments/payment_name@3x.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/payments/payment_phone.png
Normal file
After Width: | Height: | Size: 711 B |
BIN
Telegram/Resources/icons/payments/payment_phone@2x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/payments/payment_phone@3x.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
Telegram/Resources/icons/payments/payment_shipping.png
Normal file
After Width: | Height: | Size: 526 B |
BIN
Telegram/Resources/icons/payments/payment_shipping@2x.png
Normal file
After Width: | Height: | Size: 1022 B |
BIN
Telegram/Resources/icons/payments/payment_shipping@3x.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
@ -1865,24 +1865,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_payments_pay_amount" = "Pay {amount}";
|
||||
"lng_payments_payment_method" = "Payment Method";
|
||||
"lng_payments_new_card" = "New Card...";
|
||||
"lng_payments_payment_method_ph" = "Enter your card details";
|
||||
"lng_payments_shipping_address" = "Shipping Information";
|
||||
"lng_payments_shipping_address_ph" = "Enter your shipping information";
|
||||
"lng_payments_shipping_address" = "Shipping Address";
|
||||
"lng_payments_receiver_information" = "Receiver";
|
||||
"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_ph" = "Choose your shipping method";
|
||||
"lng_payments_info_name" = "Name";
|
||||
"lng_payments_info_name_ph" = "Enter your name";
|
||||
"lng_payments_info_email" = "Email";
|
||||
"lng_payments_info_email_ph" = "Enter your email";
|
||||
"lng_payments_info_phone" = "Phone";
|
||||
"lng_payments_info_phone_ph" = "Enter your phone number";
|
||||
"lng_payments_shipping_address_title" = "Shipping Address";
|
||||
"lng_payments_save_shipping_about" = "You can save your shipping information for future use.";
|
||||
"lng_payments_payment_card" = "Payment Card";
|
||||
"lng_payments_cardholder_title" = "Cardholder";
|
||||
"lng_payments_cardholder_about" = "Cardholder Name";
|
||||
"lng_payments_billing_address" = "Billing Address";
|
||||
"lng_payments_zip_code" = "Zip Code";
|
||||
"lng_payments_card_title" = "New Card";
|
||||
"lng_payments_card_number" = "Card Number";
|
||||
"lng_payments_card_holder" = "Cardholder name";
|
||||
"lng_payments_billing_address" = "Billing Information";
|
||||
"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_information" = "Save Information";
|
||||
|
||||
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_domain.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
#include "data/data_user.h" // UserData::isBot.
|
||||
#include "core/local_url_handlers.h" // TryConvertUrlToLocal.
|
||||
#include "core/file_utilities.h" // File::OpenUrl.
|
||||
#include "apiwrap.h"
|
||||
@ -105,6 +106,8 @@ void CheckoutProcess::handleFormUpdate(const FormUpdate &update) {
|
||||
if (!_initialSilentValidation) {
|
||||
showForm();
|
||||
}
|
||||
}, [&](const ThumbnailUpdated &data) {
|
||||
_panel->updateFormThumbnail(data.thumbnail);
|
||||
}, [&](const ValidateFinished &) {
|
||||
if (_initialSilentValidation) {
|
||||
_initialSilentValidation = false;
|
||||
@ -114,16 +117,16 @@ void CheckoutProcess::handleFormUpdate(const FormUpdate &update) {
|
||||
_submitState = SubmitState::Validated;
|
||||
panelSubmit();
|
||||
}
|
||||
}, [&](const PaymentMethodUpdate&) {
|
||||
}, [&](const PaymentMethodUpdate &) {
|
||||
showForm();
|
||||
}, [&](const VerificationNeeded &info) {
|
||||
if (!_panel->showWebview(info.url, false)) {
|
||||
File::OpenUrl(info.url);
|
||||
}, [&](const VerificationNeeded &data) {
|
||||
if (!_panel->showWebview(data.url, false)) {
|
||||
File::OpenUrl(data.url);
|
||||
panelCloseSure();
|
||||
}
|
||||
}, [&](const PaymentFinished &result) {
|
||||
}, [&](const PaymentFinished &data) {
|
||||
const auto weak = base::make_weak(this);
|
||||
_session->api().applyUpdates(result.updates);
|
||||
_session->api().applyUpdates(data.updates);
|
||||
if (weak) {
|
||||
panelCloseSure();
|
||||
}
|
||||
|
@ -9,10 +9,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "main/main_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_error.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/QJsonObject>
|
||||
@ -82,15 +90,110 @@ namespace {
|
||||
Form::Form(not_null<Main::Session*> session, FullMsgId itemId)
|
||||
: _session(session)
|
||||
, _api(&_session->mtp())
|
||||
, _msgId(itemId.msg) {
|
||||
, _msgId(itemId) {
|
||||
fillInvoiceFromMessage();
|
||||
requestForm();
|
||||
}
|
||||
|
||||
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() {
|
||||
_api.request(MTPpayments_GetPaymentForm(
|
||||
MTP_int(_msgId)
|
||||
MTP_int(_msgId.msg)
|
||||
)).done([=](const MTPpayments_PaymentForm &result) {
|
||||
result.match([&](const auto &data) {
|
||||
processForm(data);
|
||||
@ -123,6 +226,8 @@ void Form::processForm(const MTPDpayments_paymentForm &data) {
|
||||
|
||||
void Form::processInvoice(const MTPDinvoice &data) {
|
||||
_invoice = Ui::Invoice{
|
||||
.cover = std::move(_invoice.cover),
|
||||
|
||||
.prices = ParsePrices(data.vprices()),
|
||||
.currency = qs(data.vcurrency()),
|
||||
|
||||
@ -154,6 +259,11 @@ void Form::processDetails(const MTPDpayments_paymentForm &data) {
|
||||
.canSaveCredentials = data.is_can_save_credentials(),
|
||||
.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) {
|
||||
@ -240,7 +350,7 @@ void Form::submit() {
|
||||
| (_shippingOptions.selectedId.isEmpty()
|
||||
? Flag(0)
|
||||
: Flag::f_shipping_option_id)),
|
||||
MTP_int(_msgId),
|
||||
MTP_int(_msgId.msg),
|
||||
MTP_string(_requestedInformationId),
|
||||
MTP_string(_shippingOptions.selectedId),
|
||||
MTP_inputPaymentCredentials(
|
||||
@ -267,7 +377,7 @@ void Form::validateInformation(const Ui::RequestedInformation &information) {
|
||||
_validatedInformation = information;
|
||||
_validateRequestId = _api.request(MTPpayments_ValidateRequestedInfo(
|
||||
MTP_flags(0), // #TODO payments save information
|
||||
MTP_int(_msgId),
|
||||
MTP_int(_msgId.msg),
|
||||
Serialize(information)
|
||||
)).done([=](const MTPpayments_ValidatedRequestedInfo &result) {
|
||||
_validateRequestId = 0;
|
||||
|
@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/weak_ptr.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class Image;
|
||||
|
||||
namespace Stripe {
|
||||
class APIClient;
|
||||
} // namespace Stripe
|
||||
@ -19,6 +21,10 @@ namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Data {
|
||||
class PhotoMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace Payments {
|
||||
|
||||
struct FormDetails {
|
||||
@ -38,6 +44,12 @@ struct FormDetails {
|
||||
}
|
||||
};
|
||||
|
||||
struct ThumbnailLoadProcess {
|
||||
std::shared_ptr<Data::PhotoMedia> view;
|
||||
bool blurredSet = false;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
struct SavedCredentials {
|
||||
QString id;
|
||||
QString title;
|
||||
@ -88,6 +100,9 @@ struct PaymentMethod {
|
||||
};
|
||||
|
||||
struct FormReady {};
|
||||
struct ThumbnailUpdated {
|
||||
QImage thumbnail;
|
||||
};
|
||||
struct ValidateFinished {};
|
||||
struct PaymentMethodUpdate {};
|
||||
struct VerificationNeeded {
|
||||
@ -109,6 +124,7 @@ struct Error {
|
||||
|
||||
struct FormUpdate : std::variant<
|
||||
FormReady,
|
||||
ThumbnailUpdated,
|
||||
ValidateFinished,
|
||||
PaymentMethodUpdate,
|
||||
VerificationNeeded,
|
||||
@ -149,6 +165,18 @@ public:
|
||||
void submit();
|
||||
|
||||
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 processForm(const MTPDpayments_paymentForm &data);
|
||||
void processInvoice(const MTPDinvoice &data);
|
||||
@ -167,9 +195,10 @@ private:
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
MsgId _msgId = 0;
|
||||
FullMsgId _msgId;
|
||||
|
||||
Ui::Invoice _invoice;
|
||||
std::unique_ptr<ThumbnailLoadProcess> _thumbnailLoadProcess;
|
||||
FormDetails _details;
|
||||
Ui::RequestedInformation _savedInformation;
|
||||
PaymentMethod _paymentMethod;
|
||||
|
@ -7,12 +7,52 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
using "ui/basic.style";
|
||||
|
||||
using "passport/passport.style";
|
||||
using "info/info.style";
|
||||
|
||||
paymentsFormPricePadding: margins(22px, 7px, 22px, 6px);
|
||||
paymentsPanelSubmit: RoundButton(passportPasswordSubmit) {
|
||||
paymentsPanelSubmit: RoundButton(defaultActiveButton) {
|
||||
width: 0px;
|
||||
height: 49px;
|
||||
padding: margins(0px, -3px, 0px, 0px);
|
||||
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 }};
|
||||
|
@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "payments/ui/payments_form_summary.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/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
@ -22,7 +22,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Payments::Ui {
|
||||
|
||||
using namespace ::Ui;
|
||||
using namespace Passport::Ui;
|
||||
|
||||
class PanelDelegate;
|
||||
|
||||
@ -45,16 +44,24 @@ FormSummary::FormSummary(
|
||||
this,
|
||||
tr::lng_payments_pay_amount(
|
||||
lt_amount,
|
||||
rpl::single(computeTotalAmount())),
|
||||
rpl::single(formatAmount(computeTotalAmount()))),
|
||||
st::paymentsPanelSubmit) {
|
||||
setupControls();
|
||||
}
|
||||
|
||||
QString FormSummary::computeAmount(int64 amount) const {
|
||||
return FillAmountAndCurrency(amount, _invoice.currency);
|
||||
void FormSummary::updateThumbnail(const QImage &thumbnail) {
|
||||
_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(
|
||||
_invoice.prices,
|
||||
int64(0),
|
||||
@ -71,7 +78,7 @@ QString FormSummary::computeTotalAmount() const {
|
||||
std::plus<>(),
|
||||
&LabeledPrice::price)
|
||||
: int64(0);
|
||||
return computeAmount(total + shipping);
|
||||
return total + shipping;
|
||||
}
|
||||
|
||||
void FormSummary::setupControls() {
|
||||
@ -92,22 +99,125 @@ void FormSummary::setupControls() {
|
||||
_1 + _2 < _3));
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> FormSummary::setupContent() {
|
||||
const auto inner = _scroll->setOwnedWidget(
|
||||
object_ptr<Ui::VerticalLayout>(this));
|
||||
void FormSummary::setupCover(not_null<VerticalLayout*> layout) {
|
||||
struct State {
|
||||
QImage thumbnail;
|
||||
FlatLabel *title = nullptr;
|
||||
FlatLabel *description = nullptr;
|
||||
FlatLabel *seller = nullptr;
|
||||
};
|
||||
|
||||
_scroll->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
inner->resizeToWidth(width);
|
||||
}, inner->lifetime());
|
||||
const auto cover = layout->add(object_ptr<RpWidget>(layout));
|
||||
const auto state = cover->lifetime().make_state<State>();
|
||||
state->title = CreateChild<FlatLabel>(
|
||||
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) {
|
||||
inner->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
inner,
|
||||
price.label + ": " + computeAmount(price.price),
|
||||
st::passportFormPolicy),
|
||||
st::paymentsFormPricePadding);
|
||||
add(price.label, price.price);
|
||||
}
|
||||
const auto selected = ranges::find(
|
||||
_options.list,
|
||||
@ -115,44 +225,35 @@ not_null<Ui::RpWidget*> FormSummary::setupContent() {
|
||||
&ShippingOption::id);
|
||||
if (selected != end(_options.list)) {
|
||||
for (const auto &price : selected->prices) {
|
||||
inner->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
inner,
|
||||
price.label + ": " + computeAmount(price.price),
|
||||
st::passportFormPolicy),
|
||||
st::paymentsFormPricePadding);
|
||||
add(price.label, price.price);
|
||||
}
|
||||
}
|
||||
inner->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
inner,
|
||||
"Total: " + computeTotalAmount(),
|
||||
st::passportFormHeader),
|
||||
st::passportFormHeaderPadding);
|
||||
add(tr::lng_payments_total_label(tr::now), computeTotalAmount(), true);
|
||||
Settings::AddSkip(layout, st::paymentsPricesBottomSkip);
|
||||
}
|
||||
|
||||
inner->add(
|
||||
object_ptr<Ui::BoxContentDivider>(
|
||||
inner,
|
||||
st::passportFormDividerHeight),
|
||||
{ 0, 0, 0, st::passportFormHeaderPadding.top() });
|
||||
void FormSummary::setupSections(not_null<VerticalLayout*> layout) {
|
||||
Settings::AddSkip(layout, st::paymentsSectionsTopSkip);
|
||||
|
||||
const auto method = inner->add(object_ptr<FormRow>(inner));
|
||||
method->addClickHandler([=] {
|
||||
_delegate->panelEditPaymentMethod();
|
||||
});
|
||||
method->updateContent(
|
||||
tr::lng_payments_payment_method(tr::now),
|
||||
(_method.ready
|
||||
? _method.title
|
||||
: tr::lng_payments_payment_method_ph(tr::now)),
|
||||
_method.ready,
|
||||
false,
|
||||
anim::type::instant);
|
||||
const auto add = [&](
|
||||
rpl::producer<QString> title,
|
||||
const QString &label,
|
||||
const style::icon *icon,
|
||||
Fn<void()> handler) {
|
||||
Settings::AddButtonWithLabel(
|
||||
layout,
|
||||
std::move(title),
|
||||
rpl::single(label),
|
||||
st::paymentsSectionButton,
|
||||
icon
|
||||
)->addClickHandler(std::move(handler));
|
||||
};
|
||||
add(
|
||||
tr::lng_payments_payment_method(),
|
||||
_method.title,
|
||||
&st::paymentsIconPaymentMethod,
|
||||
[=] { _delegate->panelEditPaymentMethod(); });
|
||||
if (_invoice.isShippingAddressRequested) {
|
||||
const auto info = inner->add(object_ptr<FormRow>(inner));
|
||||
info->addClickHandler([=] {
|
||||
_delegate->panelEditShippingInformation();
|
||||
});
|
||||
auto list = QStringList();
|
||||
const auto push = [&](const QString &value) {
|
||||
if (!value.isEmpty()) {
|
||||
@ -165,65 +266,61 @@ not_null<Ui::RpWidget*> FormSummary::setupContent() {
|
||||
push(_information.shippingAddress.state);
|
||||
push(_information.shippingAddress.countryIso2);
|
||||
push(_information.shippingAddress.postcode);
|
||||
info->updateContent(
|
||||
tr::lng_payments_shipping_address(tr::now),
|
||||
(list.isEmpty()
|
||||
? tr::lng_payments_shipping_address_ph(tr::now)
|
||||
: list.join(", ")),
|
||||
!list.isEmpty(),
|
||||
false,
|
||||
anim::type::instant);
|
||||
add(
|
||||
tr::lng_payments_shipping_address(),
|
||||
list.join(", "),
|
||||
&st::paymentsIconShippingAddress,
|
||||
[=] { _delegate->panelEditShippingInformation(); });
|
||||
}
|
||||
if (!_options.list.empty()) {
|
||||
const auto options = inner->add(object_ptr<FormRow>(inner));
|
||||
options->addClickHandler([=] {
|
||||
_delegate->panelChooseShippingOption();
|
||||
});
|
||||
options->updateContent(
|
||||
tr::lng_payments_shipping_method(tr::now),
|
||||
(selected != end(_options.list)
|
||||
? selected->title
|
||||
: tr::lng_payments_shipping_method_ph(tr::now)),
|
||||
(selected != end(_options.list)),
|
||||
false,
|
||||
anim::type::instant);
|
||||
const auto selected = ranges::find(
|
||||
_options.list,
|
||||
_options.selectedId,
|
||||
&ShippingOption::id);
|
||||
add(
|
||||
tr::lng_payments_shipping_method(),
|
||||
(selected != end(_options.list)) ? selected->title : QString(),
|
||||
&st::paymentsIconShippingMethod,
|
||||
[=] { _delegate->panelChooseShippingOption(); });
|
||||
}
|
||||
if (_invoice.isNameRequested) {
|
||||
const auto name = inner->add(object_ptr<FormRow>(inner));
|
||||
name->addClickHandler([=] { _delegate->panelEditName(); });
|
||||
name->updateContent(
|
||||
tr::lng_payments_info_name(tr::now),
|
||||
(_information.name.isEmpty()
|
||||
? tr::lng_payments_info_name_ph(tr::now)
|
||||
: _information.name),
|
||||
!_information.name.isEmpty(),
|
||||
false,
|
||||
anim::type::instant);
|
||||
add(
|
||||
tr::lng_payments_info_name(),
|
||||
_information.name,
|
||||
&st::paymentsIconName,
|
||||
[=] { _delegate->panelEditName(); });
|
||||
}
|
||||
if (_invoice.isEmailRequested) {
|
||||
const auto email = inner->add(object_ptr<FormRow>(inner));
|
||||
email->addClickHandler([=] { _delegate->panelEditEmail(); });
|
||||
email->updateContent(
|
||||
tr::lng_payments_info_email(tr::now),
|
||||
(_information.email.isEmpty()
|
||||
? tr::lng_payments_info_email_ph(tr::now)
|
||||
: _information.email),
|
||||
!_information.email.isEmpty(),
|
||||
false,
|
||||
anim::type::instant);
|
||||
add(
|
||||
tr::lng_payments_info_email(),
|
||||
_information.email,
|
||||
&st::paymentsIconEmail,
|
||||
[=] { _delegate->panelEditEmail(); });
|
||||
}
|
||||
if (_invoice.isPhoneRequested) {
|
||||
const auto phone = inner->add(object_ptr<FormRow>(inner));
|
||||
phone->addClickHandler([=] { _delegate->panelEditPhone(); });
|
||||
phone->updateContent(
|
||||
tr::lng_payments_info_phone(tr::now),
|
||||
(_information.phone.isEmpty()
|
||||
? tr::lng_payments_info_phone_ph(tr::now)
|
||||
: _information.phone),
|
||||
!_information.phone.isEmpty(),
|
||||
false,
|
||||
anim::type::instant);
|
||||
add(
|
||||
tr::lng_payments_info_phone(),
|
||||
_information.phone,
|
||||
&st::paymentsIconPhone,
|
||||
[=] { _delegate->panelEditPhone(); });
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ namespace Ui {
|
||||
class ScrollArea;
|
||||
class FadeShadow;
|
||||
class RoundButton;
|
||||
class VerticalLayout;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Payments::Ui {
|
||||
@ -33,15 +34,20 @@ public:
|
||||
const ShippingOptions &options,
|
||||
not_null<PanelDelegate*> delegate);
|
||||
|
||||
void updateThumbnail(const QImage &thumbnail);
|
||||
|
||||
private:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
void setupControls();
|
||||
[[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();
|
||||
|
||||
[[nodiscard]] QString computeAmount(int64 amount) const;
|
||||
[[nodiscard]] QString computeTotalAmount() const;
|
||||
[[nodiscard]] QString formatAmount(int64 amount) const;
|
||||
[[nodiscard]] int64 computeTotalAmount() const;
|
||||
|
||||
const not_null<PanelDelegate*> _delegate;
|
||||
Invoice _invoice;
|
||||
@ -52,6 +58,7 @@ private:
|
||||
object_ptr<FadeShadow> _topShadow;
|
||||
object_ptr<FadeShadow> _bottomShadow;
|
||||
object_ptr<RoundButton> _submit;
|
||||
rpl::event_stream<QImage> _thumbnails;
|
||||
|
||||
};
|
||||
|
||||
|
@ -52,17 +52,24 @@ void Panel::showForm(
|
||||
const RequestedInformation ¤t,
|
||||
const PaymentMethodDetails &method,
|
||||
const ShippingOptions &options) {
|
||||
_widget->showInner(
|
||||
base::make_unique_q<FormSummary>(
|
||||
_widget.get(),
|
||||
invoice,
|
||||
current,
|
||||
method,
|
||||
options,
|
||||
_delegate));
|
||||
auto form = base::make_unique_q<FormSummary>(
|
||||
_widget.get(),
|
||||
invoice,
|
||||
current,
|
||||
method,
|
||||
options,
|
||||
_delegate);
|
||||
_weakFormSummary = form.get();
|
||||
_widget->showInner(std::move(form));
|
||||
_widget->setBackAllowed(false);
|
||||
}
|
||||
|
||||
void Panel::updateFormThumbnail(const QImage &thumbnail) {
|
||||
if (_weakFormSummary) {
|
||||
_weakFormSummary->updateThumbnail(thumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
void Panel::showEditInformation(
|
||||
const Invoice &invoice,
|
||||
const RequestedInformation ¤t,
|
||||
|
@ -28,6 +28,7 @@ struct RequestedInformation;
|
||||
struct ShippingOptions;
|
||||
enum class InformationField;
|
||||
enum class CardField;
|
||||
class FormSummary;
|
||||
class EditInformation;
|
||||
class EditCard;
|
||||
struct PaymentMethodDetails;
|
||||
@ -45,6 +46,7 @@ public:
|
||||
const RequestedInformation ¤t,
|
||||
const PaymentMethodDetails &method,
|
||||
const ShippingOptions &options);
|
||||
void updateFormThumbnail(const QImage &thumbnail);
|
||||
void showEditInformation(
|
||||
const Invoice &invoice,
|
||||
const RequestedInformation ¤t,
|
||||
@ -78,6 +80,7 @@ private:
|
||||
const not_null<PanelDelegate*> _delegate;
|
||||
std::unique_ptr<SeparatePanel> _widget;
|
||||
std::unique_ptr<Webview::Window> _webview;
|
||||
QPointer<FormSummary> _weakFormSummary;
|
||||
QPointer<EditInformation> _weakEditInformation;
|
||||
QPointer<EditCard> _weakEditCard;
|
||||
|
||||
|
@ -14,7 +14,16 @@ struct LabeledPrice {
|
||||
int64 price = 0;
|
||||
};
|
||||
|
||||
struct Cover {
|
||||
QString title;
|
||||
QString description;
|
||||
QString seller;
|
||||
QImage thumbnail;
|
||||
};
|
||||
|
||||
struct Invoice {
|
||||
Cover cover;
|
||||
|
||||
std::vector<LabeledPrice> prices;
|
||||
QString currency;
|
||||
|
||||
|