/* 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" #include "ui/widgets/scroll_area.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/wrap/vertical_layout.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 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 delegate, int scrollTop) : _delegate(delegate) , _invoice(invoice) , _method(method) , _options(options) , _information(current) , _scroll(this, st::passportPanelScroll) , _layout(_scroll->setOwnedWidget(object_ptr(this))) , _topShadow(this) , _bottomShadow(this) , _submit(_invoice.receipt.paid ? object_ptr(nullptr) : object_ptr( 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 FormSummary::scrollTopValue() const { return _scroll->scrollTopValue(); } bool FormSummary::showCriticalError(const TextWithEntities &text) { if (_invoice || (_scroll->height() - _layout->height() < st::paymentsPanelSize.height() / 2)) { return false; } Settings::AddSkip(_layout.get(), st::paymentsPricesTopSkip); _layout->add(object_ptr( _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->addClickHandler([=] { _delegate->panelSubmit(); }); } _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 layout) { struct State { QImage thumbnail; FlatLabel *title = nullptr; FlatLabel *description = nullptr; FlatLabel *seller = nullptr; }; const auto cover = layout->add(object_ptr(layout)); const auto state = cover->lifetime().make_state(); state->title = CreateChild( cover, _invoice.cover.title, st::paymentsTitle); state->description = CreateChild( cover, _invoice.cover.description, st::paymentsDescription); state->seller = CreateChild( 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 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( layout.get(), rpl::single(value), st); const auto &padding = st::paymentsPricePadding; const auto left = layout->add( object_ptr( 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()); return right; }; Settings::AddSkip(layout, st::paymentsPricesTopSkip); if (_invoice.receipt) { addRow( tr::lng_payments_date_label(tr::now), { langDateTime(base::unixtime::parse(_invoice.receipt.date)) }, true); Settings::AddSkip(layout, st::paymentsPricesBottomSkip); Settings::AddDivider(layout); Settings::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, "internal:edit_tips")); label->setClickHandlerFilter([=](auto&&...) { _delegate->panelChooseTips(); return false; }); setupSuggestedTips(layout); } add(tr::lng_payments_total_label(tr::now), total, true); Settings::AddSkip(layout, st::paymentsPricesBottomSkip); } void FormSummary::setupSuggestedTips(not_null layout) { if (_invoice.suggestedTips.empty()) { return; } struct Button { RoundButton *widget = nullptr; int minWidth = 0; }; struct State { std::vector