Add nice tips buttons.
This commit is contained in:
parent
d55d7f37d7
commit
b6c86fd298
|
@ -49,6 +49,23 @@ paymentsPricesTopSkip: 12px;
|
||||||
paymentsPricesBottomSkip: 13px;
|
paymentsPricesBottomSkip: 13px;
|
||||||
paymentsPricePadding: margins(28px, 6px, 28px, 5px);
|
paymentsPricePadding: margins(28px, 6px, 28px, 5px);
|
||||||
|
|
||||||
|
paymentsTipSkip: 8px;
|
||||||
|
paymentsTipButton: RoundButton(defaultLightButton) {
|
||||||
|
textFg: paymentsTipActive;
|
||||||
|
textFgOver: paymentsTipActive;
|
||||||
|
textBgOver: transparent;
|
||||||
|
|
||||||
|
width: -16px;
|
||||||
|
height: 28px;
|
||||||
|
textTop: 5px;
|
||||||
|
}
|
||||||
|
paymentsTipChosen: RoundButton(paymentsTipButton) {
|
||||||
|
textFg: windowFgActive;
|
||||||
|
textFgOver: windowFgActive;
|
||||||
|
textBgOver: transparent;
|
||||||
|
}
|
||||||
|
paymentsTipButtonsPadding: margins(26px, 6px, 26px, 6px);
|
||||||
|
|
||||||
paymentsSectionsTopSkip: 11px;
|
paymentsSectionsTopSkip: 11px;
|
||||||
paymentsSectionButton: SettingsButton(infoProfileButton) {
|
paymentsSectionButton: SettingsButton(infoProfileButton) {
|
||||||
padding: margins(68px, 11px, 14px, 9px);
|
padding: margins(68px, 11px, 14px, 9px);
|
||||||
|
|
|
@ -27,6 +27,36 @@ QString formatPhone(QString phone); // #TODO
|
||||||
} // namespace App
|
} // namespace App
|
||||||
|
|
||||||
namespace Payments::Ui {
|
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;
|
using namespace ::Ui;
|
||||||
|
|
||||||
|
@ -38,7 +68,8 @@ FormSummary::FormSummary(
|
||||||
const RequestedInformation ¤t,
|
const RequestedInformation ¤t,
|
||||||
const PaymentMethodDetails &method,
|
const PaymentMethodDetails &method,
|
||||||
const ShippingOptions &options,
|
const ShippingOptions &options,
|
||||||
not_null<PanelDelegate*> delegate)
|
not_null<PanelDelegate*> delegate,
|
||||||
|
int scrollTop)
|
||||||
: _delegate(delegate)
|
: _delegate(delegate)
|
||||||
, _invoice(invoice)
|
, _invoice(invoice)
|
||||||
, _method(method)
|
, _method(method)
|
||||||
|
@ -56,21 +87,45 @@ FormSummary::FormSummary(
|
||||||
rpl::single(formatAmount(computeTotalAmount()))),
|
rpl::single(formatAmount(computeTotalAmount()))),
|
||||||
st::paymentsPanelSubmit))
|
st::paymentsPanelSubmit))
|
||||||
, _cancel(
|
, _cancel(
|
||||||
this,
|
this,
|
||||||
(_invoice.receipt.paid
|
(_invoice.receipt.paid
|
||||||
? tr::lng_about_done()
|
? tr::lng_about_done()
|
||||||
: tr::lng_cancel()),
|
: tr::lng_cancel()),
|
||||||
st::paymentsPanelButton) {
|
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();
|
setupControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> FormSummary::scrollTopValue() const {
|
||||||
|
return _scroll->scrollTopValue();
|
||||||
|
}
|
||||||
|
|
||||||
void FormSummary::updateThumbnail(const QImage &thumbnail) {
|
void FormSummary::updateThumbnail(const QImage &thumbnail) {
|
||||||
_invoice.cover.thumbnail = thumbnail;
|
_invoice.cover.thumbnail = thumbnail;
|
||||||
_thumbnails.fire_copy(thumbnail);
|
_thumbnails.fire_copy(thumbnail);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString FormSummary::formatAmount(int64 amount) const {
|
QString FormSummary::formatAmount(
|
||||||
return FillAmountAndCurrency(amount, _invoice.currency);
|
int64 amount,
|
||||||
|
bool forceStripDotZero) const {
|
||||||
|
return FillAmountAndCurrency(
|
||||||
|
amount,
|
||||||
|
_invoice.currency,
|
||||||
|
forceStripDotZero);
|
||||||
}
|
}
|
||||||
|
|
||||||
int64 FormSummary::computeTotalAmount() const {
|
int64 FormSummary::computeTotalAmount() const {
|
||||||
|
@ -279,9 +334,7 @@ void FormSummary::setupPrices(not_null<VerticalLayout*> layout) {
|
||||||
add(tr::lng_payments_tips_label(tr::now), tips);
|
add(tr::lng_payments_tips_label(tr::now), tips);
|
||||||
}
|
}
|
||||||
} else if (_invoice.tipsMax > 0) {
|
} else if (_invoice.tipsMax > 0) {
|
||||||
const auto text = _invoice.tipsSelected
|
const auto text = formatAmount(_invoice.tipsSelected);
|
||||||
? formatAmount(_invoice.tipsSelected)
|
|
||||||
: tr::lng_payments_tips_add(tr::now);
|
|
||||||
const auto label = addRow(
|
const auto label = addRow(
|
||||||
tr::lng_payments_tips_label(tr::now),
|
tr::lng_payments_tips_label(tr::now),
|
||||||
Ui::Text::Link(text, "internal:edit_tips"));
|
Ui::Text::Link(text, "internal:edit_tips"));
|
||||||
|
@ -289,12 +342,116 @@ void FormSummary::setupPrices(not_null<VerticalLayout*> layout) {
|
||||||
_delegate->panelChooseTips();
|
_delegate->panelChooseTips();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
setupSuggestedTips(layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
add(tr::lng_payments_total_label(tr::now), total, true);
|
add(tr::lng_payments_total_label(tr::now), total, true);
|
||||||
Settings::AddSkip(layout, st::paymentsPricesBottomSkip);
|
Settings::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 text = formatAmount(amount, true);
|
||||||
|
const auto &st = (amount == _invoice.tipsSelected)
|
||||||
|
? _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(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(std::round(right) - std::round(x)));
|
||||||
|
button->moveToLeft(int(std::round(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) {
|
void FormSummary::setupSections(not_null<VerticalLayout*> layout) {
|
||||||
Settings::AddSkip(layout, st::paymentsSectionsTopSkip);
|
Settings::AddSkip(layout, st::paymentsSectionsTopSkip);
|
||||||
|
|
||||||
|
@ -419,6 +576,12 @@ void FormSummary::updateControlsGeometry() {
|
||||||
_cancel->moveToRight(right, buttonsTop + padding.top());
|
_cancel->moveToRight(right, buttonsTop + padding.top());
|
||||||
|
|
||||||
_scroll->updateBars();
|
_scroll->updateBars();
|
||||||
|
|
||||||
|
if (buttonsTop > 0 && width() > 0) {
|
||||||
|
if (const auto top = base::take(_initialScrollTop)) {
|
||||||
|
_scroll->scrollToY(top);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Payments::Ui
|
} // namespace Payments::Ui
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
#include "payments/ui/payments_panel_data.h"
|
#include "payments/ui/payments_panel_data.h"
|
||||||
#include "base/object_ptr.h"
|
#include "base/object_ptr.h"
|
||||||
|
#include "styles/style_widgets.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class ScrollArea;
|
class ScrollArea;
|
||||||
|
@ -32,9 +33,11 @@ public:
|
||||||
const RequestedInformation ¤t,
|
const RequestedInformation ¤t,
|
||||||
const PaymentMethodDetails &method,
|
const PaymentMethodDetails &method,
|
||||||
const ShippingOptions &options,
|
const ShippingOptions &options,
|
||||||
not_null<PanelDelegate*> delegate);
|
not_null<PanelDelegate*> delegate,
|
||||||
|
int scrollTop);
|
||||||
|
|
||||||
void updateThumbnail(const QImage &thumbnail);
|
void updateThumbnail(const QImage &thumbnail);
|
||||||
|
[[nodiscard]] rpl::producer<int> scrollTopValue() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
@ -43,10 +46,13 @@ private:
|
||||||
[[nodiscard]] not_null<Ui::RpWidget*> setupContent();
|
[[nodiscard]] not_null<Ui::RpWidget*> setupContent();
|
||||||
void setupCover(not_null<VerticalLayout*> layout);
|
void setupCover(not_null<VerticalLayout*> layout);
|
||||||
void setupPrices(not_null<VerticalLayout*> layout);
|
void setupPrices(not_null<VerticalLayout*> layout);
|
||||||
|
void setupSuggestedTips(not_null<VerticalLayout*> layout);
|
||||||
void setupSections(not_null<VerticalLayout*> layout);
|
void setupSections(not_null<VerticalLayout*> layout);
|
||||||
void updateControlsGeometry();
|
void updateControlsGeometry();
|
||||||
|
|
||||||
[[nodiscard]] QString formatAmount(int64 amount) const;
|
[[nodiscard]] QString formatAmount(
|
||||||
|
int64 amount,
|
||||||
|
bool forceStripDotZero = false) const;
|
||||||
[[nodiscard]] int64 computeTotalAmount() const;
|
[[nodiscard]] int64 computeTotalAmount() const;
|
||||||
|
|
||||||
const not_null<PanelDelegate*> _delegate;
|
const not_null<PanelDelegate*> _delegate;
|
||||||
|
@ -61,6 +67,14 @@ private:
|
||||||
object_ptr<RoundButton> _cancel;
|
object_ptr<RoundButton> _cancel;
|
||||||
rpl::event_stream<QImage> _thumbnails;
|
rpl::event_stream<QImage> _thumbnails;
|
||||||
|
|
||||||
|
style::complex_color _tipLightBg;
|
||||||
|
style::complex_color _tipLightRipple;
|
||||||
|
style::complex_color _tipChosenBg;
|
||||||
|
style::complex_color _tipChosenRipple;
|
||||||
|
style::RoundButton _tipButton;
|
||||||
|
style::RoundButton _tipChosen;
|
||||||
|
int _initialScrollTop = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Payments::Ui
|
} // namespace Payments::Ui
|
||||||
|
|
|
@ -66,10 +66,12 @@ void Panel::showForm(
|
||||||
current,
|
current,
|
||||||
method,
|
method,
|
||||||
options,
|
options,
|
||||||
_delegate);
|
_delegate,
|
||||||
|
_formScrollTop.current());
|
||||||
_weakFormSummary = form.get();
|
_weakFormSummary = form.get();
|
||||||
_widget->showInner(std::move(form));
|
_widget->showInner(std::move(form));
|
||||||
_widget->setBackAllowed(false);
|
_widget->setBackAllowed(false);
|
||||||
|
_formScrollTop = _weakFormSummary->scrollTopValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::updateFormThumbnail(const QImage &thumbnail) {
|
void Panel::updateFormThumbnail(const QImage &thumbnail) {
|
||||||
|
@ -204,7 +206,7 @@ void Panel::chooseTips(const Invoice &invoice) {
|
||||||
case 8: return "KZT";
|
case 8: return "KZT";
|
||||||
}
|
}
|
||||||
return currency;
|
return currency;
|
||||||
})(), // #TODO payments currency,
|
})(), // #TODO payments testing
|
||||||
});
|
});
|
||||||
box->setFocusCallback([=] {
|
box->setFocusCallback([=] {
|
||||||
row->setFocusFast();
|
row->setFocusFast();
|
||||||
|
@ -221,7 +223,7 @@ void Panel::chooseTips(const Invoice &invoice) {
|
||||||
st::paymentTipsErrorLabel)),
|
st::paymentTipsErrorLabel)),
|
||||||
st::paymentTipsErrorPadding);
|
st::paymentTipsErrorPadding);
|
||||||
errorWrap->hide(anim::type::instant);
|
errorWrap->hide(anim::type::instant);
|
||||||
box->addButton(tr::lng_settings_save(), [=] {
|
const auto submit = [=] {
|
||||||
const auto value = row->value().toLongLong();
|
const auto value = row->value().toLongLong();
|
||||||
if (value > max) {
|
if (value > max) {
|
||||||
row->showError();
|
row->showError();
|
||||||
|
@ -230,7 +232,10 @@ void Panel::chooseTips(const Invoice &invoice) {
|
||||||
_delegate->panelChangeTips(value);
|
_delegate->panelChangeTips(value);
|
||||||
box->closeBox();
|
box->closeBox();
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
row->submitted(
|
||||||
|
) | rpl::start_with_next(submit, box->lifetime());
|
||||||
|
box->addButton(tr::lng_settings_save(), submit);
|
||||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,7 @@ private:
|
||||||
std::unique_ptr<RpWidget> _webviewBottom;
|
std::unique_ptr<RpWidget> _webviewBottom;
|
||||||
QPointer<Checkbox> _saveWebviewInformation;
|
QPointer<Checkbox> _saveWebviewInformation;
|
||||||
QPointer<FormSummary> _weakFormSummary;
|
QPointer<FormSummary> _weakFormSummary;
|
||||||
|
rpl::variable<int> _formScrollTop;
|
||||||
QPointer<EditInformation> _weakEditInformation;
|
QPointer<EditInformation> _weakEditInformation;
|
||||||
QPointer<EditCard> _weakEditCard;
|
QPointer<EditCard> _weakEditCard;
|
||||||
bool _testMode = false;
|
bool _testMode = false;
|
||||||
|
|
|
@ -127,7 +127,10 @@ QString FormatPlayedText(qint64 played, qint64 duration) {
|
||||||
return tr::lng_duration_played(tr::now, lt_played, FormatDurationText(played), lt_duration, FormatDurationText(duration));
|
return tr::lng_duration_played(tr::now, lt_played, FormatDurationText(played), lt_duration, FormatDurationText(duration));
|
||||||
}
|
}
|
||||||
|
|
||||||
QString FillAmountAndCurrency(int64 amount, const QString ¤cy) {
|
QString FillAmountAndCurrency(
|
||||||
|
int64 amount,
|
||||||
|
const QString ¤cy,
|
||||||
|
bool forceStripDotZero) {
|
||||||
const auto rule = LookupCurrencyRule(currency);
|
const auto rule = LookupCurrencyRule(currency);
|
||||||
|
|
||||||
const auto prefix = (amount < 0)
|
const auto prefix = (amount < 0)
|
||||||
|
@ -142,7 +145,8 @@ QString FillAmountAndCurrency(int64 amount, const QString ¤cy) {
|
||||||
result.append(name);
|
result.append(name);
|
||||||
if (rule.space) result.append(' ');
|
if (rule.space) result.append(' ');
|
||||||
}
|
}
|
||||||
const auto precision = (!rule.stripDotZero || std::floor(value) != value)
|
const auto precision = ((!rule.stripDotZero && !forceStripDotZero)
|
||||||
|
|| std::floor(value) != value)
|
||||||
? rule.exponent
|
? rule.exponent
|
||||||
: 0;
|
: 0;
|
||||||
result.append(FormatWithSeparators(
|
result.append(FormatWithSeparators(
|
||||||
|
|
|
@ -35,7 +35,8 @@ struct CurrencyRule {
|
||||||
|
|
||||||
[[nodiscard]] QString FillAmountAndCurrency(
|
[[nodiscard]] QString FillAmountAndCurrency(
|
||||||
int64 amount,
|
int64 amount,
|
||||||
const QString ¤cy);
|
const QString ¤cy,
|
||||||
|
bool forceStripDotZero = false);
|
||||||
[[nodiscard]] CurrencyRule LookupCurrencyRule(const QString ¤cy);
|
[[nodiscard]] CurrencyRule LookupCurrencyRule(const QString ¤cy);
|
||||||
[[nodiscard]] QString FormatWithSeparators(
|
[[nodiscard]] QString FormatWithSeparators(
|
||||||
double amount,
|
double amount,
|
||||||
|
|
Loading…
Reference in New Issue