diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 6cf0a3403..30103f72e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1502,6 +1502,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_terms_decline" = "Decline"; "lng_terms_signup_sorry" = "We're very sorry, but this means you can't sign up for Telegram.\n\nUnlike others, we don't use your data for ad targeting or other commercial purposes. Telegram only stores the information it needs to function as a feature-rich cloud service. You can adjust how we use your data in Privacy & Security settings.\n\nBut if you're generally not OK with Telegram's modest needs, it won't be possible for us to provide this service."; +"lng_passport_title" = "Telegram passport"; +"lng_passport_request" = "{domain} requests access to your personal data\nto sign you up for their services"; +"lng_passport_password_request" = "Please provide the password for\naccessing your personal data."; +"lng_passport_password_placeholder" = "Enter password"; +"lng_passport_password_wrong" = "Wrong password"; +"lng_passport_header" = "Requested information"; +"lng_passport_identity_title" = "Identity document"; +"lng_passport_identity_description" = "Upload scan of your passport or other ID"; +"lng_passport_identity_passport" = "Passport"; +"lng_passport_identity_card" = "Identity card"; +"lng_passport_identity_license" = "Driver's license"; +"lng_passport_address_title" = "Residential address"; +"lng_passport_address_description" = "Upload proof of your address"; +"lng_passport_phone_title" = "Phone number"; +"lng_passport_email_title" = "E-mail"; +"lng_passport_email_description" = "Specify your e-mail address"; +"lng_passport_accept_allow" = "You accept {policy} and allow their {bot} to send messages to you."; +"lng_passport_accept" = "You accept {policy}."; +"lng_passport_policy" = "{domain} privacy policy"; +"lng_passport_authorize" = "Authorize"; +"lng_passport_form_error" = "Could not get authorization form."; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/SourceFiles/base/openssl_help.h b/Telegram/SourceFiles/base/openssl_help.h index 087d6555a..b34099e0d 100644 --- a/Telegram/SourceFiles/base/openssl_help.h +++ b/Telegram/SourceFiles/base/openssl_help.h @@ -223,6 +223,10 @@ inline int FillRandom(base::byte_span bytes) { return RAND_bytes(reinterpret_cast(bytes.data()), bytes.size()); } +inline void AddRandomSeed(base::const_byte_span bytes) { + RAND_seed(bytes.data(), bytes.size()); +} + } // namespace openssl namespace bytes { diff --git a/Telegram/SourceFiles/boxes/abstract_box.cpp b/Telegram/SourceFiles/boxes/abstract_box.cpp index cd03a5ec9..0aafd3a20 100644 --- a/Telegram/SourceFiles/boxes/abstract_box.cpp +++ b/Telegram/SourceFiles/boxes/abstract_box.cpp @@ -76,6 +76,8 @@ void BoxContent::finishPrepare() { void BoxContent::finishScrollCreate() { Expects(_scroll != nullptr); + + _scroll->show(); updateScrollAreaGeometry(); connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); connect(_scroll, SIGNAL(innerResized()), this, SLOT(onInnerResize())); diff --git a/Telegram/SourceFiles/boxes/passcode_box.cpp b/Telegram/SourceFiles/boxes/passcode_box.cpp index c3334083c..ac6c187b5 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.cpp +++ b/Telegram/SourceFiles/boxes/passcode_box.cpp @@ -323,7 +323,9 @@ void PasscodeBox::save(bool force) { if (!_oldPasscode->isHidden()) { hashSha256(oldPasswordData.constData(), oldPasswordData.size(), oldPasswordHash.data()); } - auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_salt | MTPDaccount_passwordInputSettings::Flag::f_new_password_hash | MTPDaccount_passwordInputSettings::Flag::f_hint; + auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_salt + | MTPDaccount_passwordInputSettings::Flag::f_new_password_hash + | MTPDaccount_passwordInputSettings::Flag::f_hint; if (_oldPasscode->isHidden() || _newPasscode->isHidden()) { flags |= MTPDaccount_passwordInputSettings::Flag::f_email; } diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp index 079a5fc74..6c02b92fd 100644 --- a/Telegram/SourceFiles/messenger.cpp +++ b/Telegram/SourceFiles/messenger.cpp @@ -923,6 +923,7 @@ bool Messenger::openLocalUrl(const QString &url) { scope, callback, publicKey)); + return true; } } } diff --git a/Telegram/SourceFiles/passport/passport.style b/Telegram/SourceFiles/passport/passport.style new file mode 100644 index 000000000..0c57ec881 --- /dev/null +++ b/Telegram/SourceFiles/passport/passport.style @@ -0,0 +1,39 @@ +/* +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 +*/ +using "basic.style"; + +using "ui/widgets/widgets.style"; +using "boxes/boxes.style"; +using "intro/intro.style"; + +passportPasswordPadding: margins(20px, 30px, 20px, 40px); +passportPasswordForgotSkip: 5px; +passportPasswordAboutSkip: 15px; +passportPasswordLabel: FlatLabel(boxLabel) { + minWidth: 275px; +} +passportPasswordHintLabel: passportPasswordLabel; +passportErrorLabel: FlatLabel(passportPasswordLabel) { + textFg: boxTextFgError; +} + +passportRowPadding: margins(20px, 10px, 20px, 10px); +passportRowSkip: 5px; +passportRowRipple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; +} +passportRowCheckbox: IconButton(defaultIconButton) { + width: 38px; + height: 38px; + iconPosition: point(10px, 13px); + icon: icon {{ "send_control_save", menuIconFg }}; + iconOver: icon {{ "send_control_save", menuIconFgOver }}; + rippleAreaPosition: point(0px, 0px); + rippleAreaSize: 38px; + ripple: passportRowRipple; +} diff --git a/Telegram/SourceFiles/passport/passport_form_box.cpp b/Telegram/SourceFiles/passport/passport_form_box.cpp new file mode 100644 index 000000000..58cb2e1b2 --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_form_box.cpp @@ -0,0 +1,261 @@ +/* +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/passport_form_box.h" + +#include "passport/passport_form_controller.h" +#include "passport/passport_form_row.h" +#include "lang/lang_keys.h" +#include "ui/widgets/input_fields.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/buttons.h" +#include "ui/text_options.h" +#include "styles/style_boxes.h" +#include "styles/style_widgets.h" +#include "styles/style_passport.h" + +namespace Passport { + +class FormBox::CheckWidget : public Ui::RpWidget { +public: + CheckWidget(QWidget *parent, not_null controller); + + void setInnerFocus(); + void submit(); + +protected: + int resizeGetHeight(int newWidth) override; + +private: + void showError(const QString &error); + void hideError(); + + not_null _controller; + + object_ptr _password; + object_ptr _hint = { nullptr }; + object_ptr _error = { nullptr }; + object_ptr _forgot; + object_ptr _about; + +}; + +class FormBox::Inner : public Ui::RpWidget { +public: + Inner(QWidget *parent, not_null controller); + + void refresh(); + +protected: + int resizeGetHeight(int newWidth) override; + + void paintEvent(QPaintEvent *e) override; + +private: + not_null _controller; + std::vector> _rows; + +}; + +FormBox::CheckWidget::CheckWidget( + QWidget *parent, + not_null controller) +: RpWidget(parent) +, _controller(controller) +, _password( + this, + st::defaultInputField, + langFactory(lng_passport_password_placeholder)) +, _forgot(this, lang(lng_signin_recover), st::boxLinkButton) +, _about( + this, + lang(lng_passport_password_request), + Ui::FlatLabel::InitType::Simple, + st::passportPasswordLabel) { + connect(_password, &Ui::PasswordInput::submitted, this, [=] { + submit(); + }); + connect(_password, &Ui::PasswordInput::changed, this, [=] { + hideError(); + }); + if (const auto hint = _controller->passwordHint(); !hint.isEmpty()) { + _hint.create( + this, + hint, + Ui::FlatLabel::InitType::Simple, + st::passportPasswordHintLabel); + } + _controller->passwordError( + ) | rpl::start_with_next([=](const QString &error) { + showError(error); + }, lifetime()); +} + +void FormBox::CheckWidget::showError(const QString &error) { + _password->showError(); + _error.create( + this, + error, + Ui::FlatLabel::InitType::Simple, + st::passportErrorLabel); + _error->show(); + if (_hint) { + _hint->hide(); + } + resizeToWidth(width()); +} + +void FormBox::CheckWidget::hideError() { + _error.destroy(); + if (_hint) { + _hint->show(); + } +} + +void FormBox::CheckWidget::submit() { + _controller->submitPassword(_password->getLastText()); +} + +void FormBox::CheckWidget::setInnerFocus() { + _password->setFocusFast(); +} + +int FormBox::CheckWidget::resizeGetHeight(int newWidth) { + const auto padding = st::passportPasswordPadding; + const auto availableWidth = newWidth + - st::boxPadding.left() + - st::boxPadding.right(); + auto top = padding.top(); + + _about->resizeToWidth(availableWidth); + _about->moveToLeft(padding.left(), top); + top += _about->height(); + + _password->resize(availableWidth, _password->height()); + _password->moveToLeft(padding.left(), top); + top += _password->height(); + + if (_error) { + _error->resizeToWidth(availableWidth); + _error->moveToLeft(padding.left(), top); + } + if (_hint) { + _hint->resizeToWidth(availableWidth); + _hint->moveToLeft(padding.left(), top); + top += _hint->height(); + } else { + top += st::passportPasswordHintLabel.style.font->height; + } + _forgot->moveToLeft(padding.left(), top); + top += _forgot->height(); + return top + padding.bottom(); +} + +FormBox::Inner::Inner( + QWidget *parent, + not_null controller) +: RpWidget(parent) +, _controller(controller) { + refresh(); +} + +void FormBox::Inner::refresh() { + auto index = 0; + _controller->fillRows([&]( + QString title, + QString description, + bool ready) { + if (_rows.size() <= index) { + _rows.push_back(object_ptr(this, title, description)); + } + _rows[index++]->setReady(ready); + }); + while (_rows.size() > index) { + _rows.pop_back(); + } + resizeToWidth(width()); +} + +int FormBox::Inner::resizeGetHeight(int newWidth) { + auto result = 0; + for (auto &row : _rows) { + row->resizeToWidth(newWidth); + row->moveToLeft(0, result); + result += row->height(); + } + return result; +} + +void FormBox::Inner::paintEvent(QPaintEvent *e) { + Painter p(this); + +} + +FormBox::FormBox(QWidget*, not_null controller) +: _controller(controller) { +} + +void FormBox::prepare() { + setTitle(langFactory(lng_passport_title)); + + addButton(langFactory(lng_create_group_next), [=] { + submitPassword(); + }); + addButton(langFactory(lng_cancel), [=] { + closeBox(); + }); + + _passwordCheck = setInnerWidget( + object_ptr(this, _controller)); + _passwordCheck->resizeToWidth(st::boxWideWidth); + + _innerCached.create(this, _controller); + _innerCached->resizeToWidth(st::boxWideWidth); + const auto desiredHeight = std::min( + std::max(_innerCached->height(), _passwordCheck->height()), + st::boxMaxListHeight); + setDimensions(st::boxWideWidth, desiredHeight); + _innerCached->hide(); + + _controller->secretReadyEvents( + ) | rpl::start_with_next([=] { + showForm(); + }, lifetime()); +} + +void FormBox::setInnerFocus() { + if (_passwordCheck) { + _passwordCheck->setInnerFocus(); + } else { + _inner->setFocus(); + } +} + +void FormBox::submitPassword() { + Expects(_passwordCheck != nullptr); + + _passwordCheck->submit(); +} + +void FormBox::showForm() { + clearButtons(); + addButton(langFactory(lng_passport_authorize), [=] { + submitForm(); + }); + addButton(langFactory(lng_cancel), [=] { + closeBox(); + }); + + _inner = setInnerWidget(std::move(_innerCached)); + _inner->show(); +} + +void FormBox::submitForm() { + +} + +} // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_form_box.h b/Telegram/SourceFiles/passport/passport_form_box.h new file mode 100644 index 000000000..67ceb36d1 --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_form_box.h @@ -0,0 +1,39 @@ +/* +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" + +namespace Passport { + +class FormController; + +class FormBox : public BoxContent { +public: + FormBox(QWidget*, not_null controller); + +protected: + void prepare() override; + void setInnerFocus() override; + +private: + class CheckWidget; + class Inner; + + void submitPassword(); + void showForm(); + void submitForm(); + + not_null _controller; + object_ptr _innerCached = { nullptr }; + QPointer _passwordCheck; + QPointer _inner; + +}; + +} // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index 528508b4a..a530cea5f 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -7,8 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "passport/passport_form_controller.h" +#include "passport/passport_form_box.h" #include "boxes/confirm_box.h" #include "lang/lang_keys.h" +#include "base/openssl_help.h" #include "mainwindow.h" namespace Passport { @@ -42,6 +44,95 @@ void FormController::show() { requestPassword(); } +void FormController::submitPassword(const QString &password) { + Expects(!_password.salt.isEmpty()); + + if (_passwordCheckRequestId) { + return; + } + const auto data = _password.salt + password.toUtf8() + _password.salt; + const auto hash = hashSha256(data.constData(), data.size()); + _passwordCheckRequestId = request(MTPaccount_GetPasswordSettings( + MTP_bytes(gsl::as_bytes(gsl::make_span(hash))) + )).handleFloodErrors( + ).done([=](const MTPaccount_PasswordSettings &result) { + Expects(result.type() == mtpc_account_passwordSettings); + + _passwordCheckRequestId = 0; + const auto &data = result.c_account_passwordSettings(); + _passwordEmail = qs(data.vemail); + _secret = byteVectorFromMTP(data.vsecure_secret); + _secretReady.fire({}); + }).fail([=](const RPCError &error) { + _passwordCheckRequestId = 0; + if (MTP::isFloodError(error)) { + _passwordError.fire(lang(lng_flood_error)); + } else if (error.type() == qstr("PASSWORD_HASH_INVALID")) { + _passwordError.fire(lang(lng_passport_password_wrong)); + } else { + _passwordError.fire_copy(error.type()); + } + }).send(); +} + +rpl::producer FormController::passwordError() const { + return _passwordError.events(); +} + +QString FormController::passwordHint() const { + return _password.hint; +} + +rpl::producer<> FormController::secretReadyEvents() const { + return _secretReady.events(); +} + +QString FormController::defaultEmail() const { + return _passwordEmail; +} + +QString FormController::defaultPhoneNumber() const { + if (const auto self = App::self()) { + return self->phone(); + } + return QString(); +} + +void FormController::fillRows( + base::lambda callback) { + for (const auto &field : _form.fields) { + switch (field.type) { + case Field::Type::Identity: + callback( + lang(lng_passport_identity_title), + lang(lng_passport_identity_description), + false); + break; + case Field::Type::Address: + callback( + lang(lng_passport_address_title), + lang(lng_passport_address_description), + false); + break; + case Field::Type::Phone: + callback( + lang(lng_passport_phone_title), + App::self()->phone(), + true); + break; + case Field::Type::Email: + callback( + lang(lng_passport_email_title), + lang(lng_passport_email_description), + false); + break; + } + } +} + void FormController::requestForm() { auto scope = QVector(); scope.reserve(_request.scope.size()); @@ -51,7 +142,7 @@ void FormController::requestForm() { auto normalizedKey = _request.publicKey; normalizedKey.replace("\r\n", "\n"); const auto bytes = normalizedKey.toUtf8(); - request(MTPaccount_GetAuthorizationForm( + _formRequestId = request(MTPaccount_GetAuthorizationForm( MTP_flags(MTPaccount_GetAuthorizationForm::Flag::f_origin | MTPaccount_GetAuthorizationForm::Flag::f_public_key), MTP_int(_request.botId), @@ -61,6 +152,7 @@ void FormController::requestForm() { MTPstring(), // bundle_id MTP_bytes(bytes) )).done([=](const MTPaccount_AuthorizationForm &result) { + _formRequestId = 0; formDone(result); }).fail([=](const RPCError &error) { formFail(error); @@ -117,7 +209,9 @@ auto FormController::convertValue( void FormController::formDone(const MTPaccount_AuthorizationForm &result) { parseForm(result); - showForm(); + if (!_passwordRequestId) { + showForm(); + } } void FormController::parseForm(const MTPaccount_AuthorizationForm &result) { @@ -160,57 +254,57 @@ void FormController::showForm() { Ui::show(Box("Could not get authorization bot.")); return; } - auto text = QString("Auth request:\n"); - for (const auto &field : _form.fields) { - switch (field.type) { - case Field::Type::Identity: text += "- identity\n"; break; - case Field::Type::Address: text += "- address\n"; break; - case Field::Type::Email: text += "- email\n"; break; - case Field::Type::Phone: text += "- phone\n"; break; - } - } - if (_form.requestWrite) { - text += "and bot @" + _bot->username + " requests write permission."; - } else { - text += "and bot @" + _bot->username + " does not request write permission."; - } - Ui::show(Box(text)); -// Ui::show(Box(this)); + Ui::show(Box(this)); } void FormController::formFail(const RPCError &error) { - // #TODO langs - Ui::show(Box("Could not get authorization form.")); + Ui::show(Box(lang(lng_passport_form_error))); } void FormController::requestPassword() { - request(MTPaccount_GetPassword( + _passwordRequestId = request(MTPaccount_GetPassword( )).done([=](const MTPaccount_Password &result) { + _passwordRequestId = 0; passwordDone(result); + }).fail([=](const RPCError &error) { + formFail(error); }).send(); } void FormController::passwordDone(const MTPaccount_Password &result) { switch (result.type()) { case mtpc_account_noPassword: - createPassword(result.c_account_noPassword()); + parsePassword(result.c_account_noPassword()); break; case mtpc_account_password: - checkPassword(result.c_account_password()); + parsePassword(result.c_account_password()); break; } + if (!_formRequestId) { + showForm(); + } } void FormController::passwordFail(const RPCError &error) { Ui::show(Box("Could not get authorization form.")); } -void FormController::createPassword(const MTPDaccount_noPassword &result) { - Ui::show(Box("You need 2fa password!")); // #TODO +void FormController::parsePassword(const MTPDaccount_noPassword &result) { + _password.unconfirmedPattern = qs(result.vemail_unconfirmed_pattern); + _password.newSalt = result.vnew_salt.v; + openssl::AddRandomSeed( + gsl::as_bytes(gsl::make_span(result.vsecret_random.v))); } -void FormController::checkPassword(const MTPDaccount_password &result) { +void FormController::parsePassword(const MTPDaccount_password &result) { + _password.hint = qs(result.vhint); + _password.hasRecovery = mtpIsTrue(result.vhas_recovery); + _password.salt = result.vcurrent_salt.v; + _password.unconfirmedPattern = qs(result.vemail_unconfirmed_pattern); + _password.newSalt = result.vnew_salt.v; + openssl::AddRandomSeed( + gsl::as_bytes(gsl::make_span(result.vsecret_random.v))); } } // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h index 257c86699..aab82214c 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_controller.h @@ -31,12 +31,32 @@ struct FormRequest { class FormController : private MTP::Sender { public: + struct PasswordCheckResult { + QByteArray secret; + + }; + FormController( not_null controller, const FormRequest &request); void show(); + void submitPassword(const QString &password); + rpl::producer passwordError() const; + QString passwordHint() const; + + rpl::producer<> secretReadyEvents() const; + + QString defaultEmail() const; + QString defaultPhoneNumber() const; + + void fillRows( + base::lambda callback); + private: struct File { uint64 id = 0; @@ -76,6 +96,13 @@ private: bool requestWrite = false; std::vector fields; }; + struct PasswordSettings { + QByteArray salt; + QByteArray newSalt; + QString hint; + QString unconfirmedPattern; + bool hasRecovery = false; + }; Value convertValue(const MTPSecureValue &value) const; void requestForm(); @@ -88,16 +115,26 @@ private: void passwordDone(const MTPaccount_Password &result); void passwordFail(const RPCError &error); - void createPassword(const MTPDaccount_noPassword &settings); - void checkPassword(const MTPDaccount_password &settings); + void parsePassword(const MTPDaccount_noPassword &settings); + void parsePassword(const MTPDaccount_password &settings); not_null _controller; FormRequest _request; UserData *_bot = nullptr; QString _origin; + mtpRequestId _formRequestId = 0; + mtpRequestId _passwordRequestId = 0; + mtpRequestId _passwordCheckRequestId = 0; + + PasswordSettings _password; Form _form; + base::byte_vector _secret; + QString _passwordEmail; + rpl::event_stream<> _secretReady; + rpl::event_stream _passwordError; + }; } // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_form_row.cpp b/Telegram/SourceFiles/passport/passport_form_row.cpp new file mode 100644 index 000000000..955df35ed --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_form_row.cpp @@ -0,0 +1,96 @@ +/* +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/passport_form_row.h" + +#include "ui/wrap/fade_wrap.h" +#include "ui/text_options.h" +#include "styles/style_boxes.h" +#include "styles/style_passport.h" + +namespace Passport { + +FormRow::FormRow( + QWidget *parent, + const QString &title, + const QString &description) +: RippleButton(parent, st::passportRowRipple) +, _title( + st::semiboldTextStyle, + title, + Ui::NameTextOptions(), + st::boxWideWidth / 2) +, _description( + st::defaultTextStyle, + description, + Ui::NameTextOptions(), + st::boxWideWidth / 2) { +} + +void FormRow::setReady(bool ready) { + if (ready) { + _checkbox.create(this, object_ptr( + this, + st::passportRowCheckbox)); + _checkbox->show(anim::type::instant); + _checkbox->entity()->addClickHandler([=] { + _checkbox->hide(anim::type::normal); + }); + } else { + _checkbox.destroy(); + } + resizeToWidth(width()); +} + +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(); + if (_checkbox) { + const auto right = st::passportRowPadding.right(); + _checkbox->moveToRight( + right, + (result - _checkbox->height()) / 2, + newWidth); + } + return result; +} + +int FormRow::countAvailableWidth(int newWidth) const { + return newWidth + - st::passportRowPadding.left() + - st::passportRowPadding.right() + - (_checkbox ? _checkbox->width() : 0); +} + +int FormRow::countAvailableWidth() const { + return countAvailableWidth(width()); +} + +void FormRow::paintEvent(QPaintEvent *e) { + Painter p(this); + + const auto ms = getms(); + paintRipple(p, 0, 0, ms); + + const auto left = st::passportRowPadding.left(); + const auto availableWidth = countAvailableWidth(); + auto top = st::passportRowPadding.top(); + + _title.drawLeft(p, left, top, availableWidth, width()); + top += _titleHeight + st::passportRowSkip; + + _description.drawLeft(p, left, top, availableWidth, width()); + top += _descriptionHeight + st::passportRowPadding.bottom(); +} + +} // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_form_row.h b/Telegram/SourceFiles/passport/passport_form_row.h new file mode 100644 index 000000000..070ce952f --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_form_row.h @@ -0,0 +1,45 @@ +/* +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/widgets/buttons.h" + +namespace Ui { +template +class FadeWrapScaled; +} // namespace Ui + +namespace Passport { + +class FormRow : public Ui::RippleButton { +public: + FormRow( + QWidget *parent, + const QString &title, + const QString &description); + + void setReady(bool ready); + +protected: + int resizeGetHeight(int newWidth) override; + + void paintEvent(QPaintEvent *e) override; + +private: + int countAvailableWidth() const; + int countAvailableWidth(int newWidth) const; + + Text _title; + Text _description; + int _titleHeight = 0; + int _descriptionHeight = 0; + object_ptr> _checkbox = { nullptr }; + +}; + +} // namespace Passport diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 62a4197f8..2261f6de9 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -33,6 +33,7 @@ '<(src_loc)/media/view/mediaview.style', '<(src_loc)/media/player/media_player.style', '<(src_loc)/overview/overview.style', + '<(src_loc)/passport/passport.style', '<(src_loc)/profile/profile.style', '<(src_loc)/settings/settings.style', '<(src_loc)/chat_helpers/chat_helpers.style', diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 9a404647d..d37553a44 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -454,8 +454,12 @@ <(src_loc)/mtproto/type_utils.h <(src_loc)/overview/overview_layout.cpp <(src_loc)/overview/overview_layout.h +<(src_loc)/passport/passport_form_box.cpp +<(src_loc)/passport/passport_form_box.h <(src_loc)/passport/passport_form_controller.cpp <(src_loc)/passport/passport_form_controller.h +<(src_loc)/passport/passport_form_row.cpp +<(src_loc)/passport/passport_form_row.h <(src_loc)/platform/linux/linux_desktop_environment.cpp <(src_loc)/platform/linux/linux_desktop_environment.h <(src_loc)/platform/linux/linux_gdk_helper.cpp