From 5cfead762d0db3bbf5c52e26e93ef9fc08b29221 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 31 Mar 2018 05:45:40 +0400 Subject: [PATCH] New design of the passport in a separate window. --- Telegram/Resources/langs/lang.strings | 30 +- Telegram/Resources/scheme.tl | 20 +- Telegram/SourceFiles/boxes/abstract_box.cpp | 8 +- Telegram/SourceFiles/boxes/abstract_box.h | 2 +- Telegram/SourceFiles/calls/calls.style | 1 + Telegram/SourceFiles/calls/calls_panel.cpp | 4 +- Telegram/SourceFiles/passport/passport.style | 170 ++++++- .../passport/passport_edit_identity_box.cpp | 6 +- .../passport/passport_edit_identity_box.h | 6 +- .../passport/passport_form_box.cpp | 264 ----------- .../SourceFiles/passport/passport_form_box.h | 39 -- .../passport/passport_form_controller.cpp | 122 +++--- .../passport/passport_form_controller.h | 19 +- .../passport/passport_form_row.cpp | 33 +- .../SourceFiles/passport/passport_form_row.h | 26 -- .../passport/passport_form_view_controller.h | 4 +- .../SourceFiles/passport/passport_panel.cpp | 392 +++++++++++++++++ .../SourceFiles/passport/passport_panel.h | 95 ++++ ...rate.cpp => passport_panel_controller.cpp} | 168 ++++--- ...separate.h => passport_panel_controller.h} | 29 +- .../passport/passport_panel_details_row.cpp | 46 ++ .../passport/passport_panel_details_row.h | 38 ++ .../passport/passport_panel_edit_identity.cpp | 413 ++++++++++++++++++ .../passport/passport_panel_edit_identity.h | 88 ++++ .../passport/passport_panel_form.cpp | 266 +++++++++++ .../passport/passport_panel_form.h | 56 +++ .../passport/passport_panel_password.cpp | 163 +++++++ .../passport/passport_panel_password.h | 77 ++++ Telegram/SourceFiles/ui/special_buttons.cpp | 16 + Telegram/SourceFiles/ui/special_buttons.h | 5 + .../SourceFiles/ui/widgets/scroll_area.cpp | 4 +- Telegram/SourceFiles/ui/widgets/widgets.style | 12 + Telegram/SourceFiles/ui/wrap/wrap.h | 20 + .../SourceFiles/window/window_controller.cpp | 4 + .../SourceFiles/window/window_controller.h | 1 + Telegram/gyp/telegram_sources.txt | 20 +- 36 files changed, 2112 insertions(+), 555 deletions(-) delete mode 100644 Telegram/SourceFiles/passport/passport_form_box.cpp delete mode 100644 Telegram/SourceFiles/passport/passport_form_box.h create mode 100644 Telegram/SourceFiles/passport/passport_panel.cpp create mode 100644 Telegram/SourceFiles/passport/passport_panel.h rename Telegram/SourceFiles/passport/{passport_form_view_separate.cpp => passport_panel_controller.cpp} (50%) rename Telegram/SourceFiles/passport/{passport_form_view_separate.h => passport_panel_controller.h} (77%) create mode 100644 Telegram/SourceFiles/passport/passport_panel_details_row.cpp create mode 100644 Telegram/SourceFiles/passport/passport_panel_details_row.h create mode 100644 Telegram/SourceFiles/passport/passport_panel_edit_identity.cpp create mode 100644 Telegram/SourceFiles/passport/passport_panel_edit_identity.h create mode 100644 Telegram/SourceFiles/passport/passport_panel_form.cpp create mode 100644 Telegram/SourceFiles/passport/passport_panel_form.h create mode 100644 Telegram/SourceFiles/passport/passport_panel_password.cpp create mode 100644 Telegram/SourceFiles/passport/passport_panel_password.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 30103f72e..91bf1c4bf 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1503,26 +1503,38 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "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_request1" = "{bot} requests access to your personal data"; +"lng_passport_request2" = "to sign you up for their services"; +"lng_passport_password_placeholder" = "Your password"; +"lng_passport_next" = "Next"; "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_description" = "Upload a 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_address_description" = "Upload a 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_phone_description" = "Enter your phone number"; +"lng_passport_email_title" = "Email"; +"lng_passport_email_description" = "Enter your email 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_policy" = "{bot} privacy policy"; "lng_passport_authorize" = "Authorize"; "lng_passport_form_error" = "Could not get authorization form."; +"lng_passport_save_value" = "Done"; +"lng_passport_upload_header" = "Scans"; +"lng_passport_scan_index" = "Scan {index}"; +"lng_passport_upload_scans" = "Upload scans"; +"lng_passport_upload_more" = "Upload additional scans"; +"lng_passport_personal_details" = "Personal details"; +"lng_passport_choose_image" = "Choose scan image"; +"lng_passport_delete_scan_undo" = "Undo"; +"lng_passport_scan_uploaded" = "Uploaded on {date}"; +"lng_passport_first_name" = "First name"; +"lng_passport_last_name" = "Last name"; // Wnd specific diff --git a/Telegram/Resources/scheme.tl b/Telegram/Resources/scheme.tl index 43276bddc..8d555a61b 100644 --- a/Telegram/Resources/scheme.tl +++ b/Telegram/Resources/scheme.tl @@ -281,7 +281,7 @@ messageActionPhoneCall#80e11a7f flags:# call_id:long reason:flags.0?PhoneCallDis messageActionScreenshotTaken#4792929b = MessageAction; messageActionCustomAction#fae69f56 message:string = MessageAction; messageActionBotAllowed#abe9affe domain:string = MessageAction; -messageActionSecureValuesSentMe#9bc8ec4 values:Vector credentials:SecureCredentialsEncrypted payload:bytes = MessageAction; +messageActionSecureValuesSentMe#1b287353 values:Vector credentials:SecureCredentialsEncrypted = MessageAction; messageActionSecureValuesSent#d95c6154 types:Vector = MessageAction; dialog#e4def5db flags:# pinned:flags.2?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage = Dialog; @@ -982,15 +982,15 @@ secureValueTypeAddress#cbe31e26 = SecureValueType; secureValueTypePhone#b320aadb = SecureValueType; secureValueTypeEmail#8e3ca7ee = SecureValueType; -secureValueIdentity#4838ff84 flags:# data:SecureData files:Vector hash:bytes verified:flags.0?SecureValueVerified = SecureValue; -secureValueAddress#2b9f6bef flags:# data:SecureData files:Vector hash:bytes verified:flags.0?SecureValueVerified = SecureValue; -secureValuePhone#a1ca84fe flags:# phone:string hash:bytes verified:flags.0?SecureValueVerified = SecureValue; -secureValueEmail#c4db6579 flags:# email:string hash:bytes verified:flags.0?SecureValueVerified = SecureValue; +secureValueIdentity#15fe72a2 flags:# data:SecureData files:Vector verified:flags.0?SecureValueVerified = SecureValue; +secureValueAddress#88109b79 flags:# data:SecureData files:Vector verified:flags.0?SecureValueVerified = SecureValue; +secureValuePhone#e39470bf flags:# phone:string verified:flags.0?SecureValueVerified = SecureValue; +secureValueEmail#35d804cd flags:# email:string verified:flags.0?SecureValueVerified = SecureValue; -inputSecureValueIdentity#df93404 data:SecureData files:Vector hash:bytes = InputSecureValue; -inputSecureValueAddress#5589502 data:SecureData files:Vector hash:bytes = InputSecureValue; -inputSecureValuePhone#141e00b8 phone:string hash:bytes = InputSecureValue; -inputSecureValueEmail#2dc15b9a email:string hash:bytes = InputSecureValue; +inputSecureValueIdentity#94fa65b data:SecureData files:Vector = InputSecureValue; +inputSecureValueAddress#96689355 data:SecureData files:Vector = InputSecureValue; +inputSecureValuePhone#9d623d96 phone:string = InputSecureValue; +inputSecureValueEmail#9e885359 email:string = InputSecureValue; secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash; @@ -1061,7 +1061,7 @@ account.getSecureValue#d97e77cb user_id:InputUser types:Vector account.saveSecureValue#78969d0b value:InputSecureValue secure_secret_id:long = SecureValueSaved; account.deleteSecureValue#b880bc4b types:Vector = Bool; account.getAuthorizationForm#b86ba8e1 bot_id:int scope:string public_key:string = account.AuthorizationForm; -account.acceptAuthorization#8d5e02e6 bot_id:int scope:string public_key:string value_hashes:Vector credentials:SecureCredentialsEncrypted payload:bytes = Bool; +account.acceptAuthorization#e7027c94 bot_id:int scope:string public_key:string value_hashes:Vector credentials:SecureCredentialsEncrypted = Bool; account.sendVerifyPhoneCode#823380b4 flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool = auth.SentCode; account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool; account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode; diff --git a/Telegram/SourceFiles/boxes/abstract_box.cpp b/Telegram/SourceFiles/boxes/abstract_box.cpp index 0aafd3a20..071a7220d 100644 --- a/Telegram/SourceFiles/boxes/abstract_box.cpp +++ b/Telegram/SourceFiles/boxes/abstract_box.cpp @@ -438,11 +438,13 @@ void AbstractBox::keyPressEvent(QKeyEvent *e) { } } -BoxContentDivider::BoxContentDivider(QWidget *parent) : RpWidget(parent) { +BoxContentDivider::BoxContentDivider(QWidget *parent) +: BoxContentDivider(parent, st::rightsDividerHeight) { } -int BoxContentDivider::resizeGetHeight(int newWidth) { - return st::rightsDividerHeight; +BoxContentDivider::BoxContentDivider(QWidget *parent, int height) +: RpWidget(parent) { + resize(width(), height); } void BoxContentDivider::paintEvent(QPaintEvent *e) { diff --git a/Telegram/SourceFiles/boxes/abstract_box.h b/Telegram/SourceFiles/boxes/abstract_box.h index 7c2223286..4449fddf2 100644 --- a/Telegram/SourceFiles/boxes/abstract_box.h +++ b/Telegram/SourceFiles/boxes/abstract_box.h @@ -298,9 +298,9 @@ private: class BoxContentDivider : public Ui::RpWidget { public: BoxContentDivider(QWidget *parent); + BoxContentDivider(QWidget *parent, int height); protected: - int resizeGetHeight(int newWidth) override; void paintEvent(QPaintEvent *e) override; }; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 77d104202..89a38ef8a 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -20,6 +20,7 @@ CallSignalBars { callWidth: 300px; callHeight: 470px; +callRadius: 6px; callShadow: Shadow { left: icon {{ "call_shadow_left", windowShadowFg }}; topLeft: icon {{ "call_shadow_top_left", windowShadowFg }}; diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 77d21b413..4c1d42130 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -600,7 +600,7 @@ void Panel::createBottomImage() { p.setBrush(st::callBg); p.setPen(Qt::NoPen); PainterHighQualityEnabler hq(p); - p.drawRoundedRect(myrtlrect(_padding.left(), -st::historyMessageRadius, st::callWidth, bottomHeight - _padding.bottom() + st::historyMessageRadius), st::historyMessageRadius, st::historyMessageRadius); + p.drawRoundedRect(myrtlrect(_padding.left(), -st::callRadius, st::callWidth, bottomHeight - _padding.bottom() + st::callRadius), st::callRadius, st::callRadius); } _bottomCache = App::pixmapFromImageInPlace(std::move(image)); } @@ -620,7 +620,7 @@ void Panel::createDefaultCacheImage() { p.setBrush(st::callBg); p.setPen(Qt::NoPen); PainterHighQualityEnabler hq(p); - p.drawRoundedRect(myrtlrect(inner), st::historyMessageRadius, st::historyMessageRadius); + p.drawRoundedRect(myrtlrect(inner), st::callRadius, st::callRadius); } _cache = App::pixmapFromImageInPlace(std::move(cache)); } diff --git a/Telegram/SourceFiles/passport/passport.style b/Telegram/SourceFiles/passport/passport.style index 0c57ec881..a73b4e69c 100644 --- a/Telegram/SourceFiles/passport/passport.style +++ b/Telegram/SourceFiles/passport/passport.style @@ -9,31 +9,177 @@ using "basic.style"; using "ui/widgets/widgets.style"; using "boxes/boxes.style"; -using "intro/intro.style"; +using "info/info.style"; +using "chat_helpers/chat_helpers.style"; passportPasswordPadding: margins(20px, 30px, 20px, 40px); passportPasswordForgotSkip: 5px; passportPasswordAboutSkip: 15px; passportPasswordLabel: FlatLabel(boxLabel) { minWidth: 275px; + align: align(top); +} +passportPasswordLabelBold: FlatLabel(passportPasswordLabel) { + style: TextStyle(boxLabelStyle) { + font: font(boxFontSize semibold); + linkFont: font(boxFontSize semibold); + linkFontOver: font(boxFontSize semibold underline); + } } passportPasswordHintLabel: passportPasswordLabel; passportErrorLabel: FlatLabel(passportPasswordLabel) { textFg: boxTextFgError; } -passportRowPadding: margins(20px, 10px, 20px, 10px); -passportRowSkip: 5px; +passportPanelWidth: 392px; +passportPanelHeight: 600px; +passportPanelBorderCacheSize: 60px; +passportPanelTitleHeight: 62px; +passportPanelClose: IconButton(boxTitleClose) { + width: 60px; + height: 60px; + + rippleAreaPosition: point(8px, 8px); + rippleAreaSize: 44px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} +passportPanelTitleFont: font(18px semibold); +passportPanelTitle: FlatLabel(defaultFlatLabel) { + textFg: boxTitleFg; + maxHeight: 26px; + style: TextStyle(defaultTextStyle) { + font: passportPanelTitleFont; + linkFont: passportPanelTitleFont; + linkFontOver: font(18px semibold underline); + } +} +passportPanelTitleTop: 18px; +passportPanelTitleLeft: 22px; +passportPanelTitleSkip: 0px; +passportPanelBack: IconButton(passportPanelClose) { + icon: infoTopBarBackIcon; + iconOver: infoTopBarBackIconOver; +} + +passportPasswordFieldBottom: 306px; +passportPasswordFieldSkip: 29px; +passportPasswordHintSkip: 10px; +passportPasswordUserpicSkip: 14px; +passportPasswordUserpic: UserpicButton(defaultUserpicButton) { + size: size(80px, 80px); + photoSize: 80px; + photoPosition: point(0px, 0px); +} +passportPasswordSubmit: RoundButton(defaultActiveButton) { + width: 200px; + height: 44px; + textTop: 12px; + font: font(semibold 15px); +} +passportPasswordSubmitBottom: 72px; +passportPasswordForgotBottom: 36px; + +passportPanelScroll: ScrollArea(defaultScrollArea) { + deltat: 6px; + deltab: 6px; + + topsh: 0px; + bottomsh: 0px; +} + +passportPanelAuthorize: RoundButton(passportPasswordSubmit) { + width: 0px; + height: 49px; + padding: margins(0px, -3px, 0px, 0px); + textTop: 16px; +} +passportPanelSaveValue: RoundButton(passportPanelAuthorize) { + textFg: windowActiveTextFg; + textFgOver: windowActiveTextFg; + textBg: windowBg; + textBgOver: windowBgOver; + ripple: defaultRippleAnimation; +} +passportFormAbout1Padding: margins(10px, 4px, 10px, 0px); +passportFormAbout2Padding: margins(10px, 0px, 10px, 22px); +passportFormHeader: FlatLabel(boxLabel) { + textFg: windowActiveTextFg; + style: semiboldTextStyle; +} +passportFormHeaderPadding: margins(22px, 20px, 22px, 9px); +passportFormUserpic: UserpicButton(passportPasswordUserpic) { + size: size(60px, 60px); + photoSize: 60px; +} +passportFormUserpicPadding: margins(0px, 5px, 0px, 10px); +passportFormDividerHeight: 13px; +passportFormPolicy: FlatLabel(defaultFlatLabel) { + minWidth: 285px; + align: align(topleft); + textFg: windowSubTextFg; + style: TextStyle(defaultTextStyle) { + linkFont: font(fsize semibold underline); + linkFontOver: font(fsize semibold underline); + } + palette: TextPalette(defaultTextPalette) { + linkFg: windowSubTextFg; + } +} +passportFormPolicyPadding: margins(22px, 7px, 22px, 28px); + +passportRowPadding: margins(22px, 8px, 25px, 8px); +passportRowSkip: 2px; 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; +passportRowReadyIcon: icon {{ "send_control_save", windowActiveTextFg }}; +passportRowEmptyIcon: icon {{ "title_back-flip_horizontal", menuIconFgOver }}; +passportRowTitleFg: windowFg; +passportRowDescriptionFg: windowSubTextFg; + +passportScansHeaderPadding: margins(22px, 10px, 22px, 10px); +passportUploadButton: InfoProfileButton { + textFg: windowActiveTextFg; + textFgOver: windowActiveTextFg; + textBg: windowBg; + textBgOver: windowBgOver; + + font: semiboldFont; + + height: 18px; + padding: margins(22px, 14px, 22px, 12px); + + ripple: defaultRippleAnimation; } +passportUploadButtonPadding: margins(0px, 10px, 0px, 10px); +passportUploadHeaderPadding: margins(22px, 14px, 22px, 3px); + +passportScanNameStyle: TextStyle(defaultTextStyle) { + font: font(boxFontSize semibold); +} +passportScanRow: PassportScanRow { + padding: margins(22px, 10px, 10px, 10px); + size: 40px; + textLeft: 53px; + nameTop: 1px; + statusTop: 22px; + border: 1px; + borderFg: inputBorderFg; + remove: stickersRemove; + restore: stickersUndoRemove; +} +passportScanDeletedOpacity: stickersRowDisabledOpacity; + +passportDetailsHeaderPadding: margins(22px, 20px, 33px, 10px); +passportDetailsPadding: margins(22px, 10px, 28px, 10px); +passportDetailsField: InputField(defaultInputField) { + textMargins: margins(2px, 7px, 2px, 0px); + placeholderScale: 0.; + heightMin: 32px; + font: normalFont; +} +passportDetailsFieldLeft: 116px; +passportDetailsFieldTop: 2px; +passportDetailsFieldSkipMin: 12px; diff --git a/Telegram/SourceFiles/passport/passport_edit_identity_box.cpp b/Telegram/SourceFiles/passport/passport_edit_identity_box.cpp index e23913ad2..145a55ab1 100644 --- a/Telegram/SourceFiles/passport/passport_edit_identity_box.cpp +++ b/Telegram/SourceFiles/passport/passport_edit_identity_box.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "passport/passport_edit_identity_box.h" -#include "passport/passport_form_view_separate.h" +#include "passport/passport_panel_controller.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/buttons.h" #include "ui/text_options.h" @@ -64,7 +64,7 @@ ScanButton::ScanButton( st::defaultTextStyle, description, Ui::NameTextOptions()) -, _delete(this, st::passportRowCheckbox) { +, _delete(this, st::passportScanDelete) { } void ScanButton::setImage(const QImage &image) { @@ -142,7 +142,7 @@ void ScanButton::paintEvent(QPaintEvent *e) { IdentityBox::IdentityBox( QWidget*, - not_null controller, + not_null controller, int valueIndex, const IdentityData &data, std::vector &&files) diff --git a/Telegram/SourceFiles/passport/passport_edit_identity_box.h b/Telegram/SourceFiles/passport/passport_edit_identity_box.h index 343f4677a..6359ffc92 100644 --- a/Telegram/SourceFiles/passport/passport_edit_identity_box.h +++ b/Telegram/SourceFiles/passport/passport_edit_identity_box.h @@ -16,7 +16,7 @@ class InputField; namespace Passport { -class ViewSeparate; +class PanelController; struct ScanInfo; class ScanButton; @@ -29,7 +29,7 @@ class IdentityBox : public BoxContent { public: IdentityBox( QWidget*, - not_null controller, + not_null controller, int valueIndex, const IdentityData &data, std::vector &&files); @@ -49,7 +49,7 @@ private: void updateControlsPosition(); void save(); - not_null _controller; + not_null _controller; int _valueIndex = -1; std::vector _files; diff --git a/Telegram/SourceFiles/passport/passport_form_box.cpp b/Telegram/SourceFiles/passport/passport_form_box.cpp deleted file mode 100644 index 96f11157d..000000000 --- a/Telegram/SourceFiles/passport/passport_form_box.cpp +++ /dev/null @@ -1,264 +0,0 @@ -/* -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_view_separate.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]->addClickHandler([=] { - _controller->editValue(index); - }); - } - _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 deleted file mode 100644 index 5f4e2ab84..000000000 --- a/Telegram/SourceFiles/passport/passport_form_box.h +++ /dev/null @@ -1,39 +0,0 @@ -/* -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 ViewSeparate; - -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 e6062cb3e..e14d4190e 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -7,14 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "passport/passport_form_controller.h" -#include "passport/passport_form_box.h" -#include "passport/passport_edit_identity_box.h" #include "passport/passport_encryption.h" -#include "passport/passport_form_view_separate.h" +#include "passport/passport_panel_controller.h" #include "boxes/confirm_box.h" #include "lang/lang_keys.h" #include "base/openssl_help.h" #include "mainwindow.h" +#include "window/window_controller.h" #include "auth_session.h" #include "storage/localimageloader.h" #include "storage/localstorage.h" @@ -93,8 +92,8 @@ FormController::FormController( not_null controller, const FormRequest &request) : _controller(controller) -, _view(std::make_unique(this)) -, _request(request) { +, _request(request) +, _view(std::make_unique(this)) { } void FormController::show() { @@ -193,7 +192,7 @@ void FormController::decryptValue(Value &value) { resetValue(value); return; } - value.data.parsed = DeserializeData(DecryptData( + value.data.parsed.fields = DeserializeData(DecryptData( bytes::make_span(value.data.original), value.data.hash, value.data.secret)); @@ -220,20 +219,6 @@ bool FormController::validateValueSecrets(Value &value) { return false; } } - const auto fileHashesSecrets = ranges::view::all( - value.files - ) | ranges::view::transform([](File &file) { - return bytes::concatenate(file.hash, file.encryptedSecret); - }); - const auto countedHash = openssl::Sha256(bytes::concatenate( - value.data.hash, - value.data.encryptedSecret, - bytes::concatenate(fileHashesSecrets))); - if (value.consistencyHash != countedHash) { - LOG(("API Error: Wrong hash after decrypting value secrets. " - "Forgetting files and data :(")); - return false; - } return true; } @@ -312,15 +297,24 @@ void FormController::encryptScan( }); } -void FormController::deleteScan( +void FormController::deleteScan(int valueIndex, int fileIndex) { + scanDeleteRestore(valueIndex, fileIndex, true); +} + +void FormController::restoreScan(int valueIndex, int fileIndex) { + scanDeleteRestore(valueIndex, fileIndex, false); +} + +void FormController::scanDeleteRestore( int valueIndex, - int fileIndex) { + int fileIndex, + bool deleted) { Expects(valueIndex >= 0 && valueIndex < _form.rows.size()); Expects(fileIndex >= 0 && fileIndex < _form.rows[valueIndex].filesInEdit.size()); auto &file = _form.rows[valueIndex].filesInEdit[fileIndex]; - file.deleted = !file.deleted; + file.deleted = deleted; _scanUpdated.fire(&file); } @@ -590,7 +584,7 @@ void FormController::saveEncryptedValue(int index) { value.data.secret = GenerateSecretBytes(); } const auto encryptedData = EncryptData( - SerializeData(value.data.parsed), + SerializeData(value.data.parsed.fields), value.data.secret); value.data.hash = encryptedData.hash; value.data.encryptedSecret = EncryptValueSecret( @@ -598,20 +592,6 @@ void FormController::saveEncryptedValue(int index) { _secret, value.data.hash); - const auto fileHashesSecrets = ranges::view::all( - value.filesInEdit - ) | ranges::view::filter([](const EditFile &file) { - return !file.deleted; - }) | ranges::view::transform([](const EditFile &file) { - return bytes::concatenate( - file.fields.hash, - file.fields.encryptedSecret); - }); - value.consistencyHash = openssl::Sha256(bytes::concatenate( - value.data.hash, - value.data.encryptedSecret, - bytes::concatenate(fileHashesSecrets))); - const auto wrap = [&] { switch (value.type) { case Value::Type::Identity: return MTP_inputSecureValueIdentity; @@ -624,8 +604,7 @@ void FormController::saveEncryptedValue(int index) { MTP_bytes(encryptedData.bytes), MTP_bytes(value.data.hash), MTP_bytes(value.data.encryptedSecret)), - MTP_vector(inputFiles), - MTP_bytes(value.consistencyHash))); + MTP_vector(inputFiles))); } void FormController::savePlainTextValue(int index) { @@ -633,11 +612,7 @@ void FormController::savePlainTextValue(int index) { Expects(!isEncryptedValue(_form.rows[index].type)); auto &value = _form.rows[index]; - const auto text = value.data.parsed[QString("value")]; - QVector(); - value.consistencyHash = openssl::Sha256( - bytes::make_span(text.toUtf8())); - + const auto text = value.data.parsed.fields[QString("value")]; const auto wrap = [&] { switch (value.type) { case Value::Type::Phone: return MTP_inputSecureValuePhone; @@ -645,9 +620,7 @@ void FormController::savePlainTextValue(int index) { } Unexpected("Value type in savePlainTextValue()."); }(); - sendSaveRequest(index, wrap( - MTP_string(text), - MTP_bytes(value.consistencyHash))); + sendSaveRequest(index, wrap(MTP_string(text))); } void FormController::sendSaveRequest( @@ -747,7 +720,6 @@ auto FormController::parseEncryptedValue( if (data.has_verified()) { result.verification = parseVerified(data.vverified); } - result.consistencyHash = bytes::make_vector(data.vhash.v); const auto &fields = data.vdata.c_secureData(); result.data.original = fields.vdata.v; result.data.hash = bytes::make_vector(fields.vdata_hash.v); @@ -822,22 +794,10 @@ auto FormController::parsePlainTextValue( const QByteArray &text, const DataType &data) const -> Value { auto result = Value(type); - const auto check = bytes::compare( - bytes::make_span(data.vhash.v), - openssl::Sha256(bytes::make_span(text))); - if (check != 0) { - LOG(("API Error: Bad hash for plain text value. " - "Value '%1', hash '%2'" - ).arg(QString::fromUtf8(text) - ).arg(Logs::mb(data.vhash.v.data(), data.vhash.v.size()).str() - )); - return result; - } - result.data.parsed[QString("value")] = QString::fromUtf8(text); + result.data.parsed.fields[QString("value")] = QString::fromUtf8(text); if (data.has_verified()) { result.verification = parseVerified(data.vverified); } - result.consistencyHash = bytes::make_vector(data.vhash.v); return result; } @@ -917,7 +877,7 @@ auto FormController::findFile(const FileKey &key) void FormController::formDone(const MTPaccount_AuthorizationForm &result) { parseForm(result); if (!_passwordRequestId) { - _view->showForm(); + showForm(); } } @@ -963,7 +923,12 @@ void FormController::parseForm(const MTPaccount_AuthorizationForm &result) { } void FormController::formFail(const RPCError &error) { - Ui::show(Box(lang(lng_passport_form_error))); + // #TODO passport testing + _form.rows.push_back(Value(Value::Type::Identity)); + _form.rows.back().data.parsed.fields["first_name"] = "test1"; + _form.rows.back().data.parsed.fields["last_name"] = "test2"; + _view->editValue(0); +// Ui::show(Box(lang(lng_passport_form_error))); } void FormController::requestPassword() { @@ -987,7 +952,21 @@ void FormController::passwordDone(const MTPaccount_Password &result) { break; } if (!_formRequestId) { - _view->showForm(); + showForm(); + } +} + +void FormController::showForm() { + if (!_bot) { + Ui::show(Box("Could not get authorization bot.")); + return; + } + if (!_password.salt.empty()) { + _view->showAskPassword(); + } else if (!_password.unconfirmedPattern.isEmpty()) { + _view->showPasswordUnconfirmed(); + } else { + _view->showNoPassword(); } } @@ -1012,6 +991,19 @@ void FormController::parsePassword(const MTPDaccount_password &result) { openssl::AddRandomSeed(bytes::make_span(result.vsecure_random.v)); } +void FormController::cancel() { + if (!_cancelled) { + _cancelled = true; + crl::on_main(this, [=] { + _controller->clearAuthForm(); + }); + } +} + +rpl::lifetime &FormController::lifetime() { + return _lifetime; +} + FormController::~FormController() = default; } // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h index 9cf09cc92..fd30ecd44 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_controller.h @@ -96,9 +96,13 @@ struct Verification { TimeId date; }; +struct ValueMap { + std::map fields; +}; + struct ValueData { QByteArray original; - std::map parsed; + ValueMap parsed; bytes::vector hash; bytes::vector secret; bytes::vector encryptedSecret; @@ -120,7 +124,6 @@ struct Value { ValueData data; std::vector files; std::vector filesInEdit; - bytes::vector consistencyHash; base::optional verification; }; @@ -178,6 +181,7 @@ public: void uploadScan(int valueIndex, QByteArray &&content); void deleteScan(int valueIndex, int fileIndex); + void restoreScan(int valueIndex, int fileIndex); rpl::producer<> secretReadyEvents() const; @@ -191,9 +195,9 @@ public: void cancelValueEdit(int index); void saveValueEdit(int index); - rpl::lifetime &lifetime() { - return _lifetime; - } + void cancel(); + + rpl::lifetime &lifetime(); ~FormController(); @@ -259,6 +263,7 @@ private: void scanUploadDone(const Storage::UploadSecureDone &data); void scanUploadProgress(const Storage::UploadSecureProgress &data); void scanUploadFail(const FullMsgId &fullId); + void scanDeleteRestore(int valueIndex, int fileIndex, bool deleted); bool isEncryptedValue(Value::Type type) const; void saveEncryptedValue(int index); @@ -266,7 +271,6 @@ private: void sendSaveRequest(int index, const MTPInputSecureValue &value); not_null _controller; - std::unique_ptr _view; FormRequest _request; UserData *_bot = nullptr; @@ -276,6 +280,7 @@ private: PasswordSettings _password; Form _form; + bool _cancelled = false; std::map> _fileLoaders; rpl::event_stream> _scanUpdated; @@ -290,6 +295,8 @@ private: rpl::lifetime _uploaderSubscriptions; rpl::lifetime _lifetime; + std::unique_ptr _view; + }; } // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_form_row.cpp b/Telegram/SourceFiles/passport/passport_form_row.cpp index 955df35ed..e07e2aede 100644 --- a/Telegram/SourceFiles/passport/passport_form_row.cpp +++ b/Telegram/SourceFiles/passport/passport_form_row.cpp @@ -32,18 +32,9 @@ FormRow::FormRow( } 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(); - } + _ready = ready; resizeToWidth(width()); + update(); } int FormRow::resizeGetHeight(int newWidth) { @@ -55,13 +46,6 @@ int FormRow::resizeGetHeight(int newWidth) { + st::passportRowSkip + _descriptionHeight + st::passportRowPadding.bottom(); - if (_checkbox) { - const auto right = st::passportRowPadding.right(); - _checkbox->moveToRight( - right, - (result - _checkbox->height()) / 2, - newWidth); - } return result; } @@ -69,7 +53,7 @@ int FormRow::countAvailableWidth(int newWidth) const { return newWidth - st::passportRowPadding.left() - st::passportRowPadding.right() - - (_checkbox ? _checkbox->width() : 0); + - (_ready ? st::passportRowReadyIcon : st::passportRowEmptyIcon).width(); } int FormRow::countAvailableWidth() const { @@ -86,11 +70,22 @@ void FormRow::paintEvent(QPaintEvent *e) { const auto availableWidth = countAvailableWidth(); auto top = st::passportRowPadding.top(); + p.setPen(st::passportRowTitleFg); _title.drawLeft(p, left, top, availableWidth, width()); top += _titleHeight + st::passportRowSkip; + p.setPen(st::passportRowDescriptionFg); _description.drawLeft(p, left, top, availableWidth, width()); top += _descriptionHeight + st::passportRowPadding.bottom(); + + const auto &icon = _ready + ? st::passportRowReadyIcon + : st::passportRowEmptyIcon; + icon.paint( + p, + width() - st::passportRowPadding.right() - icon.width(), + (height() - icon.height()) / 2, + width()); } } // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_form_row.h b/Telegram/SourceFiles/passport/passport_form_row.h index 070ce952f..b5525f0f9 100644 --- a/Telegram/SourceFiles/passport/passport_form_row.h +++ b/Telegram/SourceFiles/passport/passport_form_row.h @@ -16,30 +16,4 @@ class FadeWrapScaled; 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/SourceFiles/passport/passport_form_view_controller.h b/Telegram/SourceFiles/passport/passport_form_view_controller.h index 317df769f..a6aab70c5 100644 --- a/Telegram/SourceFiles/passport/passport_form_view_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_view_controller.h @@ -13,7 +13,9 @@ struct Value; class ViewController { public: - virtual void showForm() = 0; + virtual void showAskPassword() = 0; + virtual void showNoPassword() = 0; + virtual void showPasswordUnconfirmed() = 0; virtual void editValue(int index) = 0; virtual ~ViewController() { diff --git a/Telegram/SourceFiles/passport/passport_panel.cpp b/Telegram/SourceFiles/passport/passport_panel.cpp new file mode 100644 index 000000000..c63529693 --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_panel.cpp @@ -0,0 +1,392 @@ +/* +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_panel.h" + +#include "passport/passport_panel_controller.h" +#include "passport/passport_panel_form.h" +#include "passport/passport_panel_password.h" +#include "window/main_window.h" +#include "platform/platform_specific.h" +#include "ui/widgets/shadow.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/fade_wrap.h" +#include "lang/lang_keys.h" +#include "messenger.h" +#include "styles/style_passport.h" +#include "styles/style_widgets.h" +#include "styles/style_calls.h" + +namespace Passport { + +Panel::Panel(not_null controller) +: _controller(controller) +, _close(this, st::passportPanelClose) +, _title( + this, + lang(lng_passport_title), + Ui::FlatLabel::InitType::Simple, + st::passportPanelTitle) +, _back(this, object_ptr(this, st::passportPanelBack)) { + setMouseTracking(true); + setWindowIcon(Window::CreateIcon()); + initControls(); + initLayout(); +} + +void Panel::initControls() { + widthValue( + ) | rpl::start_with_next([=](int width) { + _back->moveToLeft(_padding.left(), _padding.top()); + _close->moveToRight(_padding.right(), _padding.top()); + _title->resizeToWidth(width + - _padding.left() - _back->width() + - _padding.right() - _close->width()); + updateTitlePosition(); + }, lifetime()); + + _close->addClickHandler([=] { + hideAndDestroy(); + }); + + _back->toggledValue( + ) | rpl::start_with_next([=](bool toggled) { + _titleLeft.start( + [=] { updateTitlePosition(); }, + toggled ? 0. : 1., + toggled ? 1. : 0., + st::fadeWrapDuration); + }, _title->lifetime()); + _back->hide(anim::type::instant); + _titleLeft.finish(); + + _title->setAttribute(Qt::WA_TransparentForMouseEvents); +} + +void Panel::updateTitlePosition() { + const auto progress = _titleLeft.current(_back->toggled() ? 1. : 0.); + const auto left = anim::interpolate( + st::passportPanelTitleLeft, + _back->width() + st::passportPanelTitleSkip, + progress); + _title->moveToLeft( + _padding.left() + left, + _padding.top() + st::passportPanelTitleTop); + +} + +rpl::producer<> Panel::backRequests() const { + return _back->entity()->clicks(); +} + +void Panel::setBackAllowed(bool allowed) { + if (allowed != _back->toggled()) { + _back->toggle(allowed, anim::type::normal); + } +} + +void Panel::showAndActivate() { + toggleOpacityAnimation(true); + raise(); + setWindowState(windowState() | Qt::WindowActive); + activateWindow(); + setFocus(); +} + +bool Panel::eventHook(QEvent *e) { + if (e->type() == QEvent::WindowDeactivate) { + } + return RpWidget::eventHook(e); +} + +void Panel::initLayout() { + setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) + | Qt::WindowStaysOnTopHint + | Qt::BypassWindowManagerHint + | Qt::NoDropShadowWindowHint + | Qt::Dialog); + setAttribute(Qt::WA_MacAlwaysShowToolWindow); + setAttribute(Qt::WA_NoSystemBackground, true); + setAttribute(Qt::WA_TranslucentBackground, true); + + initGeometry(); + createBorderImage(); + + Platform::InitOnTopPanel(this); +} + +void Panel::createBorderImage() { + if (!_useTransparency || !_borderParts.isNull()) { + return; + } + const auto cacheSize = st::passportPanelBorderCacheSize; + auto cache = QImage( + cacheSize * cIntRetinaFactor(), + cacheSize * cIntRetinaFactor(), + QImage::Format_ARGB32_Premultiplied); + cache.setDevicePixelRatio(cRetinaFactor()); + cache.fill(Qt::transparent); + { + Painter p(&cache); + auto inner = QRect(0, 0, cacheSize, cacheSize).marginsRemoved( + _padding); + Ui::Shadow::paint(p, inner, width(), st::callShadow); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.setBrush(st::windowBg); + p.setPen(Qt::NoPen); + PainterHighQualityEnabler hq(p); + p.drawRoundedRect( + myrtlrect(inner), + st::callRadius, + st::callRadius); + } + _borderParts = App::pixmapFromImageInPlace(std::move(cache)); +} + +void Panel::toggleOpacityAnimation(bool visible) { + if (_visible == visible) { + return; + } + + _visible = visible; + if (_useTransparency) { + if (_animationCache.isNull()) { + showControls(); + _animationCache = Ui::GrabWidget(this); + hideChildren(); + } + _opacityAnimation.start( + [this] { opacityCallback(); }, + _visible ? 0. : 1., + _visible ? 1. : 0., + st::callPanelDuration, + _visible ? anim::easeOutCirc : anim::easeInCirc); + } + if (isHidden() && _visible) { + show(); + } +} + +void Panel::opacityCallback() { + update(); + if (!_visible && !_opacityAnimation.animating()) { + finishAnimating(); + } +} + +void Panel::finishAnimating() { + _animationCache = QPixmap(); + if (_visible) { + showControls(); + _inner->setFocus(); + } else { + destroyDelayed(); + } +} + +void Panel::showControls() { + showChildren(); + if (!_back->toggled()) { + _back->setVisible(false); + } +} + +void Panel::destroyDelayed() { + hide(); + _controller->cancelAuth(); +} + +void Panel::hideAndDestroy() { + toggleOpacityAnimation(false); + if (_animationCache.isNull()) { + destroyDelayed(); + } +} + +void Panel::showAskPassword() { + showInner(base::make_unique_q(this, _controller)); +} + +void Panel::showNoPassword() { + showInner(base::make_unique_q(this, _controller)); +} + +void Panel::showPasswordUnconfirmed() { + showInner(base::make_unique_q(this, _controller)); +} + +void Panel::showForm() { + showInner(base::make_unique_q(this, _controller)); +} + +void Panel::showEditValue(object_ptr from) { + showInner(base::unique_qptr(from.data())); +} + +void Panel::showInner(base::unique_qptr inner) { + _inner = std::move(inner); + _inner->setParent(this); + _inner->show(); + + sizeValue( + ) | rpl::start_with_next([=] { + const auto top = _padding.top() + st::passportPanelTitleHeight; + _inner->setGeometry( + _padding.left(), + top, + width() - _padding.left() - _padding.right(), + height() - top - _padding.bottom()); + }, _inner->lifetime()); + + showAndActivate(); + if (!_inner->isHidden()) { + _inner->setFocus(); + } +} + +void Panel::initGeometry() { + auto center = Messenger::Instance().getPointForCallPanelCenter(); + _useTransparency = Platform::TranslucentWindowsSupported(center); + setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency); + _padding = _useTransparency + ? st::callShadow.extend + : style::margins( + st::lineWidth, + st::lineWidth, + st::lineWidth, + st::lineWidth); + auto screen = QApplication::desktop()->screenGeometry(center); + auto rect = QRect(0, 0, st::passportPanelWidth, st::passportPanelHeight); + setGeometry(rect.translated(center - rect.center()).marginsAdded(_padding)); + updateControlsGeometry(); +} + +void Panel::resizeEvent(QResizeEvent *e) { + updateControlsGeometry(); +} + +void Panel::updateControlsGeometry() { +} + +void Panel::paintEvent(QPaintEvent *e) { + Painter p(this); + if (!_animationCache.isNull()) { + auto opacity = _opacityAnimation.current( + getms(), + _visible ? 1. : 0.); + if (!_opacityAnimation.animating()) { + finishAnimating(); + if (isHidden()) return; + } else { + Platform::StartTranslucentPaint(p, e); + p.setOpacity(opacity); + + PainterHighQualityEnabler hq(p); + auto marginRatio = (1. - opacity) / 5; + auto marginWidth = qRound(width() * marginRatio); + auto marginHeight = qRound(height() * marginRatio); + p.drawPixmap( + rect().marginsRemoved( + QMargins( + marginWidth, + marginHeight, + marginWidth, + marginHeight)), + _animationCache, + QRect(QPoint(0, 0), _animationCache.size())); + return; + } + } + + if (_useTransparency) { + Platform::StartTranslucentPaint(p, e); + paintShadowBorder(p); + } else { + paintOpaqueBorder(p); + } +} + +void Panel::paintShadowBorder(Painter &p) const { + const auto factor = cIntRetinaFactor(); + const auto size = st::passportPanelBorderCacheSize; + const auto part1 = size / 3; + const auto part2 = size - part1; + const auto corner = QSize(part1, part1) * factor; + const auto topleft = QRect(QPoint(0, 0), corner); + p.drawPixmap(QRect(0, 0, part1, part1), _borderParts, topleft); + const auto topright = QRect(QPoint(part2, 0) * factor, corner); + p.drawPixmap(QRect(width() - part1, 0, part1, part1), _borderParts, topright); + const auto bottomleft = QRect(QPoint(0, part2) * factor, corner); + p.drawPixmap(QRect(0, height() - part1, part1, part1), _borderParts, bottomleft); + const auto bottomright = QRect(QPoint(part2, part2) * factor, corner); + p.drawPixmap(QRect(width() - part1, height() - part1, part1, part1), _borderParts, bottomright); + const auto left = QRect(QPoint(0, part1) * factor, QSize(_padding.left(), part2 - part1) * factor); + p.drawPixmap(QRect(0, part1, _padding.left(), height() - 2 * part1), _borderParts, left); + const auto top = QRect(QPoint(part1, 0) * factor, QSize(part2 - part1, _padding.top() + st::callRadius) * factor); + p.drawPixmap(QRect(part1, 0, width() - 2 * part1, _padding.top() + st::callRadius), _borderParts, top); + const auto right = QRect(QPoint(size - _padding.right(), part1) * factor, QSize(_padding.right(), part2 - part1) * factor); + p.drawPixmap(QRect(width() - _padding.right(), part1, _padding.right(), height() - 2 * part1), _borderParts, right); + const auto bottom = QRect(QPoint(part1, size - _padding.bottom() - st::callRadius) * factor, QSize(part2 - part1, _padding.bottom() + st::callRadius) * factor); + p.drawPixmap(QRect(part1, height() - _padding.bottom() - st::callRadius, width() - 2 * part1, _padding.bottom() + st::callRadius), _borderParts, bottom); + + p.fillRect(_padding.left(), _padding.top() + st::callRadius, width() - _padding.left() - _padding.right(), height() - _padding.top() - _padding.bottom() - 2 * st::callRadius, st::windowBg); +} + +void Panel::paintOpaqueBorder(Painter &p) const { + const auto border = st::windowShadowFgFallback; + p.fillRect(0, 0, width(), _padding.top(), border); + p.fillRect(myrtlrect(0, _padding.top(), _padding.left(), height() - _padding.top()), border); + p.fillRect(myrtlrect(width() - _padding.right(), _padding.top(), _padding.right(), height() - _padding.top()), border); + p.fillRect(_padding.left(), height() - _padding.bottom(), width() - _padding.left() - _padding.right(), _padding.bottom(), border); + + p.fillRect(_padding.left(), _padding.top(), width() - _padding.left() - _padding.right(), height() - _padding.top() - _padding.bottom(), st::windowBg); +} + +void Panel::closeEvent(QCloseEvent *e) { + // #TODO +} + +void Panel::mousePressEvent(QMouseEvent *e) { + auto dragArea = myrtlrect( + _padding.left(), + _padding.top(), + width() - _padding.left() - _padding.right(), + st::passportPanelTitleHeight); + if (e->button() == Qt::LeftButton) { + if (dragArea.contains(e->pos())) { + _dragging = true; + _dragStartMousePosition = e->globalPos(); + _dragStartMyPosition = QPoint(x(), y()); + } else if (!rect().contains(e->pos())) { + } + } +} + +void Panel::mouseMoveEvent(QMouseEvent *e) { + if (_dragging) { + if (!(e->buttons() & Qt::LeftButton)) { + _dragging = false; + } else { + move(_dragStartMyPosition + (e->globalPos() - _dragStartMousePosition)); + } + } +} + +void Panel::mouseReleaseEvent(QMouseEvent *e) { + if (e->button() == Qt::LeftButton) { + _dragging = false; + } +} + +void Panel::leaveEventHook(QEvent *e) { +} + +void Panel::leaveToChildEvent(QEvent *e, QWidget *child) { +} + +} // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_panel.h b/Telegram/SourceFiles/passport/passport_panel.h new file mode 100644 index 000000000..8e1ae1121 --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_panel.h @@ -0,0 +1,95 @@ +/* +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" + +namespace Ui { +class IconButton; +class FlatLabel; +template +class FadeWrapScaled; +} // namespace Ui + +namespace Passport { + +class PanelController; + +class Panel + : public Ui::RpWidget + , private base::Subscriber { + +public: + Panel(not_null controller); + + void showAndActivate(); + void hideAndDestroy(); + + void showAskPassword(); + void showNoPassword(); + void showPasswordUnconfirmed(); + void showForm(); + void showEditValue(object_ptr form); + + rpl::producer<> backRequests() const; + void setBackAllowed(bool allowed); + +protected: + void paintEvent(QPaintEvent *e) override; + void closeEvent(QCloseEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void leaveEventHook(QEvent *e) override; + void leaveToChildEvent(QEvent *e, QWidget *child) override; + bool eventHook(QEvent *e) override; + +private: + void initControls(); + void initLayout(); + void initGeometry(); + void showControls(); + void updateControlsGeometry(); + void createBorderImage(); + void opacityCallback(); + void showInner(base::unique_qptr inner); + + void updateTitlePosition(); + void paintShadowBorder(Painter &p) const; + void paintOpaqueBorder(Painter &p) const; + + void toggleOpacityAnimation(bool visible); + void finishAnimating(); + void destroyDelayed(); + + not_null _controller; + object_ptr _close; + object_ptr _title; + object_ptr> _back; + base::unique_qptr _inner; + + bool _useTransparency = true; + style::margins _padding; + + bool _dragging = false; + QPoint _dragStartMousePosition; + QPoint _dragStartMyPosition; + + int _stateChangedSubscription = 0; + + Animation _titleLeft; + bool _visible = false; + + Animation _opacityAnimation; + QPixmap _animationCache; + QPixmap _borderParts; + +}; + +} // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_form_view_separate.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp similarity index 50% rename from Telegram/SourceFiles/passport/passport_form_view_separate.cpp rename to Telegram/SourceFiles/passport/passport_panel_controller.cpp index 6e6dd4f58..8e8ee056a 100644 --- a/Telegram/SourceFiles/passport/passport_form_view_separate.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp @@ -5,12 +5,13 @@ 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_view_separate.h" +#include "passport/passport_panel_controller.h" #include "lang/lang_keys.h" -#include "passport/passport_edit_identity_box.h" -#include "passport/passport_form_box.h" +#include "passport/passport_panel_edit_identity.h" +#include "passport/passport_panel.h" #include "boxes/confirm_box.h" +#include "layout.h" namespace Passport { @@ -49,15 +50,21 @@ BoxContent *BoxPointer::operator->() const { return get(); } -ViewSeparate::ViewSeparate(not_null form) +PanelController::PanelController(not_null form) : _form(form) { + _form->secretReadyEvents( + ) | rpl::start_with_next([=] { + if (_panel) { + _panel->showForm(); + } + }, lifetime()); } -not_null ViewSeparate::bot() const { +not_null PanelController::bot() const { return _form->bot(); } -void ViewSeparate::fillRows( +void PanelController::fillRows( base::lambdasubmitPassword(password); } -rpl::producer ViewSeparate::passwordError() const { +rpl::producer PanelController::passwordError() const { return _form->passwordError(); } -QString ViewSeparate::passwordHint() const { +QString PanelController::passwordHint() const { return _form->passwordHint(); } -rpl::producer<> ViewSeparate::secretReadyEvents() const { - return _form->secretReadyEvents(); -} - -QString ViewSeparate::defaultEmail() const { +QString PanelController::defaultEmail() const { return _form->defaultEmail(); } -QString ViewSeparate::defaultPhoneNumber() const { +QString PanelController::defaultPhoneNumber() const { return _form->defaultPhoneNumber(); } -void ViewSeparate::uploadScan(int valueIndex, QByteArray &&content) { - Expects(_editBox != nullptr); +void PanelController::uploadScan(int valueIndex, QByteArray &&content) { + Expects(_panel != nullptr); _form->uploadScan(valueIndex, std::move(content)); } -void ViewSeparate::deleteScan(int valueIndex, int fileIndex) { - Expects(_editBox != nullptr); +void PanelController::deleteScan(int valueIndex, int fileIndex) { + Expects(_panel != nullptr); _form->deleteScan(valueIndex, fileIndex); } -rpl::producer ViewSeparate::scanUpdated() const { +void PanelController::restoreScan(int valueIndex, int fileIndex) { + Expects(_panel != nullptr); + + _form->restoreScan(valueIndex, fileIndex); +} + +rpl::producer PanelController::scanUpdated() const { return _form->scanUpdated( ) | rpl::map([=](not_null file) { return collectScanInfo(*file); }); } -ScanInfo ViewSeparate::collectScanInfo(const EditFile &file) const { +ScanInfo PanelController::collectScanInfo(const EditFile &file) const { const auto status = [&] { - if (file.deleted) { - return QString("deleted"); - } else if (file.fields.accessHash) { + if (file.fields.accessHash) { if (file.fields.downloadOffset < 0) { - return QString("download failed"); + return lang(lng_attach_failed); } else if (file.fields.downloadOffset < file.fields.size) { - return QString("downloading %1 / %2" - ).arg(file.fields.downloadOffset - ).arg(file.fields.size); + return formatDownloadText( + file.fields.downloadOffset, + file.fields.size); } else { - return QString("uploaded ") - + langDateTimeFull(ParseDateTime(file.fields.date)); + return lng_passport_scan_uploaded( + lt_date, + langDateTimeFull(ParseDateTime(file.fields.date))); } } else if (file.uploadData) { if (file.uploadData->offset < 0) { - return QString("upload failed"); + return lang(lng_attach_failed); } else if (file.uploadData->fullId) { - return QString("uploading %1 / %2" - ).arg(file.uploadData->offset - ).arg(file.uploadData->bytes.size()); + return formatDownloadText( + file.uploadData->offset, + file.uploadData->bytes.size()); } else { - return QString("upload ready"); + return lng_passport_scan_uploaded( + lt_date, + langDateTimeFull(ParseDateTime(file.fields.date))); } } else { - return QString("preparing"); + return formatDownloadText(0, file.fields.size); } }(); return { FileKey{ file.fields.id, file.fields.dcId }, status, - file.fields.image }; + file.fields.image, + file.deleted }; } -void ViewSeparate::showForm() { - if (!_form->bot()) { - Ui::show(Box("Could not get authorization bot.")); - return; +void PanelController::showAskPassword() { + ensurePanelCreated(); + _panel->showAskPassword(); +} + +void PanelController::showNoPassword() { + ensurePanelCreated(); + _panel->showNoPassword(); +} + +void PanelController::showPasswordUnconfirmed() { + ensurePanelCreated(); + _panel->showPasswordUnconfirmed(); +} + +void PanelController::ensurePanelCreated() { + if (!_panel) { + _panel = std::make_unique(this); } - Ui::show(Box(this)); } -void ViewSeparate::editValue(int index) { +void PanelController::editValue(int index) { + ensurePanelCreated(); // #TODO passport testing + Expects(_panel != nullptr); + _editValue = _form->startValueEdit(index); Assert(_editValue != nullptr); - auto box = [&]() -> object_ptr { + auto content = [&]() -> object_ptr { switch (_editValue->type) { case Value::Type::Identity: - return Box( + return object_ptr( + _panel.get(), this, index, - valueDataIdentity(*_editValue), + _editValue->data.parsed, valueFiles(*_editValue)); } return { nullptr }; }(); - if (box) { - _editBox = Ui::show(std::move(box), LayerOption::KeepOther); - _editBox->boxClosing() | rpl::start_with_next([=] { + if (content) { + _panel->setBackAllowed(true); + _panel->backRequests( + ) | rpl::start_with_next([=] { cancelValueEdit(index); - }, _form->lifetime()); + _panel->setBackAllowed(false); + _panel->showForm(); + }, content->lifetime()); + _panel->showEditValue(std::move(content)); } else { cancelValueEdit(index); } } -IdentityData ViewSeparate::valueDataIdentity(const Value &value) const { - const auto &map = value.data.parsed; - auto result = IdentityData(); - if (const auto i = map.find(qsl("first_name")); i != map.cend()) { - result.name = i->second; - } - if (const auto i = map.find(qsl("last_name")); i != map.cend()) { - result.surname = i->second; - } - return result; -} - -std::vector ViewSeparate::valueFiles(const Value &value) const { +std::vector PanelController::valueFiles(const Value &value) const { auto result = std::vector(); for (const auto &file : value.filesInEdit) { result.push_back(collectScanInfo(file)); @@ -223,26 +243,30 @@ std::vector ViewSeparate::valueFiles(const Value &value) const { return result; } -void ViewSeparate::cancelValueEdit(int index) { +void PanelController::cancelValueEdit(int index) { if (base::take(_editValue)) { _form->cancelValueEdit(index); } } -void ViewSeparate::saveValueIdentity( - int index, - const IdentityData &data) { - Expects(_editBox != nullptr); +void PanelController::saveValue(int index, ValueMap &&data) { + Expects(_panel != nullptr); Expects(_editValue != nullptr); - Expects(_editValue->type == Value::Type::Identity); - _editValue->data.parsed[qsl("first_name")] = data.name; - _editValue->data.parsed[qsl("last_name")] = data.surname; + _editValue->data.parsed = std::move(data); _editValue = nullptr; - _editBox->closeBox(); + _panel->showForm(); _form->saveValueEdit(index); } +void PanelController::cancelAuth() { + _form->cancel(); +} + +rpl::lifetime &PanelController::lifetime() { + return _lifetime; +} + } // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_form_view_separate.h b/Telegram/SourceFiles/passport/passport_panel_controller.h similarity index 77% rename from Telegram/SourceFiles/passport/passport_form_view_separate.h rename to Telegram/SourceFiles/passport/passport_panel_controller.h index eefca7e93..72ea5b6aa 100644 --- a/Telegram/SourceFiles/passport/passport_form_view_separate.h +++ b/Telegram/SourceFiles/passport/passport_panel_controller.h @@ -13,13 +13,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Passport { class FormController; - -struct IdentityData; +class Panel; struct ScanInfo { FileKey key; QString status; QImage thumb; + bool deleted = false; }; @@ -40,9 +40,9 @@ private: }; -class ViewSeparate : public ViewController { +class PanelController : public ViewController { public: - ViewSeparate(not_null form); + PanelController(not_null form); not_null bot() const; @@ -52,14 +52,16 @@ public: void uploadScan(int valueIndex, QByteArray &&content); void deleteScan(int valueIndex, int fileIndex); + void restoreScan(int valueIndex, int fileIndex); rpl::producer scanUpdated() const; - rpl::producer<> secretReadyEvents() const; - QString defaultEmail() const; QString defaultPhoneNumber() const; - void showForm() override; + void showAskPassword() override; + void showNoPassword() override; + void showPasswordUnconfirmed() override; + void fillRows( base::lambda callback); void editValue(int index) override; - void saveValueIdentity(int index, const IdentityData &data); + void saveValue(int index, ValueMap &&data); + + void cancelAuth(); + + rpl::lifetime &lifetime(); private: + void ensurePanelCreated(); + void cancelValueEdit(int index); - IdentityData valueDataIdentity(const Value &value) const; std::vector valueFiles(const Value &value) const; ScanInfo collectScanInfo(const EditFile &file) const; not_null _form; + std::unique_ptr _panel; Value *_editValue = nullptr; - BoxPointer _editBox; + + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/passport/passport_panel_details_row.cpp b/Telegram/SourceFiles/passport/passport_panel_details_row.cpp new file mode 100644 index 000000000..16cf11c98 --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_panel_details_row.cpp @@ -0,0 +1,46 @@ +/* +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_panel_details_row.h" + +#include "ui/widgets/input_fields.h" +#include "styles/style_passport.h" + +namespace Passport { + +PanelDetailsRow::PanelDetailsRow( + QWidget *parent, + const QString &label, + const QString &value) +: _label(label) +, _field(this, st::passportDetailsField) { +} + +QPointer PanelDetailsRow::field() const { + return _field.data(); +} + +int PanelDetailsRow::resizeGetHeight(int newWidth) { + const auto padding = st::passportDetailsPadding; + const auto inputLeft = padding.left() + st::passportDetailsFieldLeft; + const auto inputTop = st::passportDetailsFieldTop; + const auto inputRight = padding.right(); + const auto inputWidth = std::max(newWidth - inputLeft - inputRight, 0); + _field->setGeometry(inputLeft, inputTop, inputWidth, _field->height()); + return padding.top() + st::semiboldFont->height + padding.bottom(); +} + +void PanelDetailsRow::paintEvent(QPaintEvent *e) { + Painter p(this); + + p.setFont(st::semiboldFont); + p.setPen(st::passportDetailsField.placeholderFg); + const auto padding = st::passportDetailsPadding; + p.drawTextLeft(padding.left(), padding.top(), width(), _label); +} + +} // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_panel_details_row.h b/Telegram/SourceFiles/passport/passport_panel_details_row.h new file mode 100644 index 000000000..9cbda048f --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_panel_details_row.h @@ -0,0 +1,38 @@ +/* +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" + +namespace Ui { +class InputField; +} // namespace Ui + +namespace Passport { + +class PanelDetailsRow : public Ui::RpWidget { +public: + PanelDetailsRow( + QWidget *parent, + const QString &label, + const QString &value); + + QPointer field() const; + +protected: + int resizeGetHeight(int newWidth) override; + + void paintEvent(QPaintEvent *e) override; + +private: + QString _label; + object_ptr _field; + +}; + +} // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_identity.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_identity.cpp new file mode 100644 index 000000000..1087a488d --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_panel_edit_identity.cpp @@ -0,0 +1,413 @@ +/* +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_panel_edit_identity.h" + +#include "passport/passport_panel_controller.h" +#include "passport/passport_panel_details_row.h" +#include "ui/widgets/input_fields.h" +#include "ui/widgets/scroll_area.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/shadow.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/fade_wrap.h" +#include "ui/text_options.h" +#include "info/profile/info_profile_button.h" +#include "info/profile/info_profile_values.h" +#include "boxes/abstract_box.h" +#include "lang/lang_keys.h" +#include "core/file_utilities.h" +#include "styles/style_widgets.h" +#include "styles/style_boxes.h" +#include "styles/style_passport.h" + +namespace Passport { + +class ScanButton : public Ui::AbstractButton { +public: + ScanButton( + QWidget *parent, + const style::PassportScanRow &st, + const QString &name, + const QString &status, + bool deleted); + + void setImage(const QImage &image); + void setStatus(const QString &status); + void setDeleted(bool deleted); + + rpl::producer<> deleteClicks() const { + return _delete->entity()->clicks(); + } + rpl::producer<> restoreClicks() const { + return _restore->entity()->clicks(); + } + +protected: + int resizeGetHeight(int newWidth) override; + + void paintEvent(QPaintEvent *e) override; + +private: + int countAvailableWidth() const; + + const style::PassportScanRow &_st; + Text _name; + Text _status; + int _nameHeight = 0; + int _statusHeight = 0; + QImage _image; + object_ptr> _delete; + object_ptr> _restore; + +}; + +ScanButton::ScanButton( + QWidget *parent, + const style::PassportScanRow &st, + const QString &name, + const QString &status, + bool deleted) +: AbstractButton(parent) +, _st(st) +, _name( + st::passportScanNameStyle, + name, + Ui::NameTextOptions()) +, _status( + st::defaultTextStyle, + status, + Ui::NameTextOptions()) +, _delete(this, object_ptr(this, _st.remove)) +, _restore( + this, + object_ptr( + this, + langFactory(lng_passport_delete_scan_undo), + _st.restore)) { + _delete->toggle(!deleted, anim::type::instant); + _restore->toggle(deleted, anim::type::instant); +} + +void ScanButton::setImage(const QImage &image) { + _image = image; + update(); +} + +void ScanButton::setStatus(const QString &status) { + _status.setText( + st::defaultTextStyle, + status, + Ui::NameTextOptions()); + update(); +} + +void ScanButton::setDeleted(bool deleted) { + _delete->toggle(!deleted, anim::type::instant); + _restore->toggle(deleted, anim::type::instant); + update(); +} + +int ScanButton::resizeGetHeight(int newWidth) { + _nameHeight = st::semiboldFont->height; + _statusHeight = st::normalFont->height; + const auto result = _st.padding.top() + _st.size + _st.padding.bottom(); + const auto right = _st.padding.right(); + _delete->moveToRight( + right, + (result - _delete->height()) / 2, + newWidth); + _restore->moveToRight( + right, + (result - _restore->height()) / 2, + newWidth); + return result + st::lineWidth; +} + +int ScanButton::countAvailableWidth() const { + return width() + - _st.padding.left() + - _st.textLeft + - _st.padding.right() + - std::max(_delete->width(), _restore->width()); +} + +void ScanButton::paintEvent(QPaintEvent *e) { + Painter p(this); + + const auto left = _st.padding.left(); + const auto top = _st.padding.top(); + p.fillRect( + left, + height() - _st.border, + width() - left, + _st.border, + _st.borderFg); + + if (_restore->toggled()) { + p.setOpacity(st::passportScanDeletedOpacity); + } + + if (_image.isNull()) { + p.fillRect(left, top, _st.size, _st.size, Qt::black); + } else { + PainterHighQualityEnabler hq(p); + const auto fromRect = [&] { + if (_image.width() > _image.height()) { + const auto shift = (_image.width() - _image.height()) / 2; + return QRect(shift, 0, _image.height(), _image.height()); + } else { + const auto shift = (_image.height() - _image.width()) / 2; + return QRect(0, shift, _image.width(), _image.width()); + } + }(); + p.drawImage(QRect(left, top, _st.size, _st.size), _image, fromRect); + } + const auto availableWidth = countAvailableWidth(); + + p.setPen(st::windowFg); + _name.drawLeftElided( + p, + left + _st.textLeft, + top + _st.nameTop, + availableWidth, + width()); + p.setPen(st::windowSubTextFg); + _status.drawLeftElided( + p, + left + _st.textLeft, + top + _st.statusTop, + availableWidth, + width()); +} + +PanelEditIdentity::PanelEditIdentity( + QWidget*, + not_null controller, + int valueIndex, + const ValueMap &data, + std::vector &&files) +: _controller(controller) +, _valueIndex(valueIndex) +, _files(std::move(files)) +, _scroll(this, st::passportPanelScroll) +, _topShadow(this) +, _bottomShadow(this) +, _done( + this, + langFactory(lng_passport_save_value), + st::passportPanelSaveValue) { + setupControls(data); +} + +void PanelEditIdentity::setupControls(const ValueMap &data) { + const auto inner = setupContent(data); + + using namespace rpl::mappers; + + _topShadow->toggleOn( + _scroll->scrollTopValue() | rpl::map(_1 > 0)); + _done->addClickHandler([=] { + crl::on_main(this, [=] { + save(); + }); + }); + _controller->scanUpdated( + ) | rpl::start_with_next([=](ScanInfo &&info) { + updateScan(std::move(info)); + }, lifetime()); +} + +not_null PanelEditIdentity::setupContent( + const ValueMap &data) { + const auto inner = _scroll->setOwnedWidget( + object_ptr(this)); + _scroll->widthValue( + ) | rpl::start_with_next([=](int width) { + inner->resizeToWidth(width); + }, inner->lifetime()); + + _scansDivider = inner->add( + object_ptr>( + inner, + object_ptr( + inner, + st::passportFormDividerHeight))); + _scansDivider->toggle(_files.empty(), anim::type::instant); + + _scansHeader = inner->add( + object_ptr>( + inner, + object_ptr( + inner, + lang(lng_passport_upload_header), + Ui::FlatLabel::InitType::Simple, + st::passportFormHeader), + st::passportUploadHeaderPadding)); + _scansHeader->toggle(!_files.empty(), anim::type::instant); + + _scansWrap = inner->add(object_ptr(inner)); + for (const auto &scan : _files) { + pushScan(scan); + _scans.back()->show(anim::type::instant); + } + + _scansUpload = inner->add( + object_ptr( + inner, + uploadButtonText(), + st::passportUploadButton), + st::passportUploadButtonPadding); + _scansUpload->addClickHandler([=] { + chooseScan(); + }); + + inner->add(object_ptr( + inner, + st::passportFormDividerHeight)); + inner->add( + object_ptr( + inner, + lang(lng_passport_personal_details), + Ui::FlatLabel::InitType::Simple, + st::passportFormHeader), + st::passportDetailsHeaderPadding); + + const auto valueOrEmpty = [&](const QString &key) { + if (const auto i = data.fields.find(key); i != data.fields.end()) { + return i->second; + } + return QString(); + }; + + _firstName = inner->add(object_ptr( + inner, + lang(lng_passport_first_name), + valueOrEmpty("first_name")))->field(); + _lastName = inner->add(object_ptr( + inner, + lang(lng_passport_last_name), + valueOrEmpty("last_name")))->field(); + + return inner; +} + +void PanelEditIdentity::updateScan(ScanInfo &&info) { + const auto i = ranges::find(_files, info.key, [](const ScanInfo &file) { + return file.key; + }); + if (i != _files.end()) { + *i = info; + const auto scan = _scans[i - _files.begin()]->entity(); + scan->setStatus(i->status); + scan->setImage(i->thumb); + scan->setDeleted(i->deleted); + } else { + _files.push_back(std::move(info)); + pushScan(_files.back()); + _scansWrap->resizeToWidth(width()); + _scans.back()->show(anim::type::normal); + _scansDivider->hide(anim::type::normal); + _scansHeader->show(anim::type::normal); + } +} + +void PanelEditIdentity::pushScan(const ScanInfo &info) { + const auto index = _scans.size(); + _scans.push_back(base::unique_qptr>( + _scansWrap->add(object_ptr>( + _scansWrap, + object_ptr( + _scansWrap, + st::passportScanRow, + lng_passport_scan_index(lt_index, QString::number(index + 1)), + info.status, + info.deleted))))); + _scans.back()->hide(anim::type::instant); + const auto scan = _scans.back()->entity(); + scan->setImage(info.thumb); + + scan->deleteClicks( + ) | rpl::start_with_next([=] { + _controller->deleteScan(_valueIndex, index); + }, scan->lifetime()); + + scan->restoreClicks( + ) | rpl::start_with_next([=] { + _controller->restoreScan(_valueIndex, index); + }, scan->lifetime()); +} + +void PanelEditIdentity::focusInEvent(QFocusEvent *e) { + _firstName->setFocusFast(); +} + +void PanelEditIdentity::resizeEvent(QResizeEvent *e) { + updateControlsGeometry(); +} + +void PanelEditIdentity::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->resizeToWidth(width()); + _done->moveToLeft(0, submitTop); + + _scroll->updateBars(); +} + +void PanelEditIdentity::chooseScan() { + const auto filter = FileDialog::AllFilesFilter() + + qsl(";;Image files (*") + + cImgExtensions().join(qsl(" *")) + + qsl(")"); + const auto callback = [=](FileDialog::OpenResult &&result) { + if (result.paths.size() == 1) { + encryptScan(result.paths.front()); + } else if (!result.remoteContent.isEmpty()) { + encryptScanContent(std::move(result.remoteContent)); + } + }; + FileDialog::GetOpenPath( + lang(lng_passport_choose_image), + filter, + base::lambda_guarded(this, callback)); +} + +void PanelEditIdentity::encryptScan(const QString &path) { + encryptScanContent([&] { + QFile f(path); + if (!f.open(QIODevice::ReadOnly)) { + return QByteArray(); + } + return f.readAll(); + }()); +} + +void PanelEditIdentity::encryptScanContent(QByteArray &&content) { + _controller->uploadScan(_valueIndex, std::move(content)); +} + +void PanelEditIdentity::save() { + auto data = ValueMap(); + data.fields["first_name"] = _firstName->getLastText(); + data.fields["last_name"] = _lastName->getLastText(); + _controller->saveValue(_valueIndex, std::move(data)); +} + +rpl::producer PanelEditIdentity::uploadButtonText() const { + return Lang::Viewer(_files.empty() + ? lng_passport_upload_scans + : lng_passport_upload_more) | Info::Profile::ToUpperValue(); +} + +} // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_identity.h b/Telegram/SourceFiles/passport/passport_panel_edit_identity.h new file mode 100644 index 000000000..24a0ead0c --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_panel_edit_identity.h @@ -0,0 +1,88 @@ +/* +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" + +class BoxContentDivider; + +namespace Ui { +class InputField; +class VerticalLayout; +class ScrollArea; +class FadeShadow; +class PlainShadow; +class FlatLabel; +class RoundButton; +template +class SlideWrap; +} // namespace Ui + +namespace Info { +namespace Profile { +class Button; +} // namespace Profile +} // namespace Info + +namespace Passport { + +class PanelController; +struct ValueMap; +struct ScanInfo; +class ScanButton; + +class PanelEditIdentity : public Ui::RpWidget { +public: + PanelEditIdentity( + QWidget *parent, + not_null controller, + int valueIndex, + const ValueMap &data, + std::vector &&files); + +protected: + void focusInEvent(QFocusEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + +private: + void setupControls(const ValueMap &data); + not_null setupContent(const ValueMap &data); + void updateControlsGeometry(); + + void chooseScan(); + void encryptScan(const QString &path); + void encryptScanContent(QByteArray &&content); + void updateScan(ScanInfo &&info); + void pushScan(const ScanInfo &info); + + rpl::producer uploadButtonText() const; + void save(); + + not_null _controller; + int _valueIndex = -1; + + std::vector _files; + + object_ptr _scroll; + object_ptr _topShadow; + object_ptr _bottomShadow; + + QPointer> _scansDivider; + QPointer> _scansHeader; + QPointer _scansWrap; + std::vector>> _scans; + QPointer _scansUpload; + + QPointer _firstName; + QPointer _lastName; + + object_ptr _done; + +}; + +} // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_panel_form.cpp b/Telegram/SourceFiles/passport/passport_panel_form.cpp new file mode 100644 index 000000000..899c4d785 --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_panel_form.cpp @@ -0,0 +1,266 @@ +/* +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_panel_form.h" + +#include "passport/passport_panel_controller.h" +#include "lang/lang_keys.h" +#include "boxes/abstract_box.h" +#include "ui/widgets/shadow.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/scroll_area.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/wrap/fade_wrap.h" +#include "ui/wrap/padding_wrap.h" +#include "ui/text_options.h" +#include "ui/special_buttons.h" +#include "styles/style_passport.h" +#include "styles/style_boxes.h" + +namespace Passport { + +class PanelForm::Row : public Ui::RippleButton { +public: + Row( + 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; + bool _ready = false; + +}; + +PanelForm::Row::Row( + 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 PanelForm::Row::setReady(bool ready) { + _ready = ready; + 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(); +} + +int PanelForm::Row::countAvailableWidth() const { + return countAvailableWidth(width()); +} + +void PanelForm::Row::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(); + + p.setPen(st::passportRowTitleFg); + _title.drawLeft(p, left, top, availableWidth, width()); + top += _titleHeight + st::passportRowSkip; + + p.setPen(st::passportRowDescriptionFg); + _description.drawLeft(p, left, top, availableWidth, width()); + top += _descriptionHeight + st::passportRowPadding.bottom(); + + const auto &icon = _ready + ? st::passportRowReadyIcon + : st::passportRowEmptyIcon; + icon.paint( + p, + width() - st::passportRowPadding.right() - icon.width(), + (height() - icon.height()) / 2, + width()); +} + +PanelForm::PanelForm( + QWidget *parent, + not_null controller) +: RpWidget(parent) +, _controller(controller) +, _scroll(this, st::passportPanelScroll) +, _topShadow(this) +, _bottomShadow(this) +, _submit( + this, + langFactory(lng_passport_authorize), + st::passportPanelAuthorize) { + setupControls(); +} + +void PanelForm::setupControls() { + const auto inner = setupContent(); + + 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 PanelForm::setupContent() { + const auto bot = _controller->bot(); + + const auto inner = _scroll->setOwnedWidget( + object_ptr(this)); + _scroll->widthValue( + ) | rpl::start_with_next([=](int width) { + inner->resizeToWidth(width); + }, inner->lifetime()); + + const auto userpicWrap = inner->add( + object_ptr( + inner, + st::passportFormUserpic.size.height()), + st::passportFormUserpicPadding); + _userpic = Ui::AttachParentChild( + userpicWrap, + object_ptr( + userpicWrap, + bot, + Ui::UserpicButton::Role::Custom, + st::passportFormUserpic)); + userpicWrap->widthValue( + ) | rpl::start_with_next([=](int width) { + _userpic->move((width - _userpic->width()) / 2, _userpic->y()); + }, _userpic->lifetime()); + + auto about1 = object_ptr( + inner, + lng_passport_request1(lt_bot, App::peerName(bot)), + Ui::FlatLabel::InitType::Simple, + st::passportPasswordLabelBold); + _about1 = about1.data(); + inner->add( + object_ptr(inner, std::move(about1)), + st::passportFormAbout1Padding); + + auto about2 = object_ptr( + inner, + lang(lng_passport_request2), + Ui::FlatLabel::InitType::Simple, + st::passportPasswordLabel); + _about2 = about2.data(); + inner->add( + object_ptr(inner, std::move(about2)), + st::passportFormAbout2Padding); + + inner->add(object_ptr( + inner, + st::passportFormDividerHeight)); + inner->add( + object_ptr( + inner, + lang(lng_passport_header), + Ui::FlatLabel::InitType::Simple, + st::passportFormHeader), + st::passportFormHeaderPadding); + + auto index = 0; + _controller->fillRows([&]( + QString title, + QString description, + bool ready) { + _rows.push_back(inner->add(object_ptr( + this, + title, + description))); + _rows.back()->addClickHandler([=] { + _controller->editValue(index); + }); + _rows.back()->setReady(ready); + }); + const auto policy = inner->add( + object_ptr( + inner, + lng_passport_accept_allow( + lt_policy, + textcmdLink( + 1, + lng_passport_policy(lt_bot, App::peerName(bot))), + lt_bot, + '@' + bot->username), + Ui::FlatLabel::InitType::Rich, + st::passportFormPolicy), + st::passportFormPolicyPadding); + policy->setLink(1, std::make_shared([=] { + _controller->cancelAuth(); + // #TODO passport policy + })); + + return inner; +} + +void PanelForm::resizeEvent(QResizeEvent *e) { + updateControlsGeometry(); +} + +void PanelForm::updateControlsGeometry() { + const auto submitTop = height() - _submit->height(); + _scroll->setGeometry(0, 0, width(), submitTop); + _topShadow->resizeToWidth(width()); + _topShadow->moveToLeft(0, 0); + _bottomShadow->resizeToWidth(width()); + _bottomShadow->moveToLeft(0, submitTop - st::lineWidth); + _submit->resizeToWidth(width()); + _submit->moveToLeft(0, submitTop); + + _scroll->updateBars(); +} + +} // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_panel_form.h b/Telegram/SourceFiles/passport/passport_panel_form.h new file mode 100644 index 000000000..f13caf38a --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_panel_form.h @@ -0,0 +1,56 @@ +/* +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" + +class BoxContentDivider; + +namespace Ui { +class ScrollArea; +class FadeShadow; +class RoundButton; +class FlatLabel; +class UserpicButton; +} // namespace Ui + +namespace Passport { + +class PanelController; + +class PanelForm : public Ui::RpWidget { +public: + PanelForm( + QWidget *parent, + not_null controller); + +protected: + void resizeEvent(QResizeEvent *e) override; + +private: + class Row; + + void setupControls(); + not_null setupContent(); + void updateControlsGeometry(); + + not_null _controller; + + object_ptr _scroll; + object_ptr _topShadow; + object_ptr _bottomShadow; + object_ptr _submit; + + QPointer _userpic; + QPointer _about1; + QPointer _about2; + std::vector> _rows; + +}; + +} // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_panel_password.cpp b/Telegram/SourceFiles/passport/passport_panel_password.cpp new file mode 100644 index 000000000..c852c3965 --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_panel_password.cpp @@ -0,0 +1,163 @@ +/* +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_panel_password.h" + +#include "passport/passport_panel_controller.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" +#include "ui/special_buttons.h" +#include "lang/lang_keys.h" +#include "styles/style_passport.h" +#include "styles/style_boxes.h" + +namespace Passport { + +PanelAskPassword::PanelAskPassword( + QWidget *parent, + not_null controller) +: RpWidget(parent) +, _controller(controller) +, _userpic( + this, + _controller->bot(), + Ui::UserpicButton::Role::Custom, + st::passportPasswordUserpic) +, _about1( + this, + lng_passport_request1(lt_bot, App::peerName(_controller->bot())), + Ui::FlatLabel::InitType::Simple, + st::passportPasswordLabelBold) +, _about2( + this, + lang(lng_passport_request2), + Ui::FlatLabel::InitType::Simple, + st::passportPasswordLabel) +, _password( + this, + st::defaultInputField, + langFactory(lng_passport_password_placeholder)) +, _submit(this, langFactory(lng_passport_next), st::passportPasswordSubmit) +, _forgot(this, lang(lng_signin_recover), st::defaultLinkButton) { + 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()); + + _password->setFocusFast(); + _userpic->setAttribute(Qt::WA_TransparentForMouseEvents); + + _submit->addClickHandler([=] { + submit(); + }); +} + +void PanelAskPassword::showError(const QString &error) { + _password->showError(); + _error.create( + this, + error, + Ui::FlatLabel::InitType::Simple, + st::passportErrorLabel); + _error->show(); + updateControlsGeometry(); +} + +void PanelAskPassword::hideError() { + _error.destroy(); +} + +void PanelAskPassword::submit() { + _controller->submitPassword(_password->getLastText()); +} + +void PanelAskPassword::setInnerFocus() { + _password->setFocusFast(); +} + +void PanelAskPassword::resizeEvent(QResizeEvent *e) { + updateControlsGeometry(); +} + +void PanelAskPassword::focusInEvent(QFocusEvent *e) { + _password->setFocusFast(); +} + +void PanelAskPassword::updateControlsGeometry() { + const auto padding = st::passportPasswordPadding; + const auto availableWidth = width() + - st::boxPadding.left() + - st::boxPadding.right(); + + auto top = st::passportPasswordFieldBottom; + top -= _password->height(); + _password->resize( + st::passportPasswordSubmit.width, + _password->height()); + _password->moveToLeft((width() - _password->width()) / 2, top); + + top -= st::passportPasswordFieldSkip + _about2->height(); + _about2->resizeToWidth(availableWidth); + _about2->moveToLeft(padding.left(), top); + + top -= _about1->height(); + _about1->resizeToWidth(availableWidth); + _about1->moveToLeft(padding.left(), top); + + top -= st::passportPasswordUserpicSkip + _userpic->height(); + _userpic->moveToLeft((width() - _userpic->width()) / 2, top); + + top = st::passportPasswordFieldBottom; + if (_hint) { + top += st::passportPasswordHintSkip; + _hint->resizeToWidth(availableWidth); + _hint->moveToLeft(padding.left(), top); + top += _hint->height(); + } + if (_error) { + top += st::passportPasswordHintSkip; + _error->resizeToWidth(availableWidth); + _error->moveToLeft(padding.left(), top); + top += _error->height(); + } + + top = height() - st::passportPasswordSubmitBottom - _submit->height(); + _submit->moveToLeft((width() - _submit->width()) / 2, top); + + top = height() - st::passportPasswordForgotBottom - _forgot->height(); + _forgot->moveToLeft((width() - _forgot->width()) / 2, top); +} + +PanelNoPassword::PanelNoPassword( + QWidget *parent, + not_null controller) +: RpWidget(parent) +, _controller(controller) { +} + +PanelPasswordUnconfirmed::PanelPasswordUnconfirmed( + QWidget *parent, + not_null controller) +: RpWidget(parent) +, _controller(controller) { +} + +} // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_panel_password.h b/Telegram/SourceFiles/passport/passport_panel_password.h new file mode 100644 index 000000000..90672d100 --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_panel_password.h @@ -0,0 +1,77 @@ +/* +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" + +namespace Ui { +class PasswordInput; +class FlatLabel; +class LinkButton; +class RoundButton; +class UserpicButton; +} // namespace Ui + +namespace Passport { + +class PanelController; + +class PanelAskPassword : public Ui::RpWidget { +public: + PanelAskPassword( + QWidget *parent, + not_null controller); + + void setInnerFocus(); + void submit(); + +protected: + void resizeEvent(QResizeEvent *e) override; + void focusInEvent(QFocusEvent *e) override; + +private: + void updateControlsGeometry(); + void showError(const QString &error); + void hideError(); + + not_null _controller; + + object_ptr _userpic; + object_ptr _about1; + object_ptr _about2; + object_ptr _password; + object_ptr _hint = { nullptr }; + object_ptr _error = { nullptr }; + object_ptr _submit; + object_ptr _forgot; + +}; + +class PanelNoPassword : public Ui::RpWidget { +public: + PanelNoPassword( + QWidget *parent, + not_null controller); + +private: + not_null _controller; + +}; + +class PanelPasswordUnconfirmed : public Ui::RpWidget { +public: + PanelPasswordUnconfirmed( + QWidget *parent, + not_null controller); + +private: + not_null _controller; + +}; + +} // namespace Passport diff --git a/Telegram/SourceFiles/ui/special_buttons.cpp b/Telegram/SourceFiles/ui/special_buttons.cpp index dbc33c63c..966244264 100644 --- a/Telegram/SourceFiles/ui/special_buttons.cpp +++ b/Telegram/SourceFiles/ui/special_buttons.cpp @@ -416,6 +416,22 @@ UserpicButton::UserpicButton( setupPeerViewers(); } +UserpicButton::UserpicButton( + QWidget *parent, + not_null peer, + Role role, + const style::UserpicButton &st) +: RippleButton(parent, st.changeButton.ripple) +, _st(st) +, _peer(peer) +, _peerForCrop(_peer->id) +, _role(role) { + Expects(_role == Role::Custom); + + _waiting = false; + prepare(); +} + void UserpicButton::prepare() { resize(_st.size); _notShownYet = _waiting; diff --git a/Telegram/SourceFiles/ui/special_buttons.h b/Telegram/SourceFiles/ui/special_buttons.h index c6badf860..a373d8467 100644 --- a/Telegram/SourceFiles/ui/special_buttons.h +++ b/Telegram/SourceFiles/ui/special_buttons.h @@ -162,6 +162,11 @@ public: not_null peer, Role role, const style::UserpicButton &st); + UserpicButton( + QWidget *parent, + not_null peer, + Role role, + const style::UserpicButton &st); void switchChangePhotoOverlay(bool enabled); void showSavedMessagesOnSelf(bool enabled); diff --git a/Telegram/SourceFiles/ui/widgets/scroll_area.cpp b/Telegram/SourceFiles/ui/widgets/scroll_area.cpp index a82494f81..718b14fb6 100644 --- a/Telegram/SourceFiles/ui/widgets/scroll_area.cpp +++ b/Telegram/SourceFiles/ui/widgets/scroll_area.cpp @@ -785,8 +785,8 @@ void ScrollArea::rangeChanged(int oldMax, int newMax, bool vertical) { } void ScrollArea::updateBars() { - _horizontalBar->update(); - _verticalBar->update(); + _horizontalBar->updateBar(true); + _verticalBar->updateBar(true); } bool ScrollArea::focusNextPrevChild(bool next) { diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index 1ed7c6ae0..f3a510061 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -525,6 +525,18 @@ InfoProfileButton { ripple: RippleAnimation; } +PassportScanRow { + padding: margins; + size: pixels; + textLeft: pixels; + nameTop: pixels; + statusTop: pixels; + border: pixels; + borderFg: color; + remove: IconButton; + restore: RoundButton; +} + defaultLabelSimple: LabelSimple { font: normalFont; maxWidth: 0px; diff --git a/Telegram/SourceFiles/ui/wrap/wrap.h b/Telegram/SourceFiles/ui/wrap/wrap.h index 221dae1a3..be855168f 100644 --- a/Telegram/SourceFiles/ui/wrap/wrap.h +++ b/Telegram/SourceFiles/ui/wrap/wrap.h @@ -183,4 +183,24 @@ private: }; +class IgnoreNaturalWidth : public Wrap { + using Parent = Wrap; + +public: + IgnoreNaturalWidth(QWidget *parent, object_ptr &&child) + : Parent(parent, std::move(child)) { + if (auto weak = wrapped()) { + auto margins = weak->getMargins(); + resizeToWidth(weak->width() + - margins.left() + - margins.right()); + } + } + + int naturalWidth() const override { + return -1; + } + +}; + } // namespace Ui diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp index c06e5bcb9..9abc21f76 100644 --- a/Telegram/SourceFiles/window/window_controller.cpp +++ b/Telegram/SourceFiles/window/window_controller.cpp @@ -410,6 +410,10 @@ void Controller::showAuthForm(const Passport::FormRequest &request) { _authForm->show(); } +void Controller::clearAuthForm() { + _authForm = nullptr; +} + void Controller::updateColumnLayout() { App::main()->updateColumnLayout(); } diff --git a/Telegram/SourceFiles/window/window_controller.h b/Telegram/SourceFiles/window/window_controller.h index c33e83430..eeae7ae22 100644 --- a/Telegram/SourceFiles/window/window_controller.h +++ b/Telegram/SourceFiles/window/window_controller.h @@ -206,6 +206,7 @@ public: QDate requestedDate); void showAuthForm(const Passport::FormRequest &request); + void clearAuthForm(); base::Variable &dialogsListFocused() { return _dialogsListFocused; diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 24b43e4d8..ad4df532d 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -454,19 +454,23 @@ <(src_loc)/mtproto/type_utils.h <(src_loc)/overview/overview_layout.cpp <(src_loc)/overview/overview_layout.h -<(src_loc)/passport/passport_edit_identity_box.cpp -<(src_loc)/passport/passport_edit_identity_box.h <(src_loc)/passport/passport_encryption.cpp <(src_loc)/passport/passport_encryption.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)/passport/passport_form_view_controller.h -<(src_loc)/passport/passport_form_view_separate.cpp -<(src_loc)/passport/passport_form_view_separate.h +<(src_loc)/passport/passport_panel.cpp +<(src_loc)/passport/passport_panel.h +<(src_loc)/passport/passport_panel_controller.cpp +<(src_loc)/passport/passport_panel_controller.h +<(src_loc)/passport/passport_panel_details_row.cpp +<(src_loc)/passport/passport_panel_details_row.h +<(src_loc)/passport/passport_panel_edit_identity.cpp +<(src_loc)/passport/passport_panel_edit_identity.h +<(src_loc)/passport/passport_panel_form.cpp +<(src_loc)/passport/passport_panel_form.h +<(src_loc)/passport/passport_panel_password.cpp +<(src_loc)/passport/passport_panel_password.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