| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610 |
- /*
- 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_form_summary.h"
- #include "payments/ui/payments_panel_delegate.h"
- #include "settings/settings_common.h" // AddButtonWithLabel.
- #include "ui/widgets/scroll_area.h"
- #include "ui/widgets/buttons.h"
- #include "ui/widgets/labels.h"
- #include "ui/wrap/vertical_layout.h"
- #include "ui/vertical_list.h"
- #include "ui/wrap/fade_wrap.h"
- #include "ui/text/format_values.h"
- #include "ui/text/text_utilities.h"
- #include "countries/countries_instance.h"
- #include "lang/lang_keys.h"
- #include "base/unixtime.h"
- #include "styles/style_payments.h"
- #include "styles/style_passport.h"
- namespace Payments::Ui {
- namespace {
- constexpr auto kLightOpacity = 0.1;
- constexpr auto kLightRippleOpacity = 0.11;
- constexpr auto kChosenOpacity = 0.8;
- constexpr auto kChosenRippleOpacity = 0.5;
- [[nodiscard]] Fn<QColor()> TransparentColor(
- const style::color &c,
- float64 opacity) {
- return [&c, opacity] {
- return QColor(
- c->c.red(),
- c->c.green(),
- c->c.blue(),
- c->c.alpha() * opacity);
- };
- }
- [[nodiscard]] style::RoundButton TipButtonStyle(
- const style::RoundButton &original,
- const style::color &light,
- const style::color &ripple) {
- auto result = original;
- result.textBg = light;
- result.ripple.color = ripple;
- return result;
- }
- } // namespace
- using namespace ::Ui;
- class PanelDelegate;
- FormSummary::FormSummary(
- QWidget *parent,
- const Invoice &invoice,
- const RequestedInformation ¤t,
- const PaymentMethodDetails &method,
- const ShippingOptions &options,
- not_null<PanelDelegate*> delegate,
- int scrollTop)
- : _delegate(delegate)
- , _invoice(invoice)
- , _method(method)
- , _options(options)
- , _information(current)
- , _scroll(this, st::passportPanelScroll)
- , _layout(_scroll->setOwnedWidget(object_ptr<VerticalLayout>(this)))
- , _topShadow(this)
- , _bottomShadow(this)
- , _submit(_invoice.receipt.paid
- ? object_ptr<RoundButton>(nullptr)
- : object_ptr<RoundButton>(
- this,
- tr::lng_payments_pay_amount(
- lt_amount,
- rpl::single(formatAmount(computeTotalAmount()))),
- st::paymentsPanelSubmit))
- , _cancel(
- this,
- (_invoice.receipt.paid
- ? tr::lng_about_done()
- : tr::lng_cancel()),
- st::paymentsPanelButton)
- , _tipLightBg(TransparentColor(st::paymentsTipActive, kLightOpacity))
- , _tipLightRipple(
- TransparentColor(st::paymentsTipActive, kLightRippleOpacity))
- , _tipChosenBg(TransparentColor(st::paymentsTipActive, kChosenOpacity))
- , _tipChosenRipple(
- TransparentColor(st::paymentsTipActive, kChosenRippleOpacity))
- , _tipButton(TipButtonStyle(
- st::paymentsTipButton,
- _tipLightBg.color(),
- _tipLightRipple.color()))
- , _tipChosen(TipButtonStyle(
- st::paymentsTipChosen,
- _tipChosenBg.color(),
- _tipChosenRipple.color()))
- , _initialScrollTop(scrollTop) {
- setupControls();
- }
- rpl::producer<int> FormSummary::scrollTopValue() const {
- return _scroll->scrollTopValue();
- }
- bool FormSummary::showCriticalError(const TextWithEntities &text) {
- if (_invoice
- || (_scroll->height() - _layout->height()
- < st::paymentsPanelSize.height() / 2)) {
- return false;
- }
- Ui::AddSkip(_layout.get(), st::paymentsPricesTopSkip);
- _layout->add(object_ptr<FlatLabel>(
- _layout.get(),
- rpl::single(text),
- st::paymentsCriticalError));
- return true;
- }
- int FormSummary::contentHeight() const {
- return _invoice ? _scroll->height() : _layout->height();
- }
- void FormSummary::updateThumbnail(const QImage &thumbnail) {
- _invoice.cover.thumbnail = thumbnail;
- _thumbnails.fire_copy(thumbnail);
- }
- QString FormSummary::formatAmount(
- int64 amount,
- bool forceStripDotZero) const {
- return FillAmountAndCurrency(
- amount,
- _invoice.currency,
- forceStripDotZero);
- }
- int64 FormSummary::computeTotalAmount() const {
- const auto total = ranges::accumulate(
- _invoice.prices,
- int64(0),
- std::plus<>(),
- &LabeledPrice::price);
- const auto selected = ranges::find(
- _options.list,
- _options.selectedId,
- &ShippingOption::id);
- const auto shipping = (selected != end(_options.list))
- ? ranges::accumulate(
- selected->prices,
- int64(0),
- std::plus<>(),
- &LabeledPrice::price)
- : int64(0);
- return total + shipping + _invoice.tipsSelected;
- }
- void FormSummary::setupControls() {
- setupContent(_layout.get());
- if (_submit) {
- _submit->setTextTransform(
- Ui::RoundButton::TextTransform::NoTransform);
- _submit->addClickHandler([=] {
- _delegate->panelSubmit();
- });
- }
- _cancel->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
- _cancel->addClickHandler([=] {
- _delegate->panelRequestClose();
- });
- if (!_invoice) {
- if (_submit) {
- _submit->hide();
- }
- _cancel->hide();
- }
- using namespace rpl::mappers;
- _topShadow->toggleOn(
- _scroll->scrollTopValue() | rpl::map(_1 > 0));
- _bottomShadow->toggleOn(rpl::combine(
- _scroll->scrollTopValue(),
- _scroll->heightValue(),
- _layout->heightValue(),
- _1 + _2 < _3));
- rpl::merge(
- (_submit ? _submit->widthValue() : rpl::single(0)),
- _cancel->widthValue()
- ) | rpl::skip(2) | rpl::start_with_next([=] {
- updateControlsGeometry();
- }, lifetime());
- }
- void FormSummary::setupCover(not_null<VerticalLayout*> layout) {
- struct State {
- QImage thumbnail;
- FlatLabel *title = nullptr;
- FlatLabel *description = nullptr;
- FlatLabel *seller = nullptr;
- };
- 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,
- rpl::single(_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 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) {
- const auto addRow = [&](
- const QString &label,
- const TextWithEntities &value,
- bool full = false) {
- const auto &st = full
- ? st::paymentsFullPriceAmount
- : st::paymentsPriceAmount;
- const auto right = CreateChild<FlatLabel>(
- layout.get(),
- rpl::single(value),
- 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->textMaxWidth()
- + 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());
- return right;
- };
- Ui::AddSkip(layout, st::paymentsPricesTopSkip);
- if (_invoice.receipt) {
- addRow(
- tr::lng_payments_date_label(tr::now),
- { langDateTime(base::unixtime::parse(_invoice.receipt.date)) },
- true);
- Ui::AddSkip(layout, st::paymentsPricesBottomSkip);
- Ui::AddDivider(layout);
- Ui::AddSkip(layout, st::paymentsPricesBottomSkip);
- }
- const auto add = [&](
- const QString &label,
- int64 amount,
- bool full = false) {
- addRow(label, { formatAmount(amount) }, full);
- };
- for (const auto &price : _invoice.prices) {
- add(price.label, price.price);
- }
- const auto selected = ranges::find(
- _options.list,
- _options.selectedId,
- &ShippingOption::id);
- if (selected != end(_options.list)) {
- for (const auto &price : selected->prices) {
- add(price.label, price.price);
- }
- }
- const auto computedTotal = computeTotalAmount();
- const auto total = _invoice.receipt.paid
- ? _invoice.receipt.totalAmount
- : computedTotal;
- if (_invoice.receipt.paid) {
- if (const auto tips = total - computedTotal) {
- add(tr::lng_payments_tips_label(tr::now), tips);
- }
- } else if (_invoice.tipsMax > 0) {
- const auto text = formatAmount(_invoice.tipsSelected);
- const auto label = addRow(
- tr::lng_payments_tips_label(tr::now),
- Ui::Text::Link(text));
- label->overrideLinkClickHandler([=] {
- _delegate->panelChooseTips();
- });
- setupSuggestedTips(layout);
- }
- add(tr::lng_payments_total_label(tr::now), total, true);
- Ui::AddSkip(layout, st::paymentsPricesBottomSkip);
- }
- void FormSummary::setupSuggestedTips(not_null<VerticalLayout*> layout) {
- if (_invoice.suggestedTips.empty()) {
- return;
- }
- struct Button {
- RoundButton *widget = nullptr;
- int minWidth = 0;
- };
- struct State {
- std::vector<Button> buttons;
- int maxWidth = 0;
- };
- const auto outer = layout->add(
- object_ptr<RpWidget>(layout),
- st::paymentsTipButtonsPadding);
- const auto state = outer->lifetime().make_state<State>();
- for (const auto amount : _invoice.suggestedTips) {
- const auto selected = (amount == _invoice.tipsSelected);
- const auto &st = selected
- ? _tipChosen
- : _tipButton;
- state->buttons.push_back(Button{
- .widget = CreateChild<RoundButton>(
- outer,
- rpl::single(formatAmount(amount, true)),
- st),
- });
- auto &button = state->buttons.back();
- button.widget->show();
- button.widget->setClickedCallback([=] {
- _delegate->panelChangeTips(selected ? 0 : amount);
- });
- button.minWidth = button.widget->width();
- state->maxWidth = std::max(state->maxWidth, button.minWidth);
- }
- outer->widthValue(
- ) | rpl::filter([=](int outerWidth) {
- return outerWidth >= state->maxWidth;
- }) | rpl::start_with_next([=](int outerWidth) {
- const auto skip = st::paymentsTipSkip;
- const auto &buttons = state->buttons;
- auto left = outerWidth;
- auto height = 0;
- auto rowStart = 0;
- auto rowEnd = 0;
- auto buttonWidths = std::vector<float64>();
- const auto layoutRow = [&] {
- const auto count = rowEnd - rowStart;
- if (!count) {
- return;
- }
- buttonWidths.resize(count);
- ranges::fill(buttonWidths, 0.);
- auto available = float64(outerWidth - (count - 1) * skip);
- auto zeros = count;
- do {
- const auto started = zeros;
- const auto average = available / zeros;
- for (auto i = 0; i != count; ++i) {
- if (buttonWidths[i] > 0.) {
- continue;
- }
- const auto min = buttons[rowStart + i].minWidth;
- if (min > average) {
- buttonWidths[i] = min;
- available -= min;
- --zeros;
- }
- }
- if (started == zeros) {
- for (auto i = 0; i != count; ++i) {
- if (!buttonWidths[i]) {
- buttonWidths[i] = average;
- }
- }
- break;
- }
- } while (zeros > 0);
- auto x = 0.;
- for (auto i = 0; i != count; ++i) {
- const auto button = buttons[rowStart + i].widget;
- auto right = x + buttonWidths[i];
- button->setFullWidth(
- int(base::SafeRound(right) - base::SafeRound(x)));
- button->moveToLeft(
- int(base::SafeRound(x)),
- height,
- outerWidth);
- x = right + skip;
- }
- height += buttons[0].widget->height() + skip;
- };
- for (const auto &button : buttons) {
- if (button.minWidth <= left) {
- left -= button.minWidth + skip;
- ++rowEnd;
- continue;
- }
- layoutRow();
- rowStart = rowEnd++;
- left = outerWidth - button.minWidth - skip;
- }
- layoutRow();
- outer->resize(outerWidth, height - skip);
- }, outer->lifetime());
- }
- void FormSummary::setupSections(not_null<VerticalLayout*> layout) {
- Ui::AddSkip(layout, st::paymentsSectionsTopSkip);
- const auto add = [&](
- rpl::producer<QString> title,
- const QString &label,
- const style::icon *icon,
- Fn<void()> handler) {
- const auto button = Settings::AddButtonWithLabel(
- layout,
- std::move(title),
- rpl::single(label),
- st::paymentsSectionButton,
- { .icon = icon });
- button->addClickHandler(std::move(handler));
- if (_invoice.receipt) {
- button->setAttribute(Qt::WA_TransparentForMouseEvents);
- }
- };
- add(
- tr::lng_payments_payment_method(),
- (_method.savedMethods.empty()
- ? QString()
- : _method.savedMethods[_method.savedMethodIndex].title),
- &st::paymentsIconPaymentMethod,
- [=] { _delegate->panelEditPaymentMethod(); });
- if (_invoice.isShippingAddressRequested) {
- auto list = QStringList();
- const auto push = [&](const QString &value) {
- if (!value.isEmpty()) {
- list.push_back(value);
- }
- };
- push(_information.shippingAddress.address1);
- push(_information.shippingAddress.address2);
- push(_information.shippingAddress.city);
- push(_information.shippingAddress.state);
- push(Countries::Instance().countryNameByISO2(
- _information.shippingAddress.countryIso2));
- push(_information.shippingAddress.postcode);
- add(
- tr::lng_payments_shipping_address(),
- list.join(", "),
- &st::paymentsIconShippingAddress,
- [=] { _delegate->panelEditShippingInformation(); });
- }
- if (!_options.list.empty()) {
- 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) {
- add(
- tr::lng_payments_info_name(),
- _information.name,
- &st::paymentsIconName,
- [=] { _delegate->panelEditName(); });
- }
- if (_invoice.isEmailRequested) {
- add(
- tr::lng_payments_info_email(),
- _information.email,
- &st::paymentsIconEmail,
- [=] { _delegate->panelEditEmail(); });
- }
- if (_invoice.isPhoneRequested) {
- add(
- tr::lng_payments_info_phone(),
- (_information.phone.isEmpty()
- ? QString()
- : Ui::FormatPhone(_information.phone)),
- &st::paymentsIconPhone,
- [=] { _delegate->panelEditPhone(); });
- }
- Ui::AddSkip(layout, st::paymentsSectionsTopSkip);
- }
- void FormSummary::setupContent(not_null<VerticalLayout*> layout) {
- _scroll->widthValue(
- ) | rpl::start_with_next([=](int width) {
- layout->resizeToWidth(width);
- }, layout->lifetime());
- setupCover(layout);
- if (_invoice) {
- Ui::AddDivider(layout);
- setupPrices(layout);
- Ui::AddDivider(layout);
- setupSections(layout);
- }
- }
- void FormSummary::resizeEvent(QResizeEvent *e) {
- updateControlsGeometry();
- }
- void FormSummary::updateControlsGeometry() {
- const auto &padding = st::paymentsPanelPadding;
- const auto buttonsHeight = padding.top()
- + _cancel->height()
- + padding.bottom();
- const auto buttonsTop = height() - buttonsHeight;
- _scroll->setGeometry(0, 0, width(), buttonsTop);
- _topShadow->resizeToWidth(width());
- _topShadow->moveToLeft(0, 0);
- _bottomShadow->resizeToWidth(width());
- _bottomShadow->moveToLeft(0, buttonsTop - st::lineWidth);
- auto right = padding.right();
- if (_submit) {
- _submit->moveToRight(right, buttonsTop + padding.top());
- right += _submit->width() + padding.left();
- }
- _cancel->moveToRight(right, buttonsTop + padding.top());
- _scroll->updateBars();
- if (buttonsTop > 0 && width() > 0) {
- if (const auto top = base::take(_initialScrollTop)) {
- _scroll->scrollToY(top);
- }
- }
- }
- } // namespace Payments::Ui
|