Add nice tips buttons.

This commit is contained in:
John Preston 2021-04-02 15:46:48 +04:00
parent d55d7f37d7
commit b6c86fd298
7 changed files with 225 additions and 20 deletions

View File

@ -49,6 +49,23 @@ paymentsPricesTopSkip: 12px;
paymentsPricesBottomSkip: 13px;
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;
paymentsSectionButton: SettingsButton(infoProfileButton) {
padding: margins(68px, 11px, 14px, 9px);

View File

@ -27,6 +27,36 @@ QString formatPhone(QString phone); // #TODO
} // namespace App
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;
@ -38,7 +68,8 @@ FormSummary::FormSummary(
const RequestedInformation &current,
const PaymentMethodDetails &method,
const ShippingOptions &options,
not_null<PanelDelegate*> delegate)
not_null<PanelDelegate*> delegate,
int scrollTop)
: _delegate(delegate)
, _invoice(invoice)
, _method(method)
@ -56,21 +87,45 @@ FormSummary::FormSummary(
rpl::single(formatAmount(computeTotalAmount()))),
st::paymentsPanelSubmit))
, _cancel(
this,
(_invoice.receipt.paid
? tr::lng_about_done()
: tr::lng_cancel()),
st::paymentsPanelButton) {
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();
}
void FormSummary::updateThumbnail(const QImage &thumbnail) {
_invoice.cover.thumbnail = thumbnail;
_thumbnails.fire_copy(thumbnail);
}
QString FormSummary::formatAmount(int64 amount) const {
return FillAmountAndCurrency(amount, _invoice.currency);
QString FormSummary::formatAmount(
int64 amount,
bool forceStripDotZero) const {
return FillAmountAndCurrency(
amount,
_invoice.currency,
forceStripDotZero);
}
int64 FormSummary::computeTotalAmount() const {
@ -279,9 +334,7 @@ void FormSummary::setupPrices(not_null<VerticalLayout*> layout) {
add(tr::lng_payments_tips_label(tr::now), tips);
}
} else if (_invoice.tipsMax > 0) {
const auto text = _invoice.tipsSelected
? formatAmount(_invoice.tipsSelected)
: tr::lng_payments_tips_add(tr::now);
const auto text = formatAmount(_invoice.tipsSelected);
const auto label = addRow(
tr::lng_payments_tips_label(tr::now),
Ui::Text::Link(text, "internal:edit_tips"));
@ -289,12 +342,116 @@ void FormSummary::setupPrices(not_null<VerticalLayout*> layout) {
_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<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) {
Settings::AddSkip(layout, st::paymentsSectionsTopSkip);
@ -419,6 +576,12 @@ void FormSummary::updateControlsGeometry() {
_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

View File

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rp_widget.h"
#include "payments/ui/payments_panel_data.h"
#include "base/object_ptr.h"
#include "styles/style_widgets.h"
namespace Ui {
class ScrollArea;
@ -32,9 +33,11 @@ public:
const RequestedInformation &current,
const PaymentMethodDetails &method,
const ShippingOptions &options,
not_null<PanelDelegate*> delegate);
not_null<PanelDelegate*> delegate,
int scrollTop);
void updateThumbnail(const QImage &thumbnail);
[[nodiscard]] rpl::producer<int> scrollTopValue() const;
private:
void resizeEvent(QResizeEvent *e) override;
@ -43,10 +46,13 @@ private:
[[nodiscard]] not_null<Ui::RpWidget*> setupContent();
void setupCover(not_null<VerticalLayout*> layout);
void setupPrices(not_null<VerticalLayout*> layout);
void setupSuggestedTips(not_null<VerticalLayout*> layout);
void setupSections(not_null<VerticalLayout*> layout);
void updateControlsGeometry();
[[nodiscard]] QString formatAmount(int64 amount) const;
[[nodiscard]] QString formatAmount(
int64 amount,
bool forceStripDotZero = false) const;
[[nodiscard]] int64 computeTotalAmount() const;
const not_null<PanelDelegate*> _delegate;
@ -61,6 +67,14 @@ private:
object_ptr<RoundButton> _cancel;
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

View File

@ -66,10 +66,12 @@ void Panel::showForm(
current,
method,
options,
_delegate);
_delegate,
_formScrollTop.current());
_weakFormSummary = form.get();
_widget->showInner(std::move(form));
_widget->setBackAllowed(false);
_formScrollTop = _weakFormSummary->scrollTopValue();
}
void Panel::updateFormThumbnail(const QImage &thumbnail) {
@ -204,7 +206,7 @@ void Panel::chooseTips(const Invoice &invoice) {
case 8: return "KZT";
}
return currency;
})(), // #TODO payments currency,
})(), // #TODO payments testing
});
box->setFocusCallback([=] {
row->setFocusFast();
@ -221,7 +223,7 @@ void Panel::chooseTips(const Invoice &invoice) {
st::paymentTipsErrorLabel)),
st::paymentTipsErrorPadding);
errorWrap->hide(anim::type::instant);
box->addButton(tr::lng_settings_save(), [=] {
const auto submit = [=] {
const auto value = row->value().toLongLong();
if (value > max) {
row->showError();
@ -230,7 +232,10 @@ void Panel::chooseTips(const Invoice &invoice) {
_delegate->panelChangeTips(value);
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(); });
}));
}

View File

@ -91,6 +91,7 @@ private:
std::unique_ptr<RpWidget> _webviewBottom;
QPointer<Checkbox> _saveWebviewInformation;
QPointer<FormSummary> _weakFormSummary;
rpl::variable<int> _formScrollTop;
QPointer<EditInformation> _weakEditInformation;
QPointer<EditCard> _weakEditCard;
bool _testMode = false;

View File

@ -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));
}
QString FillAmountAndCurrency(int64 amount, const QString &currency) {
QString FillAmountAndCurrency(
int64 amount,
const QString &currency,
bool forceStripDotZero) {
const auto rule = LookupCurrencyRule(currency);
const auto prefix = (amount < 0)
@ -142,7 +145,8 @@ QString FillAmountAndCurrency(int64 amount, const QString &currency) {
result.append(name);
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
: 0;
result.append(FormatWithSeparators(

View File

@ -35,7 +35,8 @@ struct CurrencyRule {
[[nodiscard]] QString FillAmountAndCurrency(
int64 amount,
const QString &currency);
const QString &currency,
bool forceStripDotZero = false);
[[nodiscard]] CurrencyRule LookupCurrencyRule(const QString &currency);
[[nodiscard]] QString FormatWithSeparators(
double amount,