First full-featured version of payments, no design.

This commit is contained in:
John Preston 2021-03-23 20:06:59 +04:00
parent 28137dfb60
commit 46508f7e5e
42 changed files with 1756 additions and 736 deletions

View File

@ -256,8 +256,6 @@ PRIVATE
boxes/sessions_box.h
boxes/share_box.cpp
boxes/share_box.h
boxes/single_choice_box.cpp
boxes/single_choice_box.h
boxes/sticker_set_box.cpp
boxes/sticker_set_box.h
boxes/stickers_box.cpp
@ -390,8 +388,6 @@ PRIVATE
data/data_cloud_file.h
data/data_cloud_themes.cpp
data/data_cloud_themes.h
data/data_countries.cpp
data/data_countries.h
data/data_document.cpp
data/data_document.h
data/data_document_media.cpp
@ -803,8 +799,6 @@ PRIVATE
passport/passport_panel.h
passport/passport_panel_controller.cpp
passport/passport_panel_controller.h
passport/passport_panel_details_row.cpp
passport/passport_panel_details_row.h
passport/passport_panel_edit_contact.cpp
passport/passport_panel_edit_contact.h
passport/passport_panel_edit_document.cpp

View File

@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "boxes/add_contact_box.h"
#include "boxes/confirm_box.h"
#include "boxes/single_choice_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/edit_participants_box.h"
#include "boxes/peers/edit_peer_type_box.h"
@ -20,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_invite_links.h"
#include "boxes/peers/edit_linked_chat_box.h"
#include "boxes/stickers_box.h"
#include "ui/boxes/single_choice_box.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "core/application.h"
#include "core/core_settings.h"

View File

@ -35,7 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_group_call.h"
#include "data/data_changes.h"
#include "core/application.h"
#include "boxes/single_choice_box.h"
#include "ui/boxes/single_choice_box.h"
#include "webrtc/webrtc_audio_input_tester.h"
#include "webrtc/webrtc_media_devices.h"
#include "settings/settings_common.h"

View File

@ -1820,7 +1820,7 @@ Utf8String FormatDateTime(
).toUtf8();
}
Utf8String FormatMoneyAmount(uint64 amount, const Utf8String &currency) {
Utf8String FormatMoneyAmount(int64 amount, const Utf8String &currency) {
return Ui::FillAmountAndCurrency(
amount,
QString::fromUtf8(currency)).toUtf8();

View File

@ -660,7 +660,7 @@ Utf8String FormatDateTime(
QChar dateSeparator = QChar('.'),
QChar timeSeparator = QChar(':'),
QChar separator = QChar(' '));
Utf8String FormatMoneyAmount(uint64 amount, const Utf8String &currency);
Utf8String FormatMoneyAmount(int64 amount, const Utf8String &currency);
Utf8String FormatFileSize(int64 size);
Utf8String FormatDuration(int64 seconds);

View File

@ -958,7 +958,9 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) {
UpdateComponents(HistoryServicePayment::Bit());
const auto amount = data.vtotal_amount().v;
const auto currency = qs(data.vcurrency());
Get<HistoryServicePayment>()->amount = Ui::FillAmountAndCurrency(amount, currency);
Get<HistoryServicePayment>()->amount = Ui::FillAmountAndCurrency(
amount,
currency);
} else if (message.vaction().type() == mtpc_messageActionGroupCall) {
const auto &data = message.vaction().c_messageActionGroupCall();
if (data.vduration()) {

View File

@ -61,8 +61,8 @@ PhoneWidget::PhoneWidget(
setErrorCentered(true);
setupQrLogin();
if (!_country->onChooseCountry(getData()->country)) {
_country->onChooseCountry(qsl("US"));
if (!_country->chooseCountry(getData()->country)) {
_country->chooseCountry(qsl("US"));
}
_changed = false;
}
@ -251,7 +251,7 @@ QString PhoneWidget::fullNumber() const {
}
void PhoneWidget::selectCountry(const QString &country) {
_country->onChooseCountry(country);
_country->chooseCountry(country);
}
void PhoneWidget::setInnerFocus() {

View File

@ -9,10 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "passport/passport_panel_edit_document.h"
#include "passport/passport_panel_details_row.h"
#include "passport/passport_panel_edit_contact.h"
#include "passport/passport_panel_edit_scans.h"
#include "passport/passport_panel.h"
#include "passport/ui/passport_details_row.h"
#include "base/openssl_help.h"
#include "base/unixtime.h"
#include "boxes/passcode_box.h"
@ -212,7 +212,7 @@ EditDocumentScheme GetDocumentScheme(
result.rows = {
{
ValueClass::Fields,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("first_name"),
tr::lng_passport_first_name(tr::now),
NameValidate,
@ -221,7 +221,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("middle_name"),
tr::lng_passport_middle_name(tr::now),
NameOrEmptyValidate,
@ -231,7 +231,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("last_name"),
tr::lng_passport_last_name(tr::now),
NameValidate,
@ -241,7 +241,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Date,
Ui::PanelDetailsType::Date,
qsl("birth_date"),
tr::lng_passport_birth_date(tr::now),
DateValidate,
@ -249,7 +249,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Gender,
Ui::PanelDetailsType::Gender,
qsl("gender"),
tr::lng_passport_gender(tr::now),
GenderValidate,
@ -257,7 +257,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Country,
Ui::PanelDetailsType::Country,
qsl("country_code"),
tr::lng_passport_country(tr::now),
CountryValidate,
@ -265,7 +265,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Country,
Ui::PanelDetailsType::Country,
qsl("residence_country_code"),
tr::lng_passport_residence_country(tr::now),
CountryValidate,
@ -273,7 +273,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Scans,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("document_no"),
tr::lng_passport_document_number(tr::now),
DocumentValidate,
@ -282,7 +282,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Scans,
PanelDetailsType::Date,
Ui::PanelDetailsType::Date,
qsl("expiry_date"),
tr::lng_passport_expiry_date(tr::now),
DateOrEmptyValidate,
@ -344,7 +344,7 @@ EditDocumentScheme GetDocumentScheme(
auto additional = std::initializer_list<Row>{
{
ValueClass::Additional,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("first_name_native"),
tr::lng_passport_first_name(tr::now),
NativeNameValidate,
@ -355,7 +355,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Additional,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("middle_name_native"),
tr::lng_passport_middle_name(tr::now),
NativeNameOrEmptyValidate,
@ -366,7 +366,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Additional,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("last_name_native"),
tr::lng_passport_last_name(tr::now),
NativeNameValidate,
@ -411,7 +411,7 @@ EditDocumentScheme GetDocumentScheme(
result.rows = {
{
ValueClass::Fields,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("street_line1"),
tr::lng_passport_street(tr::now),
StreetValidate,
@ -420,7 +420,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("street_line2"),
tr::lng_passport_street(tr::now),
DontValidate,
@ -429,7 +429,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("city"),
tr::lng_passport_city(tr::now),
CityValidate,
@ -438,7 +438,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("state"),
tr::lng_passport_state(tr::now),
DontValidate,
@ -447,7 +447,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Country,
Ui::PanelDetailsType::Country,
qsl("country_code"),
tr::lng_passport_country(tr::now),
CountryValidate,
@ -455,7 +455,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Postcode,
Ui::PanelDetailsType::Postcode,
qsl("post_code"),
tr::lng_passport_postcode(tr::now),
PostcodeValidate,

View File

@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_panel_edit_contact.h"
#include "passport/passport_panel_controller.h"
#include "passport/passport_panel_details_row.h"
#include "passport/ui/passport_details_row.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"

View File

@ -8,8 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_panel_edit_document.h"
#include "passport/passport_panel_controller.h"
#include "passport/passport_panel_details_row.h"
#include "passport/passport_panel_edit_scans.h"
#include "passport/ui/passport_details_row.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/labels.h"
@ -19,6 +19,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "data/data_countries.h"
#include "data/data_user.h" // ->bot()->session()
#include "main/main_session.h" // ->session().user()
#include "ui/text/text_utilities.h" // Ui::Text::ToUpper
#include "boxes/abstract_box.h"
#include "boxes/confirm_box.h"
@ -363,7 +366,7 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
const ValueMap &fields) {
accumulate_max(
maxLabelWidth,
PanelDetailsRow::LabelWidth(row.label));
Ui::PanelDetailsRow::LabelWidth(row.label));
});
if (maxLabelWidth > 0) {
if (error && !error->isEmpty()) {
@ -513,12 +516,20 @@ void PanelEditDocument::createDetailsRow(
};
const auto current = valueOrEmpty(fields, row.key);
const auto showBox = [controller = _controller](
object_ptr<Ui::BoxContent> box) {
controller->show(std::move(box));
};
const auto isoByPhone = Data::CountryISO2ByPhone(
_controller->bot()->session().user()->phone());
const auto [it, ok] = _details.emplace(
i,
container->add(PanelDetailsRow::Create(
container->add(Ui::PanelDetailsRow::Create(
container,
showBox,
isoByPhone,
row.inputType,
_controller,
row.label,
maxLabelWidth,
current.text,
@ -537,7 +548,7 @@ void PanelEditDocument::createDetailsRow(
}, it->second->lifetime());
}
not_null<PanelDetailsRow*> PanelEditDocument::findRow(
not_null<Ui::PanelDetailsRow*> PanelEditDocument::findRow(
const QString &key) const {
for (auto i = 0, count = int(_scheme.rows.size()); i != count; ++i) {
const auto &row = _scheme.rows[i];
@ -636,7 +647,7 @@ bool PanelEditDocument::validate() {
_scroll->scrollToY(_scroll->scrollTop() + scrolldelta);
error = firsttop.y();
}
auto first = QPointer<PanelDetailsRow>();
auto first = QPointer<Ui::PanelDetailsRow>();
for (const auto &[i, field] : ranges::views::reverse(_details)) {
const auto &row = _scheme.rows[i];
if (row.valueClass == Scheme::ValueClass::Additional

View File

@ -24,15 +24,19 @@ template <typename Widget>
class SlideWrap;
} // namespace Ui
namespace Passport::Ui {
using namespace ::Ui;
enum class PanelDetailsType;
class PanelDetailsRow;
} // namespace Passport::Ui
namespace Passport {
class PanelController;
struct ValueMap;
struct ScanInfo;
class EditScans;
class PanelDetailsRow;
enum class FileType;
enum class PanelDetailsType;
struct ScanListData;
struct EditDocumentScheme {
@ -50,7 +54,7 @@ struct EditDocumentScheme {
using Validator = Fn<std::optional<QString>(const QString &value)>;
using Formatter = Fn<QString(const QString &value)>;
ValueClass valueClass = ValueClass::Fields;
PanelDetailsType inputType = PanelDetailsType();
Ui::PanelDetailsType inputType = Ui::PanelDetailsType();
QString key;
QString label;
Validator error;
@ -140,7 +144,7 @@ private:
const Scheme::Row &row,
const ValueMap &fields,
int maxLabelWidth);
not_null<PanelDetailsRow*> findRow(const QString &key) const;
not_null<Ui::PanelDetailsRow*> findRow(const QString &key) const;
not_null<PanelController*> _controller;
Scheme _scheme;
@ -151,7 +155,7 @@ private:
QPointer<EditScans> _editScans;
QPointer<Ui::SlideWrap<Ui::FlatLabel>> _commonError;
std::map<int, QPointer<PanelDetailsRow>> _details;
std::map<int, QPointer<Ui::PanelDetailsRow>> _details;
bool _fieldsChanged = false;
bool _additionalShown = false;

View File

@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_panel_edit_scans.h"
#include "passport/passport_panel_controller.h"
#include "passport/passport_panel_details_row.h"
#include "passport/ui/passport_details_row.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/box_content_divider.h"

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_panel_form.h"
#include "passport/passport_panel_controller.h"
#include "passport/ui/passport_form_row.h"
#include "lang/lang_keys.h"
#include "boxes/abstract_box.h"
#include "core/click_handler_types.h"
@ -30,145 +31,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Passport {
class PanelForm::Row : public Ui::RippleButton {
public:
explicit Row(QWidget *parent);
void updateContent(
const QString &title,
const QString &description,
bool ready,
bool error,
anim::type animated);
protected:
int resizeGetHeight(int newWidth) override;
void paintEvent(QPaintEvent *e) override;
private:
int countAvailableWidth() const;
int countAvailableWidth(int newWidth) const;
Ui::Text::String _title;
Ui::Text::String _description;
int _titleHeight = 0;
int _descriptionHeight = 0;
bool _ready = false;
bool _error = false;
Ui::Animations::Simple _errorAnimation;
};
PanelForm::Row::Row(QWidget *parent)
: RippleButton(parent, st::passportRowRipple)
, _title(st::boxWideWidth / 2)
, _description(st::boxWideWidth / 2) {
}
void PanelForm::Row::updateContent(
const QString &title,
const QString &description,
bool ready,
bool error,
anim::type animated) {
_title.setText(
st::semiboldTextStyle,
title,
Ui::NameTextOptions());
_description.setText(
st::defaultTextStyle,
description,
TextParseOptions {
TextParseMultiline,
0,
0,
Qt::LayoutDirectionAuto
});
_ready = ready && !error;
if (_error != error) {
_error = error;
if (animated == anim::type::instant) {
_errorAnimation.stop();
} else {
_errorAnimation.start(
[=] { update(); },
_error ? 0. : 1.,
_error ? 1. : 0.,
st::fadeWrapDuration);
}
}
resizeToWidth(width());
update();
}
int PanelForm::Row::resizeGetHeight(int newWidth) {
const auto availableWidth = countAvailableWidth(newWidth);
_titleHeight = _title.countHeight(availableWidth);
_descriptionHeight = _description.countHeight(availableWidth);
const auto result = st::passportRowPadding.top()
+ _titleHeight
+ st::passportRowSkip
+ _descriptionHeight
+ st::passportRowPadding.bottom();
return result;
}
int PanelForm::Row::countAvailableWidth(int newWidth) const {
return newWidth
- st::passportRowPadding.left()
- st::passportRowPadding.right()
- (_ready
? st::passportRowReadyIcon
: st::passportRowEmptyIcon).width()
- st::passportRowIconSkip;
}
int PanelForm::Row::countAvailableWidth() const {
return countAvailableWidth(width());
}
void PanelForm::Row::paintEvent(QPaintEvent *e) {
Painter p(this);
paintRipple(p, 0, 0);
const auto left = st::passportRowPadding.left();
const auto availableWidth = countAvailableWidth();
auto top = st::passportRowPadding.top();
const auto error = _errorAnimation.value(_error ? 1. : 0.);
p.setPen(st::passportRowTitleFg);
_title.drawLeft(p, left, top, availableWidth, width());
top += _titleHeight + st::passportRowSkip;
p.setPen(anim::pen(
st::passportRowDescriptionFg,
st::boxTextFgError,
error));
_description.drawLeft(p, left, top, availableWidth, width());
top += _descriptionHeight + st::passportRowPadding.bottom();
const auto &icon = _ready
? st::passportRowReadyIcon
: st::passportRowEmptyIcon;
if (error > 0. && !_ready) {
icon.paint(
p,
width() - st::passportRowPadding.right() - icon.width(),
(height() - icon.height()) / 2,
width(),
anim::color(st::menuIconFgOver, st::boxTextFgError, error));
} else {
icon.paint(
p,
width() - st::passportRowPadding.right() - icon.width(),
(height() - icon.height()) / 2,
width());
}
}
PanelForm::PanelForm(
QWidget *parent,
not_null<PanelController*> controller)

View File

@ -19,6 +19,11 @@ class FlatLabel;
class UserpicButton;
} // namespace Ui
namespace Passport::Ui {
using namespace ::Ui;
class FormRow;
} // namespace Passport::Ui
namespace Passport {
class PanelController;
@ -33,7 +38,7 @@ protected:
void resizeEvent(QResizeEvent *e) override;
private:
class Row;
using Row = Ui::FormRow;
void setupControls();
not_null<Ui::RpWidget*> setupContent();

View File

@ -5,9 +5,8 @@ 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 "passport/passport_panel_details_row.h"
#include "passport/ui/passport_details_row.h"
#include "passport/passport_panel_controller.h"
#include "lang/lang_keys.h"
#include "base/platform/base_platform_info.h"
#include "ui/widgets/input_fields.h"
@ -15,17 +14,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/countryinput.h"
#include "main/main_session.h"
#include "data/data_user.h"
#include "ui/layers/box_content.h"
#include "ui/boxes/country_select_box.h"
#include "data/data_countries.h"
#include "styles/style_layers.h"
#include "styles/style_passport.h"
namespace Passport {
#include <QtCore/QRegularExpression>
namespace Passport::Ui {
namespace {
class PostcodeInput : public Ui::MaskedInputField {
class PostcodeInput : public MaskedInputField {
public:
PostcodeInput(
QWidget *parent,
@ -103,7 +103,8 @@ class CountryRow : public PanelDetailsRow {
public:
CountryRow(
QWidget *parent,
not_null<PanelController*> controller,
Fn<void(object_ptr<BoxContent>)> showBox,
const QString &defaultCountry,
const QString &label,
int maxLabelWidth,
const QString &value);
@ -121,15 +122,16 @@ private:
void toggleError(bool shown);
void errorAnimationCallback();
not_null<PanelController*> _controller;
object_ptr<Ui::LinkButton> _link;
QString _defaultCountry;
Fn<void(object_ptr<BoxContent>)> _showBox;
object_ptr<LinkButton> _link;
rpl::variable<QString> _value;
bool _errorShown = false;
Ui::Animations::Simple _errorAnimation;
Animations::Simple _errorAnimation;
};
class DateInput final : public Ui::MaskedInputField {
class DateInput final : public MaskedInputField {
public:
using MaskedInputField::MaskedInputField;
@ -191,21 +193,21 @@ private:
int number(const object_ptr<DateInput> &field) const;
object_ptr<DateInput> _day;
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>> _separator1;
object_ptr<PaddingWrap<FlatLabel>> _separator1;
object_ptr<DateInput> _month;
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>> _separator2;
object_ptr<PaddingWrap<FlatLabel>> _separator2;
object_ptr<DateInput> _year;
rpl::variable<QString> _value;
style::cursor _cursor = style::cur_default;
Ui::Animations::Simple _a_borderShown;
Animations::Simple _a_borderShown;
int _borderAnimationStart = 0;
Ui::Animations::Simple _a_borderOpacity;
Animations::Simple _a_borderOpacity;
bool _borderVisible = false;
Ui::Animations::Simple _a_error;
Animations::Simple _a_error;
bool _error = false;
Ui::Animations::Simple _a_focused;
Animations::Simple _a_focused;
bool _focused = false;
};
@ -238,18 +240,18 @@ private:
void hideGenderError();
void errorAnimationCallback();
std::unique_ptr<Ui::AbstractCheckView> createRadioView(
Ui::RadioView* &weak) const;
std::unique_ptr<AbstractCheckView> createRadioView(
RadioView* &weak) const;
std::shared_ptr<Ui::RadioenumGroup<Gender>> _group;
Ui::RadioView *_maleRadio = nullptr;
Ui::RadioView *_femaleRadio = nullptr;
object_ptr<Ui::Radioenum<Gender>> _male;
object_ptr<Ui::Radioenum<Gender>> _female;
std::shared_ptr<RadioenumGroup<Gender>> _group;
RadioView *_maleRadio = nullptr;
RadioView *_femaleRadio = nullptr;
object_ptr<Radioenum<Gender>> _male;
object_ptr<Radioenum<Gender>> _female;
rpl::variable<QString> _value;
bool _errorShown = false;
Ui::Animations::Simple _errorAnimation;
Animations::Simple _errorAnimation;
};
@ -308,12 +310,14 @@ QString CountryString(const QString &code) {
CountryRow::CountryRow(
QWidget *parent,
not_null<PanelController*> controller,
Fn<void(object_ptr<BoxContent>)> showBox,
const QString &defaultCountry,
const QString &label,
int maxLabelWidth,
const QString &value)
: PanelDetailsRow(parent, label, maxLabelWidth)
, _controller(controller)
, _defaultCountry(defaultCountry)
, _showBox(std::move(showBox))
, _link(this, CountryString(value), st::boxLinkButton)
, _value(value) {
_value.changes(
@ -380,20 +384,23 @@ void CountryRow::errorAnimationCallback() {
void CountryRow::chooseCountry() {
const auto top = _value.current();
const auto name = Data::CountryNameByISO2(top);
const auto isoByPhone = Data::CountryISO2ByPhone(
_controller->bot()->session().user()->phone());
const auto box = _controller->show(Box<CountrySelectBox>(!name.isEmpty()
const auto country = !name.isEmpty()
? top
: !isoByPhone.isEmpty()
? isoByPhone
: Platform::SystemCountry(),
CountrySelectBox::Type::Countries));
connect(box, &CountrySelectBox::countryChosen, this, [=](QString iso) {
: !_defaultCountry.isEmpty()
? _defaultCountry
: Platform::SystemCountry();
auto box = Box<CountrySelectBox>(
country,
CountrySelectBox::Type::Countries);
const auto raw = box.data();
raw->countryChosen(
) | rpl::start_with_next([=](QString iso) {
_value = iso;
_link->setText(CountryString(iso));
hideCountryError();
box->closeBox();
});
raw->closeBox();
}, lifetime());
_showBox(std::move(box));
}
QDate ValidateDate(const QString &value) {
@ -528,7 +535,7 @@ DateRow::DateRow(
GetDay(value))
, _separator1(
this,
object_ptr<Ui::FlatLabel>(
object_ptr<FlatLabel>(
this,
QString(" / "),
st::passportDetailsSeparator),
@ -540,7 +547,7 @@ DateRow::DateRow(
GetMonth(value))
, _separator2(
this,
object_ptr<Ui::FlatLabel>(
object_ptr<FlatLabel>(
this,
QString(" / "),
st::passportDetailsSeparator),
@ -552,7 +559,7 @@ DateRow::DateRow(
GetYear(value))
, _value(valueCurrent()) {
const auto focused = [=](const object_ptr<DateInput> &field) {
return [this, pointer = Ui::MakeWeak(field.data())]{
return [this, pointer = MakeWeak(field.data())]{
_borderAnimationStart = pointer->borderAnimationStart()
+ pointer->x()
- _day->x();
@ -565,15 +572,15 @@ DateRow::DateRow(
const auto changed = [=] {
_value = valueCurrent();
};
connect(_day, &Ui::MaskedInputField::focused, focused(_day));
connect(_month, &Ui::MaskedInputField::focused, focused(_month));
connect(_year, &Ui::MaskedInputField::focused, focused(_year));
connect(_day, &Ui::MaskedInputField::blurred, blurred);
connect(_month, &Ui::MaskedInputField::blurred, blurred);
connect(_year, &Ui::MaskedInputField::blurred, blurred);
connect(_day, &Ui::MaskedInputField::changed, changed);
connect(_month, &Ui::MaskedInputField::changed, changed);
connect(_year, &Ui::MaskedInputField::changed, changed);
connect(_day, &MaskedInputField::focused, focused(_day));
connect(_month, &MaskedInputField::focused, focused(_month));
connect(_year, &MaskedInputField::focused, focused(_year));
connect(_day, &MaskedInputField::blurred, blurred);
connect(_month, &MaskedInputField::blurred, blurred);
connect(_year, &MaskedInputField::blurred, blurred);
connect(_day, &MaskedInputField::changed, changed);
connect(_month, &MaskedInputField::changed, changed);
connect(_year, &MaskedInputField::changed, changed);
_day->setMaxValue(31);
_day->putNext() | rpl::start_with_next([=](QChar ch) {
putNext(_month, ch);
@ -845,8 +852,8 @@ GenderRow::GenderRow(
const QString &value)
: PanelDetailsRow(parent, label, maxLabelWidth)
, _group(StringToGender(value).has_value()
? std::make_shared<Ui::RadioenumGroup<Gender>>(*StringToGender(value))
: std::make_shared<Ui::RadioenumGroup<Gender>>())
? std::make_shared<RadioenumGroup<Gender>>(*StringToGender(value))
: std::make_shared<RadioenumGroup<Gender>>())
, _male(
this,
_group,
@ -868,9 +875,9 @@ GenderRow::GenderRow(
});
}
std::unique_ptr<Ui::AbstractCheckView> GenderRow::createRadioView(
Ui::RadioView* &weak) const {
auto result = std::make_unique<Ui::RadioView>(st::defaultRadio, false);
std::unique_ptr<AbstractCheckView> GenderRow::createRadioView(
RadioView* &weak) const {
auto result = std::make_unique<RadioView>(st::defaultRadio, false);
weak = result.get();
return result;
}
@ -959,8 +966,9 @@ PanelDetailsRow::PanelDetailsRow(
object_ptr<PanelDetailsRow> PanelDetailsRow::Create(
QWidget *parent,
Fn<void(object_ptr<BoxContent>)> showBox,
const QString &defaultCountry,
Type type,
not_null<PanelController*> controller,
const QString &label,
int maxLabelWidth,
const QString &value,
@ -969,7 +977,7 @@ object_ptr<PanelDetailsRow> PanelDetailsRow::Create(
auto result = [&]() -> object_ptr<PanelDetailsRow> {
switch (type) {
case Type::Text:
return object_ptr<AbstractTextRow<Ui::InputField>>(
return object_ptr<AbstractTextRow<InputField>>(
parent,
label,
maxLabelWidth,
@ -985,7 +993,8 @@ object_ptr<PanelDetailsRow> PanelDetailsRow::Create(
case Type::Country:
return object_ptr<CountryRow>(
parent,
controller,
showBox,
defaultCountry,
label,
maxLabelWidth,
value);
@ -1062,7 +1071,7 @@ void PanelDetailsRow::showError(std::optional<QString> error) {
if (!_error) {
_error.create(
this,
object_ptr<Ui::FlatLabel>(
object_ptr<FlatLabel>(
this,
*error,
st::passportVerifyErrorLabel));
@ -1122,4 +1131,4 @@ void PanelDetailsRow::paintEvent(QPaintEvent *e) {
p.drawTextLeft(padding.left(), padding.top(), width(), _label);
}
} // namespace Passport
} // namespace Passport::Ui

View File

@ -11,18 +11,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/widgets/labels.h"
#include "boxes/abstract_box.h"
namespace Ui {
class BoxContent;
class InputField;
class FlatLabel;
template <typename Widget>
class SlideWrap;
} // namespace Ui
namespace Passport {
namespace Passport::Ui {
class PanelController;
using namespace ::Ui;
enum class PanelDetailsType {
Text,
@ -32,7 +32,7 @@ enum class PanelDetailsType {
Gender,
};
class PanelDetailsRow : public Ui::RpWidget {
class PanelDetailsRow : public RpWidget {
public:
using Type = PanelDetailsType;
@ -43,8 +43,9 @@ public:
static object_ptr<PanelDetailsRow> Create(
QWidget *parent,
Fn<void(object_ptr<BoxContent>)> showBox,
const QString &defaultCountry,
Type type,
not_null<PanelController*> controller,
const QString &label,
int maxLabelWidth,
const QString &value,
@ -74,11 +75,11 @@ private:
QString _label;
int _maxLabelWidth = 0;
object_ptr<Ui::SlideWrap<Ui::FlatLabel>> _error = { nullptr };
object_ptr<SlideWrap<FlatLabel>> _error = { nullptr };
bool _errorShown = false;
bool _errorHideSubscription = false;
Ui::Animations::Simple _errorAnimation;
Animations::Simple _errorAnimation;
};
} // namespace Passport
} // namespace Passport::Ui

View File

@ -0,0 +1,125 @@
/*
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 "passport/ui/passport_form_row.h"
#include "ui/text/text_options.h"
#include "styles/style_passport.h"
#include "styles/style_layers.h"
namespace Passport::Ui {
FormRow::FormRow(QWidget *parent)
: RippleButton(parent, st::passportRowRipple)
, _title(st::boxWideWidth / 2)
, _description(st::boxWideWidth / 2) {
}
void FormRow::updateContent(
const QString &title,
const QString &description,
bool ready,
bool error,
anim::type animated) {
_title.setText(
st::semiboldTextStyle,
title,
NameTextOptions());
_description.setText(
st::defaultTextStyle,
description,
TextParseOptions {
TextParseMultiline,
0,
0,
Qt::LayoutDirectionAuto
});
_ready = ready && !error;
if (_error != error) {
_error = error;
if (animated == anim::type::instant) {
_errorAnimation.stop();
} else {
_errorAnimation.start(
[=] { update(); },
_error ? 0. : 1.,
_error ? 1. : 0.,
st::fadeWrapDuration);
}
}
resizeToWidth(width());
update();
}
int FormRow::resizeGetHeight(int newWidth) {
const auto availableWidth = countAvailableWidth(newWidth);
_titleHeight = _title.countHeight(availableWidth);
_descriptionHeight = _description.countHeight(availableWidth);
const auto result = st::passportRowPadding.top()
+ _titleHeight
+ st::passportRowSkip
+ _descriptionHeight
+ st::passportRowPadding.bottom();
return result;
}
int FormRow::countAvailableWidth(int newWidth) const {
return newWidth
- st::passportRowPadding.left()
- st::passportRowPadding.right()
- (_ready
? st::passportRowReadyIcon
: st::passportRowEmptyIcon).width()
- st::passportRowIconSkip;
}
int FormRow::countAvailableWidth() const {
return countAvailableWidth(width());
}
void FormRow::paintEvent(QPaintEvent *e) {
Painter p(this);
paintRipple(p, 0, 0);
const auto left = st::passportRowPadding.left();
const auto availableWidth = countAvailableWidth();
auto top = st::passportRowPadding.top();
const auto error = _errorAnimation.value(_error ? 1. : 0.);
p.setPen(st::passportRowTitleFg);
_title.drawLeft(p, left, top, availableWidth, width());
top += _titleHeight + st::passportRowSkip;
p.setPen(anim::pen(
st::passportRowDescriptionFg,
st::boxTextFgError,
error));
_description.drawLeft(p, left, top, availableWidth, width());
top += _descriptionHeight + st::passportRowPadding.bottom();
const auto &icon = _ready
? st::passportRowReadyIcon
: st::passportRowEmptyIcon;
if (error > 0. && !_ready) {
icon.paint(
p,
width() - st::passportRowPadding.right() - icon.width(),
(height() - icon.height()) / 2,
width(),
anim::color(st::menuIconFgOver, st::boxTextFgError, error));
} else {
icon.paint(
p,
width() - st::passportRowPadding.right() - icon.width(),
(height() - icon.height()) / 2,
width());
}
}
} // namespace Passport::Ui

View File

@ -0,0 +1,48 @@
/*
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
*/
#pragma once
#include "ui/text/text.h"
#include "ui/effects/animations.h"
#include "ui/widgets/buttons.h"
namespace Passport::Ui {
using namespace ::Ui;
class FormRow : public RippleButton {
public:
explicit FormRow(QWidget *parent);
void updateContent(
const QString &title,
const QString &description,
bool ready,
bool error,
anim::type animated);
protected:
int resizeGetHeight(int newWidth) override;
void paintEvent(QPaintEvent *e) override;
private:
int countAvailableWidth() const;
int countAvailableWidth(int newWidth) const;
Text::String _title;
Text::String _description;
int _titleHeight = 0;
int _descriptionHeight = 0;
bool _ready = false;
bool _error = false;
Animations::Simple _errorAnimation;
};
} // namespace Passport::Ui

View File

@ -95,11 +95,19 @@ not_null<Ui::PanelDelegate*> CheckoutProcess::panelDelegate() {
void CheckoutProcess::handleFormUpdate(const FormUpdate &update) {
v::match(update.data, [&](const FormReady &) {
_panel->showForm(_form->invoice());
}, [&](const FormError &error) {
showForm();
}, [&](const FormError &error) { // #TODO payments refactor errors
handleFormError(error);
}, [&](const ValidateError &error) {
handleValidateError(error);
}, [&](const SendError &error) {
handleSendError(error);
}, [&](const ValidateFinished &) {
showForm();
if (_submitState == SubmitState::Validation) {
_submitState = SubmitState::Validated;
panelSubmit();
}
}, [&](const VerificationNeeded &info) {
if (_webviewWindow) {
_webviewWindow->navigate(info.url);
@ -130,8 +138,44 @@ void CheckoutProcess::handleFormError(const FormError &error) {
} else if (type == u"INVOICE_ALREADY_PAID"_q) {
}
App::wnd()->activate();
Ui::Toast::Show("payments.getPaymentForm: " + type);
if (_panel) {
_panel->showToast("payments.getPaymentForm: " + type);
} else {
App::wnd()->activate();
Ui::Toast::Show("payments.getPaymentForm: " + type);
}
}
void CheckoutProcess::handleValidateError(const ValidateError &error) {
// #TODO payments errors
const auto &type = error.type;
if (type == u"REQ_INFO_NAME_INVALID"_q) {
} else if (type == u"REQ_INFO_EMAIL_INVALID"_q) {
} else if (type == u"REQ_INFO_PHONE_INVALID"_q) {
} else if (type == u"ADDRESS_STREET_LINE1_INVALID"_q) {
} else if (type == u"ADDRESS_CITY_INVALID"_q) {
} else if (type == u"ADDRESS_STATE_INVALID"_q) {
} else if (type == u"ADDRESS_COUNTRY_INVALID"_q) {
} else if (type == u"ADDRESS_POSTCODE_INVALID"_q) {
} else if (type == u"SHIPPING_BOT_TIMEOUT"_q) {
} else if (type == u"SHIPPING_NOT_AVAILABLE"_q) {
}
if (_panel) {
_panel->showToast("payments.validateRequestedInfo: " + type);
} else {
App::wnd()->activate();
Ui::Toast::Show("payments.validateRequestedInfo: " + type);
}
}
void CheckoutProcess::handleSendError(const SendError &error) {
@ -150,8 +194,12 @@ void CheckoutProcess::handleSendError(const SendError &error) {
} else if (type == u"BOT_PRECHECKOUT_FAILED"_q) {
}
App::wnd()->activate();
Ui::Toast::Show("payments.sendPaymentForm: " + type);
if (_panel) {
_panel->showToast("payments.sendPaymentForm: " + type);
} else {
App::wnd()->activate();
Ui::Toast::Show("payments.sendPaymentForm: " + type);
}
}
void CheckoutProcess::panelRequestClose() {
@ -176,6 +224,26 @@ void CheckoutProcess::panelCloseSure() {
}
void CheckoutProcess::panelSubmit() {
if (_submitState == SubmitState::Validation
|| _submitState == SubmitState::Finishing) {
return;
}
const auto &invoice = _form->invoice();
const auto &options = _form->shippingOptions();
if (!options.list.empty() && options.selectedId.isEmpty()) {
chooseShippingOption();
return;
} else if (_submitState != SubmitState::Validated
&& options.list.empty()
&& (invoice.isShippingAddressRequested
|| invoice.isNameRequested
|| invoice.isEmailRequested
|| invoice.isPhoneRequested)) {
_submitState = SubmitState::Validation;
_form->validateInformation(_form->savedInformation());
return;
}
_submitState = SubmitState::Finishing;
_webviewWindow = std::make_unique<Ui::WebviewWindow>(
_form->details().url,
panelDelegate());
@ -237,4 +305,62 @@ bool CheckoutProcess::panelWebviewNavigationAttempt(const QString &uri) {
return false;
}
void CheckoutProcess::panelEditShippingInformation() {
showEditInformation(Ui::EditField::ShippingInformation);
}
void CheckoutProcess::panelEditName() {
showEditInformation(Ui::EditField::Name);
}
void CheckoutProcess::panelEditEmail() {
showEditInformation(Ui::EditField::Email);
}
void CheckoutProcess::panelEditPhone() {
showEditInformation(Ui::EditField::Phone);
}
void CheckoutProcess::showForm() {
_panel->showForm(
_form->invoice(),
_form->savedInformation(),
_form->shippingOptions());
}
void CheckoutProcess::showEditInformation(Ui::EditField field) {
if (_submitState != SubmitState::None) {
return;
}
_panel->showEditInformation(
_form->invoice(),
_form->savedInformation(),
field);
}
void CheckoutProcess::chooseShippingOption() {
_panel->chooseShippingOption(_form->shippingOptions());
}
void CheckoutProcess::panelChooseShippingOption() {
if (_submitState != SubmitState::None) {
return;
}
chooseShippingOption();
}
void CheckoutProcess::panelChangeShippingOption(const QString &id) {
_form->setShippingOption(id);
showForm();
}
void CheckoutProcess::panelValidateInformation(
Ui::RequestedInformation data) {
_form->validateInformation(data);
}
void CheckoutProcess::panelShowBox(object_ptr<Ui::BoxContent> box) {
_panel->showBox(std::move(box));
}
} // namespace Payments

View File

@ -19,6 +19,7 @@ class Session;
namespace Payments::Ui {
class Panel;
class WebviewWindow;
enum class EditField;
} // namespace Payments::Ui
namespace Payments {
@ -27,6 +28,7 @@ class Form;
struct FormUpdate;
struct FormError;
struct SendError;
struct ValidateError;
class CheckoutProcess final
: public base::has_weak_ptr
@ -45,22 +47,44 @@ public:
void requestActivate();
private:
enum class SubmitState {
None,
Validation,
Validated,
Finishing,
};
[[nodiscard]] not_null<PanelDelegate*> panelDelegate();
void handleFormUpdate(const FormUpdate &update);
void handleFormError(const FormError &error);
void handleValidateError(const ValidateError &error);
void handleSendError(const SendError &error);
void showForm();
void showEditInformation(Ui::EditField field);
void chooseShippingOption();
void panelRequestClose() override;
void panelCloseSure() override;
void panelSubmit() override;
void panelWebviewMessage(const QJsonDocument &message) override;
bool panelWebviewNavigationAttempt(const QString &uri) override;
void panelEditShippingInformation() override;
void panelEditName() override;
void panelEditEmail() override;
void panelEditPhone() override;
void panelChooseShippingOption() override;
void panelChangeShippingOption(const QString &id) override;
void panelValidateInformation(Ui::RequestedInformation data) override;
void panelShowBox(object_ptr<Ui::BoxContent> box) override;
const not_null<Main::Session*> _session;
const std::unique_ptr<Form> _form;
const std::unique_ptr<Ui::Panel> _panel;
std::unique_ptr<Ui::WebviewWindow> _webviewWindow;
SubmitState _submitState = SubmitState::None;
rpl::lifetime _lifetime;

View File

@ -27,16 +27,53 @@ namespace {
});
}
[[nodiscard]] std::vector<Ui::LabeledPrice> ParsePrices(
const MTPVector<MTPLabeledPrice> &data) {
return ranges::views::all(
data.v
) | ranges::views::transform([](const MTPLabeledPrice &price) {
return price.match([&](const MTPDlabeledPrice &data) {
return Ui::LabeledPrice{
.label = qs(data.vlabel()),
.price = *reinterpret_cast<const int64*>(&data.vamount().v),
};
});
}) | ranges::to_vector;
}
[[nodiscard]] MTPPaymentRequestedInfo Serialize(
const Ui::RequestedInformation &information) {
using Flag = MTPDpaymentRequestedInfo::Flag;
return MTP_paymentRequestedInfo(
MTP_flags((information.name.isEmpty() ? Flag(0) : Flag::f_name)
| (information.email.isEmpty() ? Flag(0) : Flag::f_email)
| (information.phone.isEmpty() ? Flag(0) : Flag::f_phone)
| (information.shippingAddress
? Flag::f_shipping_address
: Flag(0))),
MTP_string(information.name),
MTP_string(information.phone),
MTP_string(information.email),
MTP_postAddress(
MTP_string(information.shippingAddress.address1),
MTP_string(information.shippingAddress.address2),
MTP_string(information.shippingAddress.city),
MTP_string(information.shippingAddress.state),
MTP_string(information.shippingAddress.countryIso2),
MTP_string(information.shippingAddress.postCode)));
}
} // namespace
Form::Form(not_null<Main::Session*> session, FullMsgId itemId)
: _session(session)
, _api(&_session->mtp())
, _msgId(itemId.msg) {
requestForm();
}
void Form::requestForm() {
_session->api().request(MTPpayments_GetPaymentForm(
_api.request(MTPpayments_GetPaymentForm(
MTP_int(_msgId)
)).done([=](const MTPpayments_PaymentForm &result) {
result.match([&](const auto &data) {
@ -69,18 +106,8 @@ void Form::processForm(const MTPDpayments_paymentForm &data) {
}
void Form::processInvoice(const MTPDinvoice &data) {
auto &&prices = ranges::views::all(
data.vprices().v
) | ranges::views::transform([](const MTPLabeledPrice &price) {
return price.match([&](const MTPDlabeledPrice &data) {
return Ui::LabeledPrice{
.label = qs(data.vlabel()),
.price = data.vamount().v,
};
});
});
_invoice = Ui::Invoice{
.prices = prices | ranges::to_vector,
.prices = ParsePrices(data.vprices()),
.currency = qs(data.vcurrency()),
.isNameRequested = data.is_name_requested(),
@ -115,7 +142,7 @@ void Form::processDetails(const MTPDpayments_paymentForm &data) {
void Form::processSavedInformation(const MTPDpaymentRequestedInfo &data) {
const auto address = data.vshipping_address();
_savedInformation = Ui::SavedInformation{
_savedInformation = Ui::RequestedInformation{
.name = qs(data.vname().value_or_empty()),
.phone = qs(data.vphone().value_or_empty()),
.email = qs(data.vemail().value_or_empty()),
@ -132,11 +159,17 @@ void Form::processSavedCredentials(
}
void Form::send(const QByteArray &serializedCredentials) {
_session->api().request(MTPpayments_SendPaymentForm(
MTP_flags(0),
using Flag = MTPpayments_SendPaymentForm::Flag;
_api.request(MTPpayments_SendPaymentForm(
MTP_flags((_requestedInformationId.isEmpty()
? Flag(0)
: Flag::f_requested_info_id)
| (_shippingOptions.selectedId.isEmpty()
? Flag(0)
: Flag::f_shipping_option_id)),
MTP_int(_msgId),
MTPstring(), // requested_info_id
MTPstring(), // shipping_option_id,
MTP_string(_requestedInformationId),
MTP_string(_shippingOptions.selectedId),
MTP_inputPaymentCredentials(
MTP_flags(0),
MTP_dataJSON(MTP_bytes(serializedCredentials)))
@ -151,4 +184,59 @@ void Form::send(const QByteArray &serializedCredentials) {
}).send();
}
void Form::validateInformation(const Ui::RequestedInformation &information) {
if (_validateRequestId) {
if (_validatedInformation == information) {
return;
}
_api.request(base::take(_validateRequestId)).cancel();
}
_validatedInformation = information;
_validateRequestId = _api.request(MTPpayments_ValidateRequestedInfo(
MTP_flags(0), // #TODO payments save information
MTP_int(_msgId),
Serialize(information)
)).done([=](const MTPpayments_ValidatedRequestedInfo &result) {
_validateRequestId = 0;
const auto oldSelectedId = _shippingOptions.selectedId;
result.match([&](const MTPDpayments_validatedRequestedInfo &data) {
_requestedInformationId = data.vid().value_or_empty();
processShippingOptions(
data.vshipping_options().value_or_empty());
});
_shippingOptions.selectedId = ranges::contains(
_shippingOptions.list,
oldSelectedId,
&Ui::ShippingOption::id
) ? oldSelectedId : QString();
if (_shippingOptions.selectedId.isEmpty()
&& _shippingOptions.list.size() == 1) {
_shippingOptions.selectedId = _shippingOptions.list.front().id;
}
_savedInformation = _validatedInformation;
_updates.fire({ ValidateFinished{} });
}).fail([=](const MTP::Error &error) {
_validateRequestId = 0;
_updates.fire({ ValidateError{ error.type() } });
}).send();
}
void Form::setShippingOption(const QString &id) {
_shippingOptions.selectedId = id;
}
void Form::processShippingOptions(const QVector<MTPShippingOption> &data) {
_shippingOptions = Ui::ShippingOptions{ ranges::views::all(
data
) | ranges::views::transform([](const MTPShippingOption &option) {
return option.match([](const MTPDshippingOption &data) {
return Ui::ShippingOption{
.id = qs(data.vid()),
.title = qs(data.vtitle()),
.prices = ParsePrices(data.vprices()),
};
});
}) | ranges::to_vector };
}
} // namespace Payments

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "payments/ui/payments_panel_data.h"
#include "mtproto/sender.h"
namespace Main {
class Session;
@ -34,10 +35,16 @@ struct FormDetails {
struct FormReady {};
struct ValidateFinished {};
struct FormError {
QString type;
};
struct ValidateError {
QString type;
};
struct SendError {
QString type;
};
@ -54,8 +61,10 @@ struct FormUpdate {
std::variant<
FormReady,
FormError,
ValidateError,
SendError,
VerificationNeeded,
ValidateFinished,
PaymentFinished> data;
};
@ -69,17 +78,22 @@ public:
[[nodiscard]] const FormDetails &details() const {
return _details;
}
[[nodiscard]] const Ui::SavedInformation &savedInformation() const {
[[nodiscard]] const Ui::RequestedInformation &savedInformation() const {
return _savedInformation;
}
[[nodiscard]] const Ui::SavedCredentials &savedCredentials() const {
return _savedCredentials;
}
[[nodiscard]] const Ui::ShippingOptions &shippingOptions() const {
return _shippingOptions;
}
[[nodiscard]] rpl::producer<FormUpdate> updates() const {
return _updates.events();
}
void validateInformation(const Ui::RequestedInformation &information);
void setShippingOption(const QString &id);
void send(const QByteArray &serializedCredentials);
private:
@ -90,15 +104,23 @@ private:
void processSavedInformation(const MTPDpaymentRequestedInfo &data);
void processSavedCredentials(
const MTPDpaymentSavedCredentialsCard &data);
void processShippingOptions(const QVector<MTPShippingOption> &data);
const not_null<Main::Session*> _session;
MTP::Sender _api;
MsgId _msgId = 0;
Ui::Invoice _invoice;
FormDetails _details;
Ui::SavedInformation _savedInformation;
Ui::RequestedInformation _savedInformation;
Ui::SavedCredentials _savedCredentials;
Ui::RequestedInformation _validatedInformation;
mtpRequestId _validateRequestId = 0;
Ui::ShippingOptions _shippingOptions;
QString _requestedInformationId;
rpl::event_stream<FormUpdate> _updates;
};

View File

@ -8,3 +8,5 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
using "ui/basic.style";
using "passport/passport.style";
paymentsFormPricePadding: margins(22px, 7px, 22px, 6px);

View File

@ -0,0 +1,262 @@
/*
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_edit_information.h"
#include "payments/ui/payments_panel_delegate.h"
#include "passport/ui/passport_details_row.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 "lang/lang_keys.h"
#include "styles/style_payments.h"
#include "styles/style_passport.h"
namespace Payments::Ui {
namespace {
constexpr auto kMaxStreetSize = 64;
constexpr auto kMaxPostcodeSize = 10;
constexpr auto kMaxNameSize = 64;
constexpr auto kMaxEmailSize = 128;
constexpr auto kMaxPhoneSize = 16;
} // namespace
EditInformation::EditInformation(
QWidget *parent,
const Invoice &invoice,
const RequestedInformation &current,
EditField field,
not_null<PanelDelegate*> delegate)
: _delegate(delegate)
, _invoice(invoice)
, _information(current)
, _scroll(this, st::passportPanelScroll)
, _topShadow(this)
, _bottomShadow(this)
, _done(
this,
tr::lng_about_done(),
st::passportPanelSaveValue) {
setupControls();
}
void EditInformation::setupControls() {
const auto inner = setupContent();
_done->addClickHandler([=] {
_delegate->panelValidateInformation(collect());
});
using namespace rpl::mappers;
_topShadow->toggleOn(
_scroll->scrollTopValue() | rpl::map(_1 > 0));
_bottomShadow->toggleOn(rpl::combine(
_scroll->scrollTopValue(),
_scroll->heightValue(),
inner->heightValue(),
_1 + _2 < _3));
}
not_null<RpWidget*> EditInformation::setupContent() {
const auto inner = _scroll->setOwnedWidget(
object_ptr<VerticalLayout>(this));
_scroll->widthValue(
) | rpl::start_with_next([=](int width) {
inner->resizeToWidth(width);
}, inner->lifetime());
const auto showBox = [=](object_ptr<BoxContent> box) {
_delegate->panelShowBox(std::move(box));
};
using Type = Passport::Ui::PanelDetailsType;
auto maxLabelWidth = 0;
if (_invoice.isShippingAddressRequested) {
accumulate_max(
maxLabelWidth,
Row::LabelWidth(tr::lng_passport_street(tr::now)));
accumulate_max(
maxLabelWidth,
Row::LabelWidth(tr::lng_passport_city(tr::now)));
accumulate_max(
maxLabelWidth,
Row::LabelWidth(tr::lng_passport_state(tr::now)));
accumulate_max(
maxLabelWidth,
Row::LabelWidth(tr::lng_passport_country(tr::now)));
accumulate_max(
maxLabelWidth,
Row::LabelWidth(tr::lng_passport_postcode(tr::now)));
}
if (_invoice.isNameRequested) {
accumulate_max(
maxLabelWidth,
Row::LabelWidth(tr::lng_payments_info_name(tr::now)));
}
if (_invoice.isEmailRequested) {
accumulate_max(
maxLabelWidth,
Row::LabelWidth(tr::lng_payments_info_email(tr::now)));
}
if (_invoice.isPhoneRequested) {
accumulate_max(
maxLabelWidth,
Row::LabelWidth(tr::lng_payments_info_phone(tr::now)));
}
if (_invoice.isShippingAddressRequested) {
_street1 = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Text,
tr::lng_passport_street(tr::now),
maxLabelWidth,
_information.shippingAddress.address1,
QString(),
kMaxStreetSize));
_street2 = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Text,
tr::lng_passport_street(tr::now),
maxLabelWidth,
_information.shippingAddress.address2,
QString(),
kMaxStreetSize));
_city = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Text,
tr::lng_passport_city(tr::now),
maxLabelWidth,
_information.shippingAddress.city,
QString(),
kMaxStreetSize));
_state = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Text,
tr::lng_passport_state(tr::now),
maxLabelWidth,
_information.shippingAddress.state,
QString(),
kMaxStreetSize));
_country = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Country,
tr::lng_passport_country(tr::now),
maxLabelWidth,
_information.shippingAddress.countryIso2,
QString()));
_postcode = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Postcode,
tr::lng_passport_postcode(tr::now),
maxLabelWidth,
_information.shippingAddress.postCode,
QString(),
kMaxPostcodeSize));
//StreetValidate, // #TODO payments
//CityValidate,
//CountryValidate,
//CountryFormat,
//PostcodeValidate,
}
if (_invoice.isNameRequested) {
_name = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Text,
tr::lng_payments_info_name(tr::now),
maxLabelWidth,
_information.name,
QString(),
kMaxNameSize));
}
if (_invoice.isEmailRequested) {
_email = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Text,
tr::lng_payments_info_email(tr::now),
maxLabelWidth,
_information.email,
QString(),
kMaxEmailSize));
}
if (_invoice.isPhoneRequested) {
_phone = inner->add(
Row::Create(
inner,
showBox,
QString(),
Type::Text,
tr::lng_payments_info_phone(tr::now),
maxLabelWidth,
_information.phone,
QString(),
kMaxPhoneSize));
}
return inner;
}
void EditInformation::resizeEvent(QResizeEvent *e) {
updateControlsGeometry();
}
void EditInformation::updateControlsGeometry() {
const auto submitTop = height() - _done->height();
_scroll->setGeometry(0, 0, width(), submitTop);
_topShadow->resizeToWidth(width());
_topShadow->moveToLeft(0, 0);
_bottomShadow->resizeToWidth(width());
_bottomShadow->moveToLeft(0, submitTop - st::lineWidth);
_done->setFullWidth(width());
_done->moveToLeft(0, submitTop);
_scroll->updateBars();
}
RequestedInformation EditInformation::collect() const {
return {
.name = _name ? _name->valueCurrent() : QString(),
.phone = _phone ? _phone->valueCurrent() : QString(),
.email = _email ? _email->valueCurrent() : QString(),
.shippingAddress = {
.address1 = _street1 ? _street1->valueCurrent() : QString(),
.address2 = _street2 ? _street2->valueCurrent() : QString(),
.city = _city ? _city->valueCurrent() : QString(),
.state = _state ? _state->valueCurrent() : QString(),
.countryIso2 = _country ? _country->valueCurrent() : QString(),
.postCode = _postcode ? _postcode->valueCurrent() : QString(),
},
};
}
} // namespace Payments::Ui

View File

@ -0,0 +1,71 @@
/*
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
*/
#pragma once
#include "ui/rp_widget.h"
#include "payments/ui/payments_panel_data.h"
#include "base/object_ptr.h"
namespace Ui {
class ScrollArea;
class FadeShadow;
class RoundButton;
} // namespace Ui
namespace Passport::Ui {
class PanelDetailsRow;
} // namespace Passport::Ui
namespace Payments::Ui {
using namespace ::Ui;
class PanelDelegate;
class EditInformation final : public RpWidget {
public:
EditInformation(
QWidget *parent,
const Invoice &invoice,
const RequestedInformation &current,
EditField field,
not_null<PanelDelegate*> delegate);
private:
using Row = Passport::Ui::PanelDetailsRow;
void resizeEvent(QResizeEvent *e) override;
void setupControls();
[[nodiscard]] not_null<Ui::RpWidget*> setupContent();
void updateControlsGeometry();
[[nodiscard]] RequestedInformation collect() const;
const not_null<PanelDelegate*> _delegate;
Invoice _invoice;
RequestedInformation _information;
object_ptr<ScrollArea> _scroll;
object_ptr<FadeShadow> _topShadow;
object_ptr<FadeShadow> _bottomShadow;
object_ptr<RoundButton> _done;
Row *_street1 = nullptr;
Row *_street2 = nullptr;
Row *_city = nullptr;
Row *_state = nullptr;
Row *_country = nullptr;
Row *_postcode = nullptr;
Row *_name = nullptr;
Row *_email = nullptr;
Row *_phone = nullptr;
};
} // namespace Payments::Ui

View File

@ -8,10 +8,13 @@ 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 "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 "lang/lang_keys.h"
#include "styles/style_payments.h"
#include "styles/style_passport.h"
@ -19,24 +22,56 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Payments::Ui {
using namespace ::Ui;
using namespace Passport::Ui;
class PanelDelegate;
FormSummary::FormSummary(
QWidget *parent,
const Invoice &invoice,
const RequestedInformation &current,
const ShippingOptions &options,
not_null<PanelDelegate*> delegate)
: _delegate(delegate)
, _invoice(invoice)
, _options(options)
, _information(current)
, _scroll(this, st::passportPanelScroll)
, _topShadow(this)
, _bottomShadow(this)
, _submit(
this,
tr::lng_payments_pay_amount(lt_amount, rpl::single(QString("much"))),
tr::lng_payments_pay_amount(
lt_amount,
rpl::single(computeTotalAmount())),
st::passportPanelAuthorize) {
setupControls();
}
QString FormSummary::computeAmount(int64 amount) const {
return FillAmountAndCurrency(amount, _invoice.currency);
}
QString 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 computeAmount(total + shipping);
}
void FormSummary::setupControls() {
const auto inner = setupContent();
@ -64,6 +99,116 @@ not_null<Ui::RpWidget*> FormSummary::setupContent() {
inner->resizeToWidth(width);
}, inner->lifetime());
for (const auto &price : _invoice.prices) {
inner->add(
object_ptr<Ui::FlatLabel>(
inner,
price.label + ": " + computeAmount(price.price),
st::passportFormPolicy),
st::paymentsFormPricePadding);
}
const auto selected = ranges::find(
_options.list,
_options.selectedId,
&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);
}
}
inner->add(
object_ptr<Ui::FlatLabel>(
inner,
"Total: " + computeTotalAmount(),
st::passportFormHeader),
st::passportFormHeaderPadding);
inner->add(
object_ptr<Ui::BoxContentDivider>(
inner,
st::passportFormDividerHeight),
{ 0, 0, 0, st::passportFormHeaderPadding.top() });
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()) {
list.push_back(value);
}
};
push(_information.shippingAddress.address1);
push(_information.shippingAddress.address2);
push(_information.shippingAddress.city);
push(_information.shippingAddress.state);
push(_information.shippingAddress.countryIso2);
push(_information.shippingAddress.postCode);
info->updateContent(
tr::lng_payments_shipping_address(tr::now),
(list.isEmpty() ? "enter pls" : list.join(", ")),
!list.isEmpty(),
false,
anim::type::instant);
}
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
: "enter pls"),
(selected != end(_options.list)),
false,
anim::type::instant);
}
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()
? "enter pls"
: _information.name),
!_information.name.isEmpty(),
false,
anim::type::instant);
}
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()
? "enter pls"
: _information.email),
!_information.email.isEmpty(),
false,
anim::type::instant);
}
if (_invoice.isPhoneRequested) {
const auto phone = inner->add(object_ptr<FormRow>(inner));
phone->addClickHandler([=] { _delegate->panelEditPhone(); });
phone->updateContent(
tr::lng_payments_info_email(tr::now),
(_information.phone.isEmpty()
? "enter pls"
: _information.phone),
!_information.phone.isEmpty(),
false,
anim::type::instant);
}
return inner;
}

View File

@ -28,6 +28,8 @@ public:
FormSummary(
QWidget *parent,
const Invoice &invoice,
const RequestedInformation &current,
const ShippingOptions &options,
not_null<PanelDelegate*> delegate);
private:
@ -37,7 +39,13 @@ private:
[[nodiscard]] not_null<Ui::RpWidget*> setupContent();
void updateControlsGeometry();
[[nodiscard]] QString computeAmount(int64 amount) const;
[[nodiscard]] QString computeTotalAmount() const;
const not_null<PanelDelegate*> _delegate;
Invoice _invoice;
ShippingOptions _options;
RequestedInformation _information;
object_ptr<ScrollArea> _scroll;
object_ptr<FadeShadow> _topShadow;
object_ptr<FadeShadow> _bottomShadow;

View File

@ -8,8 +8,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/ui/payments_panel.h"
#include "payments/ui/payments_form_summary.h"
#include "payments/ui/payments_edit_information.h"
#include "payments/ui/payments_panel_delegate.h"
#include "ui/widgets/separate_panel.h"
#include "ui/boxes/single_choice_box.h"
#include "lang/lang_keys.h"
#include "styles/style_payments.h"
#include "styles/style_passport.h"
@ -39,9 +41,63 @@ void Panel::requestActivate() {
_widget->showAndActivate();
}
void Panel::showForm(const Invoice &invoice) {
void Panel::showForm(
const Invoice &invoice,
const RequestedInformation &current,
const ShippingOptions &options) {
_widget->showInner(
base::make_unique_q<FormSummary>(_widget.get(), invoice, _delegate));
base::make_unique_q<FormSummary>(
_widget.get(),
invoice,
current,
options,
_delegate));
}
void Panel::showEditInformation(
const Invoice &invoice,
const RequestedInformation &current,
EditField field) {
_widget->showInner(base::make_unique_q<EditInformation>(
_widget.get(),
invoice,
current,
field,
_delegate));
}
void Panel::chooseShippingOption(const ShippingOptions &options) {
showBox(Box([=](not_null<Ui::GenericBox*> box) {
auto list = options.list | ranges::views::transform(
&ShippingOption::title
) | ranges::to_vector;
const auto i = ranges::find(
options.list,
options.selectedId,
&ShippingOption::id);
const auto save = [=](int option) {
_delegate->panelChangeShippingOption(options.list[option].id);
};
SingleChoiceBox(box, {
.title = tr::lng_payments_shipping_method(),
.options = list,
.initialSelection = (i != end(options.list)
? (i - begin(options.list))
: -1),
.callback = save,
});
}));
}
void Panel::showBox(object_ptr<Ui::BoxContent> box) {
_widget->showBox(
std::move(box),
Ui::LayerOption::KeepOther,
anim::type::normal);
}
void Panel::showToast(const QString &text) {
_widget->showToast(text);
}
} // namespace Payments::Ui

View File

@ -7,8 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/object_ptr.h"
namespace Ui {
class SeparatePanel;
class BoxContent;
} // namespace Ui
namespace Payments::Ui {
@ -17,6 +20,9 @@ using namespace ::Ui;
class PanelDelegate;
struct Invoice;
struct RequestedInformation;
struct ShippingOptions;
enum class EditField;
class Panel final {
public:
@ -25,7 +31,18 @@ public:
void requestActivate();
void showForm(const Invoice &invoice);
void showForm(
const Invoice &invoice,
const RequestedInformation &current,
const ShippingOptions &options);
void showEditInformation(
const Invoice &invoice,
const RequestedInformation &current,
EditField field);
void chooseShippingOption(const ShippingOptions &options);
void showBox(object_ptr<Ui::BoxContent> box);
void showToast(const QString &text);
private:
const not_null<PanelDelegate*> _delegate;

View File

@ -11,7 +11,7 @@ namespace Payments::Ui {
struct LabeledPrice {
QString label;
uint64 price = 0;
int64 price = 0;
};
struct Invoice {
@ -36,6 +36,17 @@ struct Invoice {
}
};
struct ShippingOption {
QString id;
QString title;
std::vector<LabeledPrice> prices;
};
struct ShippingOptions {
std::vector<ShippingOption> list;
QString selectedId;
};
struct Address {
QString address1;
QString address2;
@ -52,9 +63,21 @@ struct Address {
[[nodiscard]] explicit operator bool() const {
return valid();
}
inline bool operator==(const Address &other) const {
return (address1 == other.address1)
&& (address2 == other.address2)
&& (city == other.city)
&& (state == other.state)
&& (countryIso2 == other.countryIso2)
&& (postCode == other.postCode);
}
inline bool operator!=(const Address &other) const {
return !(*this == other);
}
};
struct SavedInformation {
struct RequestedInformation {
QString name;
QString phone;
QString email;
@ -69,6 +92,16 @@ struct SavedInformation {
[[nodiscard]] explicit operator bool() const {
return !empty();
}
inline bool operator==(const RequestedInformation &other) const {
return (name == other.name)
&& (phone == other.phone)
&& (email == other.email)
&& (shippingAddress == other.shippingAddress);
}
inline bool operator!=(const RequestedInformation &other) const {
return !(*this == other);
}
};
struct SavedCredentials {
@ -83,4 +116,11 @@ struct SavedCredentials {
}
};
enum class EditField {
ShippingInformation,
Name,
Email,
Phone,
};
} // namespace Payments::Ui

View File

@ -7,11 +7,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/object_ptr.h"
class QJsonDocument;
class QString;
namespace Ui {
class BoxContent;
} // namespace Ui
namespace Payments::Ui {
using namespace ::Ui;
struct RequestedInformation;
class PanelDelegate {
public:
virtual void panelRequestClose() = 0;
@ -19,6 +29,16 @@ public:
virtual void panelSubmit() = 0;
virtual void panelWebviewMessage(const QJsonDocument &message) = 0;
virtual bool panelWebviewNavigationAttempt(const QString &uri) = 0;
virtual void panelEditShippingInformation() = 0;
virtual void panelEditName() = 0;
virtual void panelEditEmail() = 0;
virtual void panelEditPhone() = 0;
virtual void panelChooseShippingOption() = 0;
virtual void panelChangeShippingOption(const QString &id) = 0;
virtual void panelValidateInformation(RequestedInformation data) = 0;
virtual void panelShowBox(object_ptr<BoxContent> box) = 0;
};
} // namespace Payments::Ui

View File

@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/checkbox.h"
#include "ui/widgets/level_meter.h"
#include "ui/widgets/buttons.h"
#include "boxes/single_choice_box.h"
#include "ui/boxes/single_choice_box.h"
#include "boxes/confirm_box.h"
#include "platform/platform_specific.h"
#include "main/main_session.h"

View File

@ -0,0 +1,443 @@
/*
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 "ui/boxes/country_select_box.h"
#include "lang/lang_keys.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/multi_select.h"
#include "ui/effects/ripple_animation.h"
#include "data/data_countries.h"
#include "base/qt_adapters.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_intro.h"
#include <QtCore/QRegularExpression>
namespace Ui {
namespace {
QString LastValidISO;
} // namespace
class CountrySelectBox::Inner : public TWidget {
public:
Inner(QWidget *parent, Type type);
~Inner();
void updateFilter(QString filter = QString());
void selectSkip(int32 dir);
void selectSkipPage(int32 h, int32 dir);
void chooseCountry();
void refresh();
[[nodiscard]] rpl::producer<QString> countryChosen() const {
return _countryChosen.events();
}
[[nodiscard]] rpl::producer<ScrollToRequest> mustScrollTo() const {
return _mustScrollTo.events();
}
protected:
void paintEvent(QPaintEvent *e) override;
void enterEventHook(QEvent *e) override;
void leaveEventHook(QEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
private:
void updateSelected() {
updateSelected(mapFromGlobal(QCursor::pos()));
}
void updateSelected(QPoint localPos);
void updateSelectedRow();
void updateRow(int index);
void setPressed(int pressed);
const std::vector<not_null<const Data::CountryInfo*>> &current() const;
Type _type = Type::Phones;
int _rowHeight = 0;
int _selected = -1;
int _pressed = -1;
QString _filter;
bool _mouseSelection = false;
std::vector<std::unique_ptr<RippleAnimation>> _ripples;
std::vector<not_null<const Data::CountryInfo*>> _list;
std::vector<not_null<const Data::CountryInfo*>> _filtered;
base::flat_map<QChar, std::vector<int>> _byLetter;
std::vector<std::vector<QString>> _namesList;
rpl::event_stream<QString> _countryChosen;
rpl::event_stream<ScrollToRequest> _mustScrollTo;
};
CountrySelectBox::CountrySelectBox(QWidget*)
: _select(this, st::defaultMultiSelect, tr::lng_country_ph()) {
}
CountrySelectBox::CountrySelectBox(QWidget*, const QString &iso, Type type)
: _type(type)
, _select(this, st::defaultMultiSelect, tr::lng_country_ph()) {
if (Data::CountriesByISO2().contains(iso)) {
LastValidISO = iso;
}
}
rpl::producer<QString> CountrySelectBox::countryChosen() const {
return _inner->countryChosen();
}
void CountrySelectBox::prepare() {
setTitle(tr::lng_country_select());
_select->resizeToWidth(st::boxWidth);
_select->setQueryChangedCallback([=](const QString &query) {
applyFilterUpdate(query);
});
_select->setSubmittedCallback([=](Qt::KeyboardModifiers) {
submit();
});
_inner = setInnerWidget(
object_ptr<Inner>(this, _type),
st::countriesScroll,
_select->height());
addButton(tr::lng_close(), [=] { closeBox(); });
setDimensions(st::boxWidth, st::boxMaxListHeight);
_inner->mustScrollTo(
) | rpl::start_with_next([=](ScrollToRequest request) {
onScrollToY(request.ymin, request.ymax);
}, lifetime());
}
void CountrySelectBox::submit() {
_inner->chooseCountry();
}
void CountrySelectBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Down) {
_inner->selectSkip(1);
} else if (e->key() == Qt::Key_Up) {
_inner->selectSkip(-1);
} else if (e->key() == Qt::Key_PageDown) {
_inner->selectSkipPage(height() - _select->height(), 1);
} else if (e->key() == Qt::Key_PageUp) {
_inner->selectSkipPage(height() - _select->height(), -1);
} else {
BoxContent::keyPressEvent(e);
}
}
void CountrySelectBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_select->resizeToWidth(width());
_select->moveToLeft(0, 0);
_inner->resizeToWidth(width());
}
void CountrySelectBox::applyFilterUpdate(const QString &query) {
onScrollToY(0);
_inner->updateFilter(query);
}
void CountrySelectBox::setInnerFocus() {
_select->setInnerFocus();
}
CountrySelectBox::Inner::Inner(QWidget *parent, Type type)
: TWidget(parent)
, _type(type)
, _rowHeight(st::countryRowHeight) {
setAttribute(Qt::WA_OpaquePaintEvent);
const auto &byISO2 = Data::CountriesByISO2();
_list.reserve(byISO2.size());
_namesList.reserve(byISO2.size());
const auto l = byISO2.constFind(LastValidISO);
const auto lastValid = (l != byISO2.cend()) ? (*l) : nullptr;
if (lastValid) {
_list.emplace_back(lastValid);
}
for (const auto &entry : Data::Countries()) {
if (&entry != lastValid) {
_list.emplace_back(&entry);
}
}
auto index = 0;
for (const auto info : _list) {
auto full = QString::fromUtf8(info->name)
+ ' '
+ (info->alternativeName
? QString::fromUtf8(info->alternativeName)
: QString());
const auto namesList = std::move(full).toLower().split(
QRegularExpression("[\\s\\-]"),
base::QStringSkipEmptyParts);
auto &names = _namesList.emplace_back();
names.reserve(namesList.size());
for (const auto &name : namesList) {
const auto part = name.trimmed();
if (part.isEmpty()) {
continue;
}
const auto ch = part[0];
auto &byLetter = _byLetter[ch];
if (byLetter.empty() || byLetter.back() != index) {
byLetter.push_back(index);
}
names.push_back(part);
}
++index;
}
_filter = u"a"_q;
updateFilter();
}
void CountrySelectBox::Inner::paintEvent(QPaintEvent *e) {
Painter p(this);
QRect r(e->rect());
p.setClipRect(r);
const auto &list = current();
if (list.empty()) {
p.fillRect(r, st::boxBg);
p.setFont(st::noContactsFont);
p.setPen(st::noContactsColor);
p.drawText(QRect(0, 0, width(), st::noContactsHeight), tr::lng_country_none(tr::now), style::al_center);
return;
}
const auto l = int(list.size());
if (r.intersects(QRect(0, 0, width(), st::countriesSkip))) {
p.fillRect(r.intersected(QRect(0, 0, width(), st::countriesSkip)), st::countryRowBg);
}
int32 from = std::clamp((r.y() - st::countriesSkip) / _rowHeight, 0, l);
int32 to = std::clamp((r.y() + r.height() - st::countriesSkip + _rowHeight - 1) / _rowHeight, 0, l);
for (int32 i = from; i < to; ++i) {
auto selected = (i == (_pressed >= 0 ? _pressed : _selected));
auto y = st::countriesSkip + i * _rowHeight;
p.fillRect(0, y, width(), _rowHeight, selected ? st::countryRowBgOver : st::countryRowBg);
if (_ripples.size() > i && _ripples[i]) {
_ripples[i]->paint(p, 0, y, width());
if (_ripples[i]->empty()) {
_ripples[i].reset();
}
}
auto code = QString("+") + list[i]->code;
auto codeWidth = st::countryRowCodeFont->width(code);
auto name = QString::fromUtf8(list[i]->name);
auto nameWidth = st::countryRowNameFont->width(name);
auto availWidth = width() - st::countryRowPadding.left() - st::countryRowPadding.right() - codeWidth - st::boxScroll.width;
if (nameWidth > availWidth) {
name = st::countryRowNameFont->elided(name, availWidth);
nameWidth = st::countryRowNameFont->width(name);
}
p.setFont(st::countryRowNameFont);
p.setPen(st::countryRowNameFg);
p.drawTextLeft(st::countryRowPadding.left(), y + st::countryRowPadding.top(), width(), name);
if (_type == Type::Phones) {
p.setFont(st::countryRowCodeFont);
p.setPen(selected ? st::countryRowCodeFgOver : st::countryRowCodeFg);
p.drawTextLeft(st::countryRowPadding.left() + nameWidth + st::countryRowPadding.right(), y + st::countryRowPadding.top(), width(), code);
}
}
}
void CountrySelectBox::Inner::enterEventHook(QEvent *e) {
setMouseTracking(true);
}
void CountrySelectBox::Inner::leaveEventHook(QEvent *e) {
_mouseSelection = false;
setMouseTracking(false);
if (_selected >= 0) {
updateSelectedRow();
_selected = -1;
}
}
void CountrySelectBox::Inner::mouseMoveEvent(QMouseEvent *e) {
_mouseSelection = true;
updateSelected(e->pos());
}
void CountrySelectBox::Inner::mousePressEvent(QMouseEvent *e) {
_mouseSelection = true;
updateSelected(e->pos());
setPressed(_selected);
const auto &list = current();
if (_pressed >= 0 && _pressed < list.size()) {
if (_ripples.size() <= _pressed) {
_ripples.reserve(_pressed + 1);
while (_ripples.size() <= _pressed) {
_ripples.push_back(nullptr);
}
}
if (!_ripples[_pressed]) {
auto mask = RippleAnimation::rectMask(QSize(width(), _rowHeight));
_ripples[_pressed] = std::make_unique<RippleAnimation>(st::countryRipple, std::move(mask), [this, index = _pressed] {
updateRow(index);
});
_ripples[_pressed]->add(e->pos() - QPoint(0, st::countriesSkip + _pressed * _rowHeight));
}
}
}
void CountrySelectBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
auto pressed = _pressed;
setPressed(-1);
updateSelectedRow();
if (e->button() == Qt::LeftButton) {
if ((pressed >= 0) && pressed == _selected) {
chooseCountry();
}
}
}
void CountrySelectBox::Inner::updateFilter(QString filter) {
const auto words = TextUtilities::PrepareSearchWords(filter);
filter = words.isEmpty() ? QString() : words.join(' ');
if (_filter == filter) {
return;
}
_filter = filter;
const auto findWord = [&](
const std::vector<QString> &names,
const QString &word) {
for (const auto &name : names) {
if (name.startsWith(word)) {
return true;
}
}
return false;
};
const auto hasAllWords = [&](const std::vector<QString> &names) {
for (const auto &word : words) {
if (!findWord(names, word)) {
return false;
}
}
return true;
};
if (!_filter.isEmpty()) {
_filtered.clear();
for (const auto index : _byLetter[_filter[0].toLower()]) {
const auto &names = _namesList[index];
if (hasAllWords(_namesList[index])) {
_filtered.push_back(_list[index]);
}
}
}
refresh();
_selected = current().empty() ? -1 : 0;
update();
}
void CountrySelectBox::Inner::selectSkip(int32 dir) {
_mouseSelection = false;
const auto &list = current();
int cur = (_selected >= 0) ? _selected : -1;
cur += dir;
if (cur <= 0) {
_selected = list.empty() ? -1 : 0;
} else if (cur >= list.size()) {
_selected = -1;
} else {
_selected = cur;
}
if (_selected >= 0) {
_mustScrollTo.fire(ScrollToRequest(
st::countriesSkip + _selected * _rowHeight,
st::countriesSkip + (_selected + 1) * _rowHeight));
}
update();
}
void CountrySelectBox::Inner::selectSkipPage(int32 h, int32 dir) {
int32 points = h / _rowHeight;
if (!points) return;
selectSkip(points * dir);
}
void CountrySelectBox::Inner::chooseCountry() {
const auto &list = current();
_countryChosen.fire((_selected >= 0 && _selected < list.size())
? QString(list[_selected]->iso2)
: QString());
}
void CountrySelectBox::Inner::refresh() {
const auto &list = current();
resize(width(), list.empty() ? st::noContactsHeight : (list.size() * _rowHeight + st::countriesSkip));
}
void CountrySelectBox::Inner::updateSelected(QPoint localPos) {
if (!_mouseSelection) return;
auto in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(QCursor::pos()));
const auto &list = current();
auto selected = (in && localPos.y() >= st::countriesSkip && localPos.y() < st::countriesSkip + list.size() * _rowHeight) ? ((localPos.y() - st::countriesSkip) / _rowHeight) : -1;
if (_selected != selected) {
updateSelectedRow();
_selected = selected;
updateSelectedRow();
}
}
auto CountrySelectBox::Inner::current() const
-> const std::vector<not_null<const Data::CountryInfo*>> & {
return _filter.isEmpty() ? _list : _filtered;
}
void CountrySelectBox::Inner::updateSelectedRow() {
updateRow(_selected);
}
void CountrySelectBox::Inner::updateRow(int index) {
if (index >= 0) {
update(0, st::countriesSkip + index * _rowHeight, width(), _rowHeight);
}
}
void CountrySelectBox::Inner::setPressed(int pressed) {
if (_pressed >= 0 && _pressed < _ripples.size() && _ripples[_pressed]) {
_ripples[_pressed]->lastStop();
}
_pressed = pressed;
}
CountrySelectBox::Inner::~Inner() = default;
} // namespace Ui

View File

@ -0,0 +1,53 @@
/*
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
*/
#pragma once
#include "boxes/abstract_box.h"
#include "styles/style_widgets.h"
namespace Data {
struct CountryInfo;
} // namespace Data
namespace Ui {
class MultiSelect;
class RippleAnimation;
class CountrySelectBox : public BoxContent {
public:
enum class Type {
Phones,
Countries,
};
CountrySelectBox(QWidget*);
CountrySelectBox(QWidget*, const QString &iso, Type type);
[[nodiscard]] rpl::producer<QString> countryChosen() const;
protected:
void prepare() override;
void setInnerFocus() override;
void keyPressEvent(QKeyEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
void submit();
void applyFilterUpdate(const QString &query);
Type _type = Type::Phones;
object_ptr<MultiSelect> _select;
class Inner;
QPointer<Inner> _inner;
};
} // namespace Ui

View File

@ -5,11 +5,9 @@ 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 "boxes/single_choice_box.h"
#include "ui/boxes/single_choice_box.h"
#include "lang/lang_keys.h"
#include "storage/localstorage.h"
#include "mainwindow.h"
#include "ui/widgets/checkbox.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/padding_wrap.h"

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/multi_select.h"
#include "ui/effects/ripple_animation.h"
#include "ui/boxes/country_select_box.h"
#include "data/data_countries.h"
#include "base/qt_adapters.h"
#include "styles/style_layers.h"
@ -23,7 +24,8 @@ QString LastValidISO;
} // namespace
CountryInput::CountryInput(QWidget *parent, const style::InputField &st) : TWidget(parent)
CountryInput::CountryInput(QWidget *parent, const style::InputField &st)
: RpWidget(parent)
, _st(st)
, _text(tr::lng_country_code(tr::now)) {
resize(_st.width, _st.heightMin);
@ -91,8 +93,11 @@ void CountryInput::mouseMoveEvent(QMouseEvent *e) {
void CountryInput::mousePressEvent(QMouseEvent *e) {
mouseMoveEvent(e);
if (_active) {
auto box = Ui::show(Box<CountrySelectBox>());
connect(box, SIGNAL(countryChosen(const QString&)), this, SLOT(onChooseCountry(const QString&)));
auto box = Ui::show(Box<Ui::CountrySelectBox>());
box->countryChosen(
) | rpl::start_with_next([=](QString iso) {
chooseCountry(iso);
}, lifetime());
}
}
@ -125,7 +130,7 @@ void CountryInput::onChooseCode(const QString &code) {
update();
}
bool CountryInput::onChooseCountry(const QString &iso) {
bool CountryInput::chooseCountry(const QString &iso) {
Ui::hideLayer();
const auto &byISO2 = Data::CountriesByISO2();
@ -146,349 +151,3 @@ bool CountryInput::onChooseCountry(const QString &iso) {
void CountryInput::setText(const QString &newText) {
_text = _st.font->elided(newText, width() - _st.textMargins.left() - _st.textMargins.right());
}
CountrySelectBox::CountrySelectBox(QWidget*)
: _select(this, st::defaultMultiSelect, tr::lng_country_ph()) {
}
CountrySelectBox::CountrySelectBox(QWidget*, const QString &iso, Type type)
: _type(type)
, _select(this, st::defaultMultiSelect, tr::lng_country_ph()) {
if (Data::CountriesByISO2().contains(iso)) {
LastValidISO = iso;
}
}
void CountrySelectBox::prepare() {
setTitle(tr::lng_country_select());
_select->resizeToWidth(st::boxWidth);
_select->setQueryChangedCallback([=](const QString &query) {
applyFilterUpdate(query);
});
_select->setSubmittedCallback([=](Qt::KeyboardModifiers) {
submit();
});
_inner = setInnerWidget(
object_ptr<Inner>(this, _type),
st::countriesScroll,
_select->height());
addButton(tr::lng_close(), [=] { closeBox(); });
setDimensions(st::boxWidth, st::boxMaxListHeight);
connect(_inner, SIGNAL(mustScrollTo(int, int)), this, SLOT(onScrollToY(int, int)));
connect(_inner, SIGNAL(countryChosen(const QString&)), this, SIGNAL(countryChosen(const QString&)));
}
void CountrySelectBox::submit() {
_inner->chooseCountry();
}
void CountrySelectBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Down) {
_inner->selectSkip(1);
} else if (e->key() == Qt::Key_Up) {
_inner->selectSkip(-1);
} else if (e->key() == Qt::Key_PageDown) {
_inner->selectSkipPage(height() - _select->height(), 1);
} else if (e->key() == Qt::Key_PageUp) {
_inner->selectSkipPage(height() - _select->height(), -1);
} else {
BoxContent::keyPressEvent(e);
}
}
void CountrySelectBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_select->resizeToWidth(width());
_select->moveToLeft(0, 0);
_inner->resizeToWidth(width());
}
void CountrySelectBox::applyFilterUpdate(const QString &query) {
onScrollToY(0);
_inner->updateFilter(query);
}
void CountrySelectBox::setInnerFocus() {
_select->setInnerFocus();
}
CountrySelectBox::Inner::Inner(QWidget *parent, Type type)
: TWidget(parent)
, _type(type)
, _rowHeight(st::countryRowHeight) {
setAttribute(Qt::WA_OpaquePaintEvent);
const auto &byISO2 = Data::CountriesByISO2();
_list.reserve(byISO2.size());
_namesList.reserve(byISO2.size());
const auto l = byISO2.constFind(LastValidISO);
const auto lastValid = (l != byISO2.cend()) ? (*l) : nullptr;
if (lastValid) {
_list.emplace_back(lastValid);
}
for (const auto &entry : Data::Countries()) {
if (&entry != lastValid) {
_list.emplace_back(&entry);
}
}
auto index = 0;
for (const auto info : _list) {
auto full = QString::fromUtf8(info->name)
+ ' '
+ (info->alternativeName
? QString::fromUtf8(info->alternativeName)
: QString());
const auto namesList = std::move(full).toLower().split(
QRegularExpression("[\\s\\-]"),
base::QStringSkipEmptyParts);
auto &names = _namesList.emplace_back();
names.reserve(namesList.size());
for (const auto &name : namesList) {
const auto part = name.trimmed();
if (part.isEmpty()) {
continue;
}
const auto ch = part[0];
auto &byLetter = _byLetter[ch];
if (byLetter.empty() || byLetter.back() != index) {
byLetter.push_back(index);
}
names.push_back(part);
}
++index;
}
_filter = qsl("a");
updateFilter();
}
void CountrySelectBox::Inner::paintEvent(QPaintEvent *e) {
Painter p(this);
QRect r(e->rect());
p.setClipRect(r);
const auto &list = current();
if (list.empty()) {
p.fillRect(r, st::boxBg);
p.setFont(st::noContactsFont);
p.setPen(st::noContactsColor);
p.drawText(QRect(0, 0, width(), st::noContactsHeight), tr::lng_country_none(tr::now), style::al_center);
return;
}
const auto l = list.size();
if (r.intersects(QRect(0, 0, width(), st::countriesSkip))) {
p.fillRect(r.intersected(QRect(0, 0, width(), st::countriesSkip)), st::countryRowBg);
}
int32 from = floorclamp(r.y() - st::countriesSkip, _rowHeight, 0, l);
int32 to = ceilclamp(r.y() + r.height() - st::countriesSkip, _rowHeight, 0, l);
for (int32 i = from; i < to; ++i) {
auto selected = (i == (_pressed >= 0 ? _pressed : _selected));
auto y = st::countriesSkip + i * _rowHeight;
p.fillRect(0, y, width(), _rowHeight, selected ? st::countryRowBgOver : st::countryRowBg);
if (_ripples.size() > i && _ripples[i]) {
_ripples[i]->paint(p, 0, y, width());
if (_ripples[i]->empty()) {
_ripples[i].reset();
}
}
auto code = QString("+") + list[i]->code;
auto codeWidth = st::countryRowCodeFont->width(code);
auto name = QString::fromUtf8(list[i]->name);
auto nameWidth = st::countryRowNameFont->width(name);
auto availWidth = width() - st::countryRowPadding.left() - st::countryRowPadding.right() - codeWidth - st::boxScroll.width;
if (nameWidth > availWidth) {
name = st::countryRowNameFont->elided(name, availWidth);
nameWidth = st::countryRowNameFont->width(name);
}
p.setFont(st::countryRowNameFont);
p.setPen(st::countryRowNameFg);
p.drawTextLeft(st::countryRowPadding.left(), y + st::countryRowPadding.top(), width(), name);
if (_type == Type::Phones) {
p.setFont(st::countryRowCodeFont);
p.setPen(selected ? st::countryRowCodeFgOver : st::countryRowCodeFg);
p.drawTextLeft(st::countryRowPadding.left() + nameWidth + st::countryRowPadding.right(), y + st::countryRowPadding.top(), width(), code);
}
}
}
void CountrySelectBox::Inner::enterEventHook(QEvent *e) {
setMouseTracking(true);
}
void CountrySelectBox::Inner::leaveEventHook(QEvent *e) {
_mouseSelection = false;
setMouseTracking(false);
if (_selected >= 0) {
updateSelectedRow();
_selected = -1;
}
}
void CountrySelectBox::Inner::mouseMoveEvent(QMouseEvent *e) {
_mouseSelection = true;
updateSelected(e->pos());
}
void CountrySelectBox::Inner::mousePressEvent(QMouseEvent *e) {
_mouseSelection = true;
updateSelected(e->pos());
setPressed(_selected);
const auto &list = current();
if (_pressed >= 0 && _pressed < list.size()) {
if (_ripples.size() <= _pressed) {
_ripples.reserve(_pressed + 1);
while (_ripples.size() <= _pressed) {
_ripples.push_back(nullptr);
}
}
if (!_ripples[_pressed]) {
auto mask = Ui::RippleAnimation::rectMask(QSize(width(), _rowHeight));
_ripples[_pressed] = std::make_unique<Ui::RippleAnimation>(st::countryRipple, std::move(mask), [this, index = _pressed] {
updateRow(index);
});
_ripples[_pressed]->add(e->pos() - QPoint(0, st::countriesSkip + _pressed * _rowHeight));
}
}
}
void CountrySelectBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
auto pressed = _pressed;
setPressed(-1);
updateSelectedRow();
if (e->button() == Qt::LeftButton) {
if ((pressed >= 0) && pressed == _selected) {
chooseCountry();
}
}
}
void CountrySelectBox::Inner::updateFilter(QString filter) {
const auto words = TextUtilities::PrepareSearchWords(filter);
filter = words.isEmpty() ? QString() : words.join(' ');
if (_filter == filter) {
return;
}
_filter = filter;
const auto findWord = [&](
const std::vector<QString> &names,
const QString &word) {
for (const auto &name : names) {
if (name.startsWith(word)) {
return true;
}
}
return false;
};
const auto hasAllWords = [&](const std::vector<QString> &names) {
for (const auto &word : words) {
if (!findWord(names, word)) {
return false;
}
}
return true;
};
if (!_filter.isEmpty()) {
_filtered.clear();
for (const auto index : _byLetter[_filter[0].toLower()]) {
const auto &names = _namesList[index];
if (hasAllWords(_namesList[index])) {
_filtered.push_back(_list[index]);
}
}
}
refresh();
_selected = current().empty() ? -1 : 0;
update();
}
void CountrySelectBox::Inner::selectSkip(int32 dir) {
_mouseSelection = false;
const auto &list = current();
int cur = (_selected >= 0) ? _selected : -1;
cur += dir;
if (cur <= 0) {
_selected = list.empty() ? -1 : 0;
} else if (cur >= list.size()) {
_selected = -1;
} else {
_selected = cur;
}
if (_selected >= 0) {
mustScrollTo(st::countriesSkip + _selected * _rowHeight, st::countriesSkip + (_selected + 1) * _rowHeight);
}
update();
}
void CountrySelectBox::Inner::selectSkipPage(int32 h, int32 dir) {
int32 points = h / _rowHeight;
if (!points) return;
selectSkip(points * dir);
}
void CountrySelectBox::Inner::chooseCountry() {
const auto &list = current();
countryChosen((_selected >= 0 && _selected < list.size())
? QString(list[_selected]->iso2)
: QString());
}
void CountrySelectBox::Inner::refresh() {
const auto &list = current();
resize(width(), list.empty() ? st::noContactsHeight : (list.size() * _rowHeight + st::countriesSkip));
}
void CountrySelectBox::Inner::updateSelected(QPoint localPos) {
if (!_mouseSelection) return;
auto in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(QCursor::pos()));
const auto &list = current();
auto selected = (in && localPos.y() >= st::countriesSkip && localPos.y() < st::countriesSkip + list.size() * _rowHeight) ? ((localPos.y() - st::countriesSkip) / _rowHeight) : -1;
if (_selected != selected) {
updateSelectedRow();
_selected = selected;
updateSelectedRow();
}
}
auto CountrySelectBox::Inner::current() const
-> const std::vector<not_null<const Data::CountryInfo*>> & {
return _filter.isEmpty() ? _list : _filtered;
}
void CountrySelectBox::Inner::updateSelectedRow() {
updateRow(_selected);
}
void CountrySelectBox::Inner::updateRow(int index) {
if (index >= 0) {
update(0, st::countriesSkip + index * _rowHeight, width(), _rowHeight);
}
}
void CountrySelectBox::Inner::setPressed(int pressed) {
if (_pressed >= 0 && _pressed < _ripples.size() && _ripples[_pressed]) {
_ripples[_pressed]->lastStop();
}
_pressed = pressed;
}
CountrySelectBox::Inner::~Inner() = default;

View File

@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "boxes/abstract_box.h"
#include "ui/rp_widget.h"
#include "styles/style_widgets.h"
namespace Data {
@ -19,19 +19,19 @@ class MultiSelect;
class RippleAnimation;
} // namespace Ui
class CountryInput : public TWidget {
class CountryInput : public Ui::RpWidget {
Q_OBJECT
public:
CountryInput(QWidget *parent, const style::InputField &st);
QString iso() const {
[[nodiscard]] QString iso() const {
return _chosenIso;
}
bool chooseCountry(const QString &country);
public Q_SLOTS:
void onChooseCode(const QString &code);
bool onChooseCountry(const QString &country);
Q_SIGNALS:
void codeChanged(const QString &code);
@ -53,94 +53,3 @@ private:
QPainterPath _placeholderPath;
};
class CountrySelectBox : public Ui::BoxContent {
Q_OBJECT
public:
enum class Type {
Phones,
Countries,
};
CountrySelectBox(QWidget*);
CountrySelectBox(QWidget*, const QString &iso, Type type);
Q_SIGNALS:
void countryChosen(const QString &iso);
protected:
void prepare() override;
void setInnerFocus() override;
void keyPressEvent(QKeyEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
void submit();
void applyFilterUpdate(const QString &query);
Type _type = Type::Phones;
object_ptr<Ui::MultiSelect> _select;
class Inner;
QPointer<Inner> _inner;
};
// This class is hold in header because it requires Qt preprocessing.
class CountrySelectBox::Inner : public TWidget {
Q_OBJECT
public:
Inner(QWidget *parent, Type type);
void updateFilter(QString filter = QString());
void selectSkip(int32 dir);
void selectSkipPage(int32 h, int32 dir);
void chooseCountry();
void refresh();
~Inner();
Q_SIGNALS:
void countryChosen(const QString &iso);
void mustScrollTo(int ymin, int ymax);
protected:
void paintEvent(QPaintEvent *e) override;
void enterEventHook(QEvent *e) override;
void leaveEventHook(QEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
private:
void updateSelected() {
updateSelected(mapFromGlobal(QCursor::pos()));
}
void updateSelected(QPoint localPos);
void updateSelectedRow();
void updateRow(int index);
void setPressed(int pressed);
const std::vector<not_null<const Data::CountryInfo*>> &current() const;
Type _type = Type::Phones;
int _rowHeight = 0;
int _selected = -1;
int _pressed = -1;
QString _filter;
bool _mouseSelection = false;
std::vector<std::unique_ptr<Ui::RippleAnimation>> _ripples;
std::vector<not_null<const Data::CountryInfo*>> _list;
std::vector<not_null<const Data::CountryInfo*>> _filtered;
base::flat_map<QChar, std::vector<int>> _byLetter;
std::vector<std::vector<QString>> _namesList;
};

View File

@ -125,7 +125,7 @@ QString FormatPlayedText(qint64 played, qint64 duration) {
return tr::lng_duration_played(tr::now, lt_played, FormatDurationText(played), lt_duration, FormatDurationText(duration));
}
QString FillAmountAndCurrency(uint64 amount, const QString &currency) {
QString FillAmountAndCurrency(int64 amount, const QString &currency) {
static const auto ShortCurrencyNames = QMap<QString, QString>{
{ u"USD"_q, QString::fromUtf8("\x24") },
{ u"GBP"_q, QString::fromUtf8("\xC2\xA3") },

View File

@ -24,7 +24,7 @@ inline constexpr auto FileStatusSizeFailed = 0x7FFFFFF2;
[[nodiscard]] QString FormatPlayedText(qint64 played, qint64 duration);
[[nodiscard]] QString FillAmountAndCurrency(
uint64 amount,
int64 amount,
const QString &currency);
[[nodiscard]] QString ComposeNameString(

View File

@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h"
#include "base/object_ptr.h"
#include <set>
namespace Ui {
class InputField;

View File

@ -52,6 +52,9 @@ PRIVATE
core/mime_type.cpp
core/mime_type.h
data/data_countries.cpp
data/data_countries.h
media/clip/media_clip_check_streaming.cpp
media/clip/media_clip_check_streaming.h
media/clip/media_clip_ffmpeg.cpp
@ -61,6 +64,13 @@ PRIVATE
media/clip/media_clip_reader.cpp
media/clip/media_clip_reader.h
passport/ui/passport_details_row.cpp
passport/ui/passport_details_row.h
passport/ui/passport_form_row.cpp
passport/ui/passport_form_row.h
payments/ui/payments_edit_information.cpp
payments/ui/payments_edit_information.h
payments/ui/payments_form_summary.cpp
payments/ui/payments_form_summary.h
payments/ui/payments_panel.cpp
@ -80,10 +90,14 @@ PRIVATE
ui/boxes/calendar_box.h
ui/boxes/choose_date_time.cpp
ui/boxes/choose_date_time.h
ui/boxes/country_select_box.cpp
ui/boxes/country_select_box.h
ui/boxes/edit_invite_link.cpp
ui/boxes/edit_invite_link.h
ui/boxes/report_box.cpp
ui/boxes/report_box.h
ui/boxes/single_choice_box.cpp
ui/boxes/single_choice_box.h
ui/chat/attach/attach_album_thumbnail.cpp
ui/chat/attach/attach_album_thumbnail.h
ui/chat/attach/attach_album_preview.cpp