diff --git a/Telegram/Resources/colors.palette b/Telegram/Resources/colors.palette index 033868bc1..9a1c2db92 100644 --- a/Telegram/Resources/colors.palette +++ b/Telegram/Resources/colors.palette @@ -125,8 +125,10 @@ notificationSampleTextFg: #d7d7d7 | windowSubTextFg; notificationSampleNameFg: #939393 | windowSubTextFg; // intro -introHeaderFg: windowFg; -introErrorFg: windowFg; +introBg: windowBg; +introTitleFg: windowBoldFg; +introDescriptionFg: windowSubTextFg; +introErrorFg: windowSubTextFg; // dialogs dialogsMenuIconFg: menuIconFg; diff --git a/Telegram/Resources/icons/intro_country_dropdown.png b/Telegram/Resources/icons/intro_country_dropdown.png new file mode 100644 index 000000000..e65ed5a5a Binary files /dev/null and b/Telegram/Resources/icons/intro_country_dropdown.png differ diff --git a/Telegram/Resources/icons/intro_country_dropdown@2x.png b/Telegram/Resources/icons/intro_country_dropdown@2x.png new file mode 100644 index 000000000..712294a4b Binary files /dev/null and b/Telegram/Resources/icons/intro_country_dropdown@2x.png differ diff --git a/Telegram/Resources/icons/intro_left.png b/Telegram/Resources/icons/intro_left.png new file mode 100644 index 000000000..4a8dcf3c0 Binary files /dev/null and b/Telegram/Resources/icons/intro_left.png differ diff --git a/Telegram/Resources/icons/intro_left@2x.png b/Telegram/Resources/icons/intro_left@2x.png new file mode 100644 index 000000000..6e7ffbcdf Binary files /dev/null and b/Telegram/Resources/icons/intro_left@2x.png differ diff --git a/Telegram/Resources/icons/intro_logo.png b/Telegram/Resources/icons/intro_logo.png deleted file mode 100644 index 4953b87d5..000000000 Binary files a/Telegram/Resources/icons/intro_logo.png and /dev/null differ diff --git a/Telegram/Resources/icons/intro_logo@2x.png b/Telegram/Resources/icons/intro_logo@2x.png deleted file mode 100644 index c43416ba2..000000000 Binary files a/Telegram/Resources/icons/intro_logo@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/intro_plane_inner.png b/Telegram/Resources/icons/intro_plane_inner.png new file mode 100644 index 000000000..cbebb1110 Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_inner.png differ diff --git a/Telegram/Resources/icons/intro_plane_inner@2x.png b/Telegram/Resources/icons/intro_plane_inner@2x.png new file mode 100644 index 000000000..11300ea1f Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_inner@2x.png differ diff --git a/Telegram/Resources/icons/intro_plane_outer.png b/Telegram/Resources/icons/intro_plane_outer.png new file mode 100644 index 000000000..6cd6308b6 Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_outer.png differ diff --git a/Telegram/Resources/icons/intro_plane_outer@2x.png b/Telegram/Resources/icons/intro_plane_outer@2x.png new file mode 100644 index 000000000..3d7b9f56e Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_outer@2x.png differ diff --git a/Telegram/Resources/icons/intro_plane_top.png b/Telegram/Resources/icons/intro_plane_top.png new file mode 100644 index 000000000..aa2e2bb75 Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_top.png differ diff --git a/Telegram/Resources/icons/intro_plane_top@2x.png b/Telegram/Resources/icons/intro_plane_top@2x.png new file mode 100644 index 000000000..f2e80eb23 Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_top@2x.png differ diff --git a/Telegram/Resources/icons/intro_plane_trace.png b/Telegram/Resources/icons/intro_plane_trace.png new file mode 100644 index 000000000..172485c2f Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_trace.png differ diff --git a/Telegram/Resources/icons/intro_plane_trace@2x.png b/Telegram/Resources/icons/intro_plane_trace@2x.png new file mode 100644 index 000000000..9d16a3197 Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_trace@2x.png differ diff --git a/Telegram/Resources/icons/intro_right.png b/Telegram/Resources/icons/intro_right.png new file mode 100644 index 000000000..511a3989f Binary files /dev/null and b/Telegram/Resources/icons/intro_right.png differ diff --git a/Telegram/Resources/icons/intro_right@2x.png b/Telegram/Resources/icons/intro_right@2x.png new file mode 100644 index 000000000..3a40127ae Binary files /dev/null and b/Telegram/Resources/icons/intro_right@2x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 9afd94ed4..49a02ec08 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -138,7 +138,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_pinned_unpin" = "Unpin"; "lng_pinned_notify" = "Notify all members"; -"lng_intro" = "Welcome to the official [a href=\"https://telegram.org/\"]Telegram[/a] desktop app.\nIt's [b]fast[/b] and [b]secure[/b]."; +"lng_intro_about" = "Welcome to the official Telegram Desktop app.\nIt's fast and secure."; "lng_start_msgs" = "START MESSAGING"; "lng_intro_next" = "NEXT"; @@ -150,7 +150,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_phone_ph" = "Your phone number"; "lng_phone_title" = "Your Phone"; "lng_phone_desc" = "Please confirm your country code and\nenter your phone number."; -"lng_phone_notreg" = "Note: if you don't have a Telegram account yet,\nplease [b]sign up[/b] with your [a href=\"https://telegram.org/\"]iOS / Android[/a] or {signup_start}here ยป{signup_end}"; +"lng_phone_notreg" = "If you don't have a Telegram account yet,\nplease [b]sign up[/b] with {link_start}Android / iPhone{link_end} or {signup_start}here{signup_end}"; "lng_country_code" = "Country Code"; "lng_bad_country_code" = "Invalid Country Code"; "lng_country_ph" = "Search"; @@ -160,7 +160,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_code_ph" = "Your code"; "lng_code_desc" = "We have sent you a message with activation\ncode to your phone. Please enter it below."; -"lng_code_telegram" = "Please enter the code you've just\nreceived in your previous [b]Telegram[/b] app."; +"lng_code_telegram" = "Please enter the code you've just received\nin your previous [b]Telegram[/b] app."; "lng_code_no_telegram" = "Send code via SMS"; "lng_code_call" = "Telegram will dial your number in {minutes}:{seconds}"; "lng_code_calling" = "Requesting a call from Telegram..."; @@ -174,7 +174,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_title" = "Cloud password check"; "lng_signin_desc" = "Please enter your cloud password."; -"lng_signin_recover_desc" = "Please enter the code from the e-mail."; +"lng_signin_recover_desc" = "Please enter the code from the e-mail\n{email}"; "lng_signin_password" = "Your cloud password"; "lng_signin_code" = "Code from e-mail"; "lng_signin_recover" = "Forgot password?"; @@ -196,7 +196,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_in_minutes" = "{count_minutes:0 minutes|# minute|# minutes}"; "lng_signin_reset_cancelled" = "Your recent attempts to reset this account have been cancelled by its active user. Please try again in 7 days."; -"lng_signup_title" = "Information and photo"; +"lng_signup_title" = "Your Info"; "lng_signup_desc" = "Please enter your name and\nupload a photo."; "lng_signup_firstname" = "First Name"; diff --git a/Telegram/Resources/sample.tdesktop-theme b/Telegram/Resources/sample.tdesktop-theme index fdcb3084b..4b410eda0 100644 --- a/Telegram/Resources/sample.tdesktop-theme +++ b/Telegram/Resources/sample.tdesktop-theme @@ -102,8 +102,10 @@ notificationSampleUserpicFg: windowBgActive; notificationSampleCloseFg: #d7d7d7; // windowSubTextFg; notificationSampleTextFg: #d7d7d7; // windowSubTextFg; notificationSampleNameFg: #939393; // windowSubTextFg; -introHeaderFg: windowFg; -introErrorFg: windowFg; +introBg: windowBg; +introTitleFg: windowBoldFg; +introDescriptionFg: windowSubTextFg; +introErrorFg: windowSubTextFg; dialogsMenuIconFg: menuIconFg; dialogsMenuIconFgOver: menuIconFgOver; dialogsBg: windowBg; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index a7eb94ef2..dcf84deeb 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -2546,7 +2546,7 @@ namespace { QImage readImage(const QString &file, QByteArray *format, bool opaque, bool *animated, QByteArray *content) { QFile f(file); - if (!f.open(QIODevice::ReadOnly)) { + if (f.size() > MediaViewImageSizeLimit || !f.open(QIODevice::ReadOnly)) { if (animated) *animated = false; return QImage(); } diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index 868b29926..5904a4ef4 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -252,7 +252,7 @@ void AddContactBox::onRetry() { GroupInfoBox::GroupInfoBox(CreatingGroupType creating, bool fromTypeChoose) : AbstractBox() , _creating(creating) -, _photo(this, st::newGroupPhotoSize) +, _photo(this, st::newGroupPhotoSize, st::newGroupPhotoIconPosition) , _title(this, st::defaultInputField, lang(_creating == CreatingGroupChannel ? lng_dlg_new_channel_name : lng_dlg_new_group_name)) , _description(this, st::newGroupDescription, lang(lng_create_group_description)) , _next(this, lang(_creating == CreatingGroupChannel ? lng_create_group_create : lng_create_group_next), st::defaultBoxButton) @@ -276,15 +276,14 @@ GroupInfoBox::GroupInfoBox(CreatingGroupType creating, bool fromTypeChoose) : Ab connect(_next, SIGNAL(clicked()), this, SLOT(onNext())); connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - subscribe(FileDialog::QueryDone(), [this](const FileDialog::QueryUpdate &update) { - notifyFileQueryUpdated(update); - }); - _photo->setClickedCallback([this] { auto imgExtensions = cImgExtensions(); auto filter = qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + filedialogAllFilesFilter(); _setPhotoFileQueryId = FileDialog::queryReadFile(lang(lng_choose_images), filter); }); + subscribe(FileDialog::QueryDone(), [this](const FileDialog::QueryUpdate &update) { + notifyFileQueryUpdated(update); + }); prepare(); } @@ -418,7 +417,7 @@ void GroupInfoBox::notifyFileQueryUpdated(const FileDialog::QueryUpdate &update) if (img.isNull() || img.width() > 10 * img.height() || img.height() > 10 * img.width()) { return; } - PhotoCropBox *box = new PhotoCropBox(img, (_creating == CreatingGroupChannel) ? peerFromChannel(0) : peerFromChat(0)); + auto box = new PhotoCropBox(img, (_creating == CreatingGroupChannel) ? peerFromChannel(0) : peerFromChat(0)); connect(box, SIGNAL(ready(const QImage&)), this, SLOT(onPhotoReady(const QImage&))); Ui::showLayer(box, KeepOtherLayers); } diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 0ad1d63d1..02cfd56f4 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -401,8 +401,7 @@ sessionTerminateAllButton: LinkButton(boxLinkButton) { passcodeHeaderFont: font(19px); passcodeHeaderHeight: 80px; -passcodeInput: FlatInput(introPhone) { -} +passcodeInput: introPhone; passcodeSubmit: RoundButton(introNextButton) { width: 225px; } @@ -420,8 +419,6 @@ newGroupLinkTop: 3px; newGroupLinkFont: font(16px); newGroupPhotoSize: 76px; -newGroupPhotoBg: #4eb5f0; -newGroupPhotoBgOver: #3fa9e7; newGroupPhotoIcon: icon {{ "new_chat_photo", #ffffff }}; newGroupPhotoIconPosition: point(23px, 25px); newGroupPhotoDuration: 150; diff --git a/Telegram/SourceFiles/boxes/photocropbox.cpp b/Telegram/SourceFiles/boxes/photocropbox.cpp index fadd7ef88..e5c2ab546 100644 --- a/Telegram/SourceFiles/boxes/photocropbox.cpp +++ b/Telegram/SourceFiles/boxes/photocropbox.cpp @@ -65,6 +65,10 @@ void PhotoCropBox::init(const QImage &img, PeerData *peer) { int32 s = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); _thumb = App::pixmapFromImageInPlace(img.scaled(s * cIntRetinaFactor(), s * cIntRetinaFactor(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); _thumb.setDevicePixelRatio(cRetinaFactor()); + _mask = QImage(_thumb.size(), QImage::Format_ARGB32_Premultiplied); + _mask.setDevicePixelRatio(cRetinaFactor()); + _fade = QImage(_thumb.size(), QImage::Format_ARGB32_Premultiplied); + _fade.setDevicePixelRatio(cRetinaFactor()); _thumbw = _thumb.width() / cIntRetinaFactor(); _thumbh = _thumb.height() / cIntRetinaFactor(); if (_thumbw > _thumbh) { @@ -238,18 +242,16 @@ void PhotoCropBox::paintEvent(QPaintEvent *e) { p.translate(_thumbx, _thumby); p.drawPixmap(0, 0, _thumb); - if (_cropy > 0) { - p.fillRect(QRect(0, 0, _cropx + _cropw, _cropy), st::photoCropFadeBg); - } - if (_cropx + _cropw < _thumbw) { - p.fillRect(QRect(_cropx + _cropw, 0, _thumbw - _cropx - _cropw, _cropy + _cropw), st::photoCropFadeBg); - } - if (_cropy + _cropw < _thumbh) { - p.fillRect(QRect(_cropx, _cropy + _cropw, _thumbw - _cropx, _thumbh - _cropy - _cropw), st::photoCropFadeBg); - } - if (_cropx > 0) { - p.fillRect(QRect(0, _cropy, _cropx, _thumbh - _cropy), st::photoCropFadeBg); + _mask.fill(Qt::white); + { + Painter p(&_mask); + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.setPen(Qt::NoPen); + p.setBrush(Qt::black); + p.drawEllipse(_cropx, _cropy, _cropw, _cropw); } + style::colorizeImage(_mask, st::photoCropFadeBg->c, &_fade); + p.drawImage(0, 0, _fade); int delta = st::cropPointSize; int mdelta = -delta / 2; diff --git a/Telegram/SourceFiles/boxes/photocropbox.h b/Telegram/SourceFiles/boxes/photocropbox.h index 4e73cdf21..d6eb2a6bd 100644 --- a/Telegram/SourceFiles/boxes/photocropbox.h +++ b/Telegram/SourceFiles/boxes/photocropbox.h @@ -62,6 +62,7 @@ private: ChildWidget _cancel; QImage _img; QPixmap _thumb; + QImage _mask, _fade; PeerId _peerId; }; diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index bbe6c7530..fcf0a8509 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -34,6 +34,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" #include "ui/effects/ripple_animation.h" +#include "ui/effects/slide_animation.h" #include "ui/widgets/discrete_sliders.h" namespace { @@ -338,37 +339,12 @@ void StickersBox::paintEvent(QPaintEvent *e) { _about.draw(p, st::stickersReorderPadding.top(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center); } - if (!_leftCache.isNull()) { - auto slide = _a_slide.current(getms(), _slideLeft ? 0. : 1.); - if (!_a_slide.animating()) { - _leftCache = _rightCache = QPixmap(); + if (_slideAnimation) { + _slideAnimation->paintFrame(p, scrollArea()->x(), scrollArea()->y() - titleHeight(), width(), getms()); + if (!_slideAnimation->animating()) { + _slideAnimation.reset(); scrollArea()->show(); update(); - } else { - auto easeOut = anim::easeOutCirc(1., slide); - auto easeIn = anim::easeInCirc(1., slide); - auto cacheWidth = (_leftCache.width() / cIntRetinaFactor()); - auto arrivingCoord = anim::interpolate(cacheWidth, 0, easeOut); - auto departingCoord = anim::interpolate(0, cacheWidth, easeIn); - auto arrivingAlpha = easeIn; - auto departingAlpha = 1. - easeOut; - auto leftCoord = (_slideLeft ? arrivingCoord : departingCoord) * -1; - auto leftAlpha = (_slideLeft ? arrivingAlpha : departingAlpha); - auto rightCoord = (_slideLeft ? departingCoord : arrivingCoord); - auto rightAlpha = (_slideLeft ? departingAlpha : arrivingAlpha); - - auto x = scrollArea()->x(); - auto y = scrollArea()->y() - titleHeight(); - auto leftWidth = (cacheWidth + leftCoord); - if (leftWidth > 0) { - p.setOpacity(leftAlpha); - p.drawPixmap(x, y, leftWidth, _leftCache.height() / cIntRetinaFactor(), _leftCache, (_leftCache.width() - leftWidth * cIntRetinaFactor()), 0, leftWidth * cIntRetinaFactor(), _leftCache.height()); - } - auto rightWidth = cacheWidth - rightCoord; - if (rightWidth > 0) { - p.setOpacity(rightAlpha); - p.drawPixmap(x + rightCoord, y, _rightCache, 0, 0, rightWidth * cIntRetinaFactor(), _rightCache.height()); - } } } } @@ -430,14 +406,11 @@ void StickersBox::switchTab() { auto nowCache = grabContentCache(); auto nowIndex = _tab->index; - _leftCache = std_::move(wasCache); - _rightCache = std_::move(nowCache); - _slideLeft = (wasIndex > nowIndex); - if (_slideLeft) { - std_::swap_moveable(_leftCache, _rightCache); - } + _slideAnimation = std_::make_unique(); + _slideAnimation->setSnapshots(std_::move(wasCache), std_::move(nowCache)); + auto slideLeft = wasIndex > nowIndex; + _slideAnimation->start(slideLeft, [this] { update(); }, st::slideDuration); scrollArea()->hide(); - _a_slide.start([this] { update(); }, 0., 1., st::slideDuration); update(); } } @@ -602,6 +575,8 @@ void StickersBox::closePressed() { } } +StickersBox::~StickersBox() = default; + StickersBox::Inner::Inner(QWidget *parent, StickersBox::Section section) : TWidget(parent) , _section(section) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) diff --git a/Telegram/SourceFiles/boxes/stickers_box.h b/Telegram/SourceFiles/boxes/stickers_box.h index 1e60821eb..2100eea54 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.h +++ b/Telegram/SourceFiles/boxes/stickers_box.h @@ -33,6 +33,7 @@ class PlainShadow; class RoundButton; class RippleAnimation; class SettingsSlider; +class SlideAnimation; } // namespace Ui class StickersBox : public ItemListBox, public RPCSender { @@ -48,6 +49,8 @@ public: StickersBox(Section section = Section::Installed); StickersBox(const Stickers::Order &archivedIds); + ~StickersBox(); + public slots: void onStickersUpdated(); @@ -111,9 +114,7 @@ private: ChildWidget _done = { nullptr }; ChildWidget _bottomShadow = { nullptr }; - FloatAnimation _a_slide; - bool _slideLeft = false; - QPixmap _leftCache, _rightCache; + std_::unique_ptr _slideAnimation; QTimer _scrollTimer; int32 _scrollDelta = 0; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 2303eb237..d2a1440cc 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -516,8 +516,8 @@ void start() { SandboxData->LangSystemISO = psCurrentLanguage(); if (SandboxData->LangSystemISO.isEmpty()) SandboxData->LangSystemISO = qstr("en"); - QByteArray l = LangSystemISO().toLatin1(); - for (int32 i = 0; i < languageCount; ++i) { + auto l = LangSystemISO().toLatin1(); + for (auto i = 0; i < languageCount; ++i) { if (l.at(0) == LanguageCodes[i][0] && l.at(1) == LanguageCodes[i][1]) { SandboxData->LangSystem = i; break; diff --git a/Telegram/SourceFiles/intro/intro.style b/Telegram/SourceFiles/intro/intro.style index 35f5b1676..7c59f82a1 100644 --- a/Telegram/SourceFiles/intro/intro.style +++ b/Telegram/SourceFiles/intro/intro.style @@ -21,119 +21,135 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org using "basic.style"; using "ui/widgets/widgets.style"; -countryInput { - width: pixels; - height: pixels; - top: pixels; - bgColor: color; - ptrSize: size; - textMrg: margins; - font: font; - align: align; -} +introCoverHeight: 208px; +introCoverTopBg: #0f89d0; +introCoverBottomBg: #39b0f0; +introCoverIconsFg: #5ec6ff; +introCoverMaxWidth: 880px; +introCoverIconsMinSkip: 120px; +introCoverLeft: icon {{ "intro_left", introCoverIconsFg }}; +introCoverRight: icon {{ "intro_right", introCoverIconsFg }}; +introCoverIcon: icon { + { "intro_plane_trace", #5ec6ff69 }, + { "intro_plane_inner", #c6d8e8 }, + { "intro_plane_outer", #a1bed4 }, + { "intro_plane_top", #ffffff }, +}; +introCoverIconLeft: 50px; +introCoverIconTop: 46px; -introCountry: countryInput { - width: 300px; - height: 41px; - top: 33px; - bgColor: windowBgOver; - ptrSize: size(15px, 8px); - textMrg: margins(16px, 5px, 16px, 15px); - font: defaultInputFont; - align: align(left); -} +introSettingsSkip: 10px; -introIcon: icon {{ "intro_logo", #008ed5 }}; +introPhotoSize: 76px; +introPhotoIconPosition: point(23px, 25px); +introPhotoTop: 10px; -introResetLink: LinkButton(defaultLinkButton) { - color: #d15948; - overColor: #d15948; - downColor: #db6352; -} - -introBtnTop: 288px; -introSkip: 25px; -introFinishSkip: 15px; -introPhotoSize: 98px; -introHeaderFont: font(24px); -introHeaderSkip: 14px; -introIconSkip: 50px; -introFont: font(16px); -introLink: LinkButton(defaultLinkButton) { - font: introFont; - overFont: font(16px underline); -} -introLabel: FlatLabel(defaultFlatLabel) { - font: introFont; +introCoverTitle: FlatLabel(defaultFlatLabel) { + font: font(22px semibold); + textFg: introTitleFg; align: align(center); } +introCoverTitleTop: 126px; +introCoverDescription: FlatLabel(defaultFlatLabel) { + font: font(15px); + textFg: introDescriptionFg; + align: align(center); +} +introCoverDescriptionTextStyle: TextStyle(defaultTextStyle) { + lineHeight: 24px; +} +introCoverDescriptionTop: 164px; +introTitle: FlatLabel(defaultFlatLabel) { + font: font(17px semibold); + textFg: introTitleFg; +} +introTitleTop: 11px; +introDescription: FlatLabel(defaultFlatLabel) { + font: normalFont; + textFg: introDescriptionFg; +} +introDescriptionTextStyle: TextStyle(defaultTextStyle) { + lineHeight: 20px; +} +introDescriptionTop: 44px; -introStepSize: size(400px, 200px); -introSize: size(400px, 460px); -introSlideShift: 500px; // intro hiding animation +introLink: defaultLinkButton; + +introPlaneWidth: 48px; +introPlaneHeight: 38px; +introHeight: 396px; +introStepTopMin: 86px; +introStepWidth: 380px; +introStepHeight: 256px; +introStepHeightAdd: 30px; +introStepHeightFull: 580px; introSlideDuration: 200; -introSlideDelta: 0; // between hide start and show start -introTextTop: 22px; -introTextSize: size(400px, 93px); -introCallSkip: 15px; -introPwdTextSize: size(400px, 73px); +introCoverDuration: 200; introNextButton: RoundButton(defaultActiveButton) { width: 300px; height: 56px; - textTop: 16px; - - font: font(17px); + textTop: 17px; + font: font(17px semibold); } -introPhoneTop: 8px; -introCountryCode: FlatInput(defaultFlatInput) { - width: 70px; +introStepFieldTop: 116px; +introPhoneTop: 16px; +introLinkTop: 21px; +introCountry: InputField(defaultInputField) { + textMargins: margins(3px, 7px, 3px, 6px); + font: font(16px); + width: 300px; height: 41px; - align: align(center); } -introPhone: FlatInput(defaultFlatInput) { - textMrg: margins(12px, 5px, 12px, 6px); +introCountryCode: InputField(introCountry) { + width: 64px; + height: 41px; + textAlign: align(top); +} +introPhone: InputField(introCountry) { + textMargins: margins(12px, 7px, 12px, 6px); width: 225px; height: 41px; } -introCode: FlatInput(defaultFlatInput) { - textMrg: margins(12px, 5px, 12px, 6px); - width: 106px; - height: 41px; - align: align(center); +introCode: introCountry; +introName: introCountry; +introPassword: introCountry; +introPasswordTop: 94px; +introPasswordHintTop: 146px; - phPos: point(0px, 0px); - phAlign: align(center); - phShift: 0px; +introPasswordHint: FlatLabel(introDescription) { + textFg: windowFg; } -introName: FlatInput(introPhone) { - width: 192px; -} -introPassword: FlatInput(introPhone) { - width: 300px; +introPasswordHintTextStyle: introDescriptionTextStyle; + +introResetButton: RoundButton(defaultLightButton) { + textFg: attentionButtonFg; + textFgOver: attentionButtonFgOver; + textBgOver: attentionButtonBgOver; + + ripple: RippleAnimation(defaultRippleAnimation) { + color: attentionButtonBgRipple; + } } +introResetBottom: 20px; + +introCountryIcon: icon {{ "intro_country_dropdown", menuIconFg }}; +introCountryIconPosition: point(8px, 17px); introSelectDelta: 30px; -introErrorWidth: 450px; +introErrorTop: 225px; +introErrorBelowLinkTop: 213px; introErrorDuration: 200; -introErrorTop: 15px; -introErrorHeight: 40px; -introErrorFont: font(16px); -introLabelTextStyle: TextStyle(defaultTextStyle) { - lineHeight: 30px; -} -introErrorLabelTextStyle: TextStyle(defaultTextStyle) { - lineHeight: 27px; -} - -introErrorLabel: FlatLabel(defaultFlatLabel) { - font: introErrorFont; +introError: introDescription; +introErrorCentered: FlatLabel(introError) { align: align(center); } +introErrorTextStyle: introDescriptionTextStyle; + introBackButton: IconButton(defaultIconButton) { width: 56px; height: 56px; diff --git a/Telegram/SourceFiles/intro/introcode.cpp b/Telegram/SourceFiles/intro/introcode.cpp index 88b75a2c0..7c5fb184b 100644 --- a/Telegram/SourceFiles/intro/introcode.cpp +++ b/Telegram/SourceFiles/intro/introcode.cpp @@ -26,21 +26,28 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "intro/introsignup.h" #include "intro/intropwdcheck.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" #include "styles/style_intro.h" -CodeInput::CodeInput(QWidget *parent, const style::FlatInput &st, const QString &ph) : Ui::FlatInput(parent, st, ph) { +namespace Intro { + +CodeInput::CodeInput(QWidget *parent, const style::InputField &st, const QString &ph) : Ui::MaskedInputField(parent, st, ph) { } -void CodeInput::correctValue(const QString &was, QString &now) { +void CodeInput::setDigitsCountMax(int digitsCount) { + _digitsCountMax = digitsCount; +} + +void CodeInput::correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) { QString newText; - int oldPos(cursorPosition()), newPos(-1), oldLen(now.length()), digitCount = 0; + int oldPos(nowCursor), newPos(-1), oldLen(now.length()), digitCount = 0; for (int i = 0; i < oldLen; ++i) { if (now[i].isDigit()) { ++digitCount; } } - if (digitCount > 5) digitCount = 5; - bool strict = (digitCount == 5); + accumulate_min(digitCount, _digitsCountMax); + auto strict = (digitCount == _digitsCountMax); newText.reserve(oldLen); for (int i = 0; i < oldLen; ++i) { @@ -58,177 +65,131 @@ void CodeInput::correctValue(const QString &was, QString &now) { newPos = newText.length(); } } - if (newPos < 0) { - newPos = newText.length(); + if (newPos < 0 || newPos > newText.size()) { + newPos = newText.size(); } if (newText != now) { now = newText; setText(now); updatePlaceholder(); - if (newPos != oldPos) { - setCursorPosition(newPos); - } + } + if (newPos != nowCursor) { + nowCursor = newPos; + setCursorPosition(nowCursor); } if (strict) emit codeEntered(); } -IntroCode::IntroCode(IntroWidget *parent) : IntroStep(parent) -, a_errorAlpha(0) -, _a_error(animation(this, &IntroCode::step_error)) -, _next(this, lang(lng_intro_next), st::introNextButton) -, _desc(st::introTextSize.width()) +CodeWidget::CodeWidget(QWidget *parent, Widget::Data *data) : Step(parent, data) , _noTelegramCode(this, lang(lng_code_no_telegram), st::introLink) -, _noTelegramCodeRequestId(0) , _code(this, st::introCode, lang(lng_code_ph)) , _callTimer(this) -, _callStatus(intro()->getCallStatus()) +, _callStatus(getData()->callStatus) +, _callTimeout(getData()->callTimeout) +, _callLabel(this, st::introDescription, st::introDescriptionTextStyle) , _checkRequest(this) { - setGeometry(parent->innerRect()); - - connect(_next, SIGNAL(clicked()), this, SLOT(onSubmitCode())); connect(_code, SIGNAL(changed()), this, SLOT(onInputChange())); connect(_callTimer, SIGNAL(timeout()), this, SLOT(onSendCall())); connect(_checkRequest, SIGNAL(timeout()), this, SLOT(onCheckRequest())); connect(_noTelegramCode, SIGNAL(clicked()), this, SLOT(onNoTelegramCode())); - updateDescText(); + _code->setDigitsCountMax(getData()->codeLength); + setErrorBelowLink(true); - if (!intro()->codeByTelegram()) { - if (_callStatus.type == IntroWidget::CallWaiting) { - _callTimer->start(1000); - } - } + setTitleText(App::formatPhone(getData()->phone)); + updateDescText(); } -void IntroCode::updateDescText() { - _desc.setRichText(st::introFont, lang(intro()->codeByTelegram() ? lng_code_telegram : lng_code_desc)); - if (intro()->codeByTelegram()) { +void CodeWidget::updateDescText() { + setDescriptionText(lang(getData()->codeByTelegram ? lng_code_telegram : lng_code_desc)); + if (getData()->codeByTelegram) { _noTelegramCode->show(); _callTimer->stop(); } else { _noTelegramCode->hide(); - _callStatus = intro()->getCallStatus(); - if (_callStatus.type == IntroWidget::CallWaiting && !_callTimer->isActive()) { + _callStatus = getData()->callStatus; + _callTimeout = getData()->callTimeout; + if (_callStatus == Widget::Data::CallStatus::Waiting && !_callTimer->isActive()) { _callTimer->start(1000); } } - update(); + updateCallText(); } -void IntroCode::paintEvent(QPaintEvent *e) { - bool trivial = (rect() == e->rect()); - - QPainter p(this); - if (!trivial) { - p.setClipRect(e->rect()); - } - bool codeByTelegram = intro()->codeByTelegram(); - if (trivial || e->rect().intersects(_textRect)) { - p.setFont(st::introHeaderFont->f); - p.drawText(_textRect, intro()->getPhone(), style::al_top); - p.setFont(st::introFont->f); - _desc.draw(p, _textRect.x(), _textRect.y() + _textRect.height() - 2 * st::introFont->height, _textRect.width(), style::al_top); - } - if (codeByTelegram) { - } else { - QString callText; - switch (_callStatus.type) { - case IntroWidget::CallWaiting: { - if (_callStatus.timeout >= 3600) { - callText = lng_code_call(lt_minutes, qsl("%1:%2").arg(_callStatus.timeout / 3600).arg((_callStatus.timeout / 60) % 60, 2, 10, QChar('0')), lt_seconds, qsl("%1").arg(_callStatus.timeout % 60, 2, 10, QChar('0'))); +void CodeWidget::updateCallText() { + auto text = ([this]() -> QString { + if (getData()->codeByTelegram) { + return QString(); + } + switch (_callStatus) { + case Widget::Data::CallStatus::Waiting: { + if (_callTimeout >= 3600) { + return lng_code_call(lt_minutes, qsl("%1:%2").arg(_callTimeout / 3600).arg((_callTimeout / 60) % 60, 2, 10, QChar('0')), lt_seconds, qsl("%1").arg(_callTimeout % 60, 2, 10, QChar('0'))); } else { - callText = lng_code_call(lt_minutes, QString::number(_callStatus.timeout / 60), lt_seconds, qsl("%1").arg(_callStatus.timeout % 60, 2, 10, QChar('0'))); + return lng_code_call(lt_minutes, QString::number(_callTimeout / 60), lt_seconds, qsl("%1").arg(_callTimeout % 60, 2, 10, QChar('0'))); } } break; - - case IntroWidget::CallCalling: { - callText = lang(lng_code_calling); - } break; - - case IntroWidget::CallCalled: { - callText = lang(lng_code_called); - } break; + case Widget::Data::CallStatus::Calling: return lang(lng_code_calling); + case Widget::Data::CallStatus::Called: return lang(lng_code_called); } - if (!callText.isEmpty()) { - p.drawText(QRect(_textRect.left(), _code->y() + _code->height() + st::introCallSkip, st::introTextSize.width(), st::introErrorHeight), callText, style::al_center); - } - } - if (_a_error.animating() || _error.length()) { - p.setOpacity(a_errorAlpha.current()); - p.setFont(st::introErrorFont); - p.setPen(st::introErrorFg); - p.drawText(QRect(_textRect.left(), _next->y() + _next->height() + st::introErrorTop, st::introTextSize.width(), st::introErrorHeight), _error, style::al_center); - } + return QString(); + })(); + _callLabel->setText(text); + _callLabel->setVisible(!text.isEmpty() && !animating()); } -void IntroCode::resizeEvent(QResizeEvent *e) { - if (e->oldSize().width() != width()) { - _next->move((width() - _next->width()) / 2, st::introBtnTop); - _code->move((width() - _code->width()) / 2, st::introTextTop + st::introTextSize.height() + st::introCountry.top); - } - _textRect = QRect((width() - st::introTextSize.width()) / 2, st::introTextTop, st::introTextSize.width(), st::introTextSize.height()); - _noTelegramCode->move(_textRect.left() + (st::introTextSize.width() - _noTelegramCode->width()) / 2, _code->y() + _code->height() + st::introCallSkip + (st::introErrorHeight - _noTelegramCode->height()) / 2); +void CodeWidget::resizeEvent(QResizeEvent *e) { + Step::resizeEvent(e); + _code->moveToLeft(contentLeft(), contentTop() + st::introStepFieldTop); + auto linkTop = _code->y() + _code->height() + st::introLinkTop; + _noTelegramCode->moveToLeft(contentLeft() + st::buttonRadius, linkTop); + _callLabel->moveToLeft(contentLeft() + st::buttonRadius, linkTop); } -void IntroCode::showError(const QString &error) { - if (!error.isEmpty()) _code->notaBene(); - if (!_a_error.animating() && error == _error) return; - - if (error.length()) { - _error = error; - a_errorAlpha.start(1); - } else { - a_errorAlpha.start(0); - } - _a_error.start(); +void CodeWidget::showCodeError(const QString &text) { + if (!text.isEmpty()) _code->showError(); + showError(text); } -void IntroCode::step_error(float64 ms, bool timer) { - float64 dt = ms / st::introErrorDuration; - - if (dt >= 1) { - _a_error.stop(); - a_errorAlpha.finish(); - if (!a_errorAlpha.current()) { - _error.clear(); - } - } else { - a_errorAlpha.update(dt, anim::linear); - } - if (timer) update(); -} - -void IntroCode::activate() { - IntroStep::activate(); +void CodeWidget::setInnerFocus() { _code->setFocus(); } -void IntroCode::finished() { - IntroStep::finished(); - _error.clear(); - a_errorAlpha = anim::fvalue(0); - - _sentCode.clear(); - _code->setDisabled(false); - - _callTimer->stop(); - _code->setText(QString()); - rpcClear(); -} - -void IntroCode::cancelled() { - if (_sentRequest) { - MTP::cancel(base::take(_sentRequest)); +void CodeWidget::activate() { + Step::activate(); + _code->show(); + if (getData()->codeByTelegram) { + _noTelegramCode->show(); + } else { + _callLabel->show(); } - MTP::send(MTPauth_CancelCode(MTP_string(intro()->getPhone()), MTP_string(intro()->getPhoneHash()))); + setInnerFocus(); } -void IntroCode::stopCheck() { +void CodeWidget::finished() { + Step::finished(); + _checkRequest->stop(); + _callTimer->stop(); + rpcClear(); + + cancelled(); + _sentCode.clear(); + _code->setText(QString()); + _code->setDisabled(false); +} + +void CodeWidget::cancelled() { + MTP::cancel(base::take(_sentRequest)); + MTP::cancel(base::take(_callRequestId)); + MTP::send(MTPauth_CancelCode(MTP_string(getData()->phone), MTP_string(getData()->phoneHash))); +} + +void CodeWidget::stopCheck() { _checkRequest->stop(); } -void IntroCode::onCheckRequest() { +void CodeWidget::onCheckRequest() { int32 status = MTP::state(_sentRequest); if (status < 0) { int32 leftms = -status; @@ -248,26 +209,25 @@ void IntroCode::onCheckRequest() { } } -void IntroCode::codeSubmitDone(const MTPauth_Authorization &result) { +void CodeWidget::codeSubmitDone(const MTPauth_Authorization &result) { stopCheck(); _sentRequest = 0; _code->setDisabled(false); - const auto &d(result.c_auth_authorization()); + auto &d = result.c_auth_authorization(); if (d.vuser.type() != mtpc_user || !d.vuser.c_user().is_self()) { // wtf? - showError(lang(lng_server_error)); + showCodeError(lang(lng_server_error)); return; } - cSetLoggedPhoneNumber(intro()->getPhone()); - intro()->finish(d.vuser); + cSetLoggedPhoneNumber(getData()->phone); + finish(d.vuser); } -bool IntroCode::codeSubmitFail(const RPCError &error) { +bool CodeWidget::codeSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { stopCheck(); _sentRequest = 0; - showError(lang(lng_flood_error)); _code->setDisabled(false); - _code->setFocus(); + showCodeError(lang(lng_flood_error)); return true; } if (MTP::isDefaultHandledError(error)) return false; @@ -277,138 +237,138 @@ bool IntroCode::codeSubmitFail(const RPCError &error) { _code->setDisabled(false); const QString &err = error.type(); if (err == qstr("PHONE_NUMBER_INVALID") || err == qstr("PHONE_CODE_EXPIRED")) { // show error - intro()->onBack(); + goBack(); return true; } else if (err == qstr("PHONE_CODE_EMPTY") || err == qstr("PHONE_CODE_INVALID")) { - showError(lang(lng_bad_code)); - _code->notaBene(); + showCodeError(lang(lng_bad_code)); return true; } else if (err == qstr("PHONE_NUMBER_UNOCCUPIED")) { // success, need to signUp - intro()->setCode(_sentCode); - intro()->replaceStep(new IntroSignup(intro())); + getData()->code = _sentCode; + goReplace(new Intro::SignupWidget(parentWidget(), getData())); return true; } else if (err == qstr("SESSION_PASSWORD_NEEDED")) { - intro()->setCode(_sentCode); + getData()->code = _sentCode; _code->setDisabled(false); _checkRequest->start(1000); - _sentRequest = MTP::send(MTPaccount_GetPassword(), rpcDone(&IntroCode::gotPassword), rpcFail(&IntroCode::codeSubmitFail)); + _sentRequest = MTP::send(MTPaccount_GetPassword(), rpcDone(&CodeWidget::gotPassword), rpcFail(&CodeWidget::codeSubmitFail)); return true; } if (cDebug()) { // internal server error - showError(err + ": " + error.description()); + showCodeError(err + ": " + error.description()); } else { - showError(lang(lng_server_error)); + showCodeError(lang(lng_server_error)); } - _code->setFocus(); return false; } -void IntroCode::onInputChange() { - showError(QString()); - if (_code->text().length() == 5) onSubmitCode(); +void CodeWidget::onInputChange() { + hideError(); + if (_code->getLastText().length() == getData()->codeLength) { + submit(); + } } -void IntroCode::onSendCall() { - if (_callStatus.type == IntroWidget::CallWaiting) { - if (--_callStatus.timeout <= 0) { - _callStatus.type = IntroWidget::CallCalling; +void CodeWidget::onSendCall() { + if (_callStatus == Widget::Data::CallStatus::Waiting) { + if (--_callTimeout <= 0) { + _callStatus = Widget::Data::CallStatus::Calling; _callTimer->stop(); - MTP::send(MTPauth_ResendCode(MTP_string(intro()->getPhone()), MTP_string(intro()->getPhoneHash())), rpcDone(&IntroCode::callDone)); + _callRequestId = MTP::send(MTPauth_ResendCode(MTP_string(getData()->phone), MTP_string(getData()->phoneHash)), rpcDone(&CodeWidget::callDone)); } else { - intro()->setCallStatus(_callStatus); + getData()->callStatus = _callStatus; + getData()->callTimeout = _callTimeout; } - } - update(); -} - -void IntroCode::callDone(const MTPauth_SentCode &v) { - if (_callStatus.type == IntroWidget::CallCalling) { - _callStatus.type = IntroWidget::CallCalled; - intro()->setCallStatus(_callStatus); - update(); + updateCallText(); } } -void IntroCode::gotPassword(const MTPaccount_Password &result) { +void CodeWidget::callDone(const MTPauth_SentCode &v) { + if (v.type() == mtpc_auth_sentCode) { + fillSentCodeData(v.c_auth_sentCode().vtype); + _code->setDigitsCountMax(getData()->codeLength); + } + if (_callStatus == Widget::Data::CallStatus::Calling) { + _callStatus = Widget::Data::CallStatus::Called; + getData()->callStatus = _callStatus; + getData()->callTimeout = _callTimeout; + updateCallText(); + } +} + +void CodeWidget::gotPassword(const MTPaccount_Password &result) { stopCheck(); _sentRequest = 0; _code->setDisabled(false); switch (result.type()) { - case mtpc_account_noPassword: // should not happen + case mtpc_account_noPassword: { // should not happen _code->setFocus(); - break; + } break; case mtpc_account_password: { - const auto &d(result.c_account_password()); - intro()->setPwdSalt(qba(d.vcurrent_salt)); - intro()->setHasRecovery(mtpIsTrue(d.vhas_recovery)); - intro()->setPwdHint(qs(d.vhint)); - intro()->replaceStep(new IntroPwdCheck(intro())); + auto &d = result.c_account_password(); + getData()->pwdSalt = qba(d.vcurrent_salt); + getData()->hasRecovery = mtpIsTrue(d.vhas_recovery); + getData()->pwdHint = qs(d.vhint); + goReplace(new Intro::PwdCheckWidget(parentWidget(), getData())); } break; } } -void IntroCode::onSubmitCode() { +void CodeWidget::submit() { if (_sentRequest) return; _code->setDisabled(true); setFocus(); - showError(QString()); + hideError(); _checkRequest->start(1000); - _sentCode = _code->text(); - intro()->setPwdSalt(QByteArray()); - intro()->setHasRecovery(false); - intro()->setPwdHint(QString()); - _sentRequest = MTP::send(MTPauth_SignIn(MTP_string(intro()->getPhone()), MTP_string(intro()->getPhoneHash()), MTP_string(_sentCode)), rpcDone(&IntroCode::codeSubmitDone), rpcFail(&IntroCode::codeSubmitFail)); + _sentCode = _code->getLastText(); + getData()->pwdSalt = QByteArray(); + getData()->hasRecovery = false; + getData()->pwdHint = QString(); + _sentRequest = MTP::send(MTPauth_SignIn(MTP_string(getData()->phone), MTP_string(getData()->phoneHash), MTP_string(_sentCode)), rpcDone(&CodeWidget::codeSubmitDone), rpcFail(&CodeWidget::codeSubmitFail)); } -void IntroCode::onNoTelegramCode() { +void CodeWidget::onNoTelegramCode() { if (_noTelegramCodeRequestId) return; - _noTelegramCodeRequestId = MTP::send(MTPauth_ResendCode(MTP_string(intro()->getPhone()), MTP_string(intro()->getPhoneHash())), rpcDone(&IntroCode::noTelegramCodeDone), rpcFail(&IntroCode::noTelegramCodeFail)); + _noTelegramCodeRequestId = MTP::send(MTPauth_ResendCode(MTP_string(getData()->phone), MTP_string(getData()->phoneHash)), rpcDone(&CodeWidget::noTelegramCodeDone), rpcFail(&CodeWidget::noTelegramCodeFail)); } -void IntroCode::noTelegramCodeDone(const MTPauth_SentCode &result) { +void CodeWidget::noTelegramCodeDone(const MTPauth_SentCode &result) { if (result.type() != mtpc_auth_sentCode) { - showError(lang(lng_server_error)); + showCodeError(lang(lng_server_error)); return; } - const auto &d(result.c_auth_sentCode()); - switch (d.vtype.type()) { - case mtpc_auth_sentCodeTypeApp: intro()->setCodeByTelegram(true); - case mtpc_auth_sentCodeTypeSms: - case mtpc_auth_sentCodeTypeCall: intro()->setCodeByTelegram(false); - case mtpc_auth_sentCodeTypeFlashCall: LOG(("Error: should not be flashcall!")); break; - } + auto &d = result.c_auth_sentCode(); + fillSentCodeData(d.vtype); + _code->setDigitsCountMax(getData()->codeLength); if (d.has_next_type() && d.vnext_type.type() == mtpc_auth_codeTypeCall) { - intro()->setCallStatus({ IntroWidget::CallWaiting, d.has_timeout() ? d.vtimeout.v : 60 }); + getData()->callStatus = Widget::Data::CallStatus::Waiting; + getData()->callTimeout = d.has_timeout() ? d.vtimeout.v : 60; } else { - intro()->setCallStatus({ IntroWidget::CallDisabled, 0 }); + getData()->callStatus = Widget::Data::CallStatus::Disabled; + getData()->callTimeout = 0; } - intro()->setCodeByTelegram(false); + getData()->codeByTelegram = false; updateDescText(); } -bool IntroCode::noTelegramCodeFail(const RPCError &error) { +bool CodeWidget::noTelegramCodeFail(const RPCError &error) { if (MTP::isFloodError(error)) { - showError(lang(lng_flood_error)); - _code->setFocus(); + showCodeError(lang(lng_flood_error)); return true; } if (MTP::isDefaultHandledError(error)) return false; if (cDebug()) { // internal server error - showError(error.type() + ": " + error.description()); + showCodeError(error.type() + ": " + error.description()); } else { - showError(lang(lng_server_error)); + showCodeError(lang(lng_server_error)); } - _code->setFocus(); return false; } -void IntroCode::onSubmit() { - onSubmitCode(); -} +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/introcode.h b/Telegram/SourceFiles/intro/introcode.h index eb8a76a9d..81b37a82a 100644 --- a/Telegram/SourceFiles/intro/introcode.h +++ b/Telegram/SourceFiles/intro/introcode.h @@ -26,55 +26,63 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Ui { class RoundButton; class LinkButton; +class FlatLabel; } // namespace Ui -class CodeInput final : public Ui::FlatInput { +namespace Intro { + +class CodeInput final : public Ui::MaskedInputField { Q_OBJECT public: - CodeInput(QWidget *parent, const style::FlatInput &st, const QString &ph); + CodeInput(QWidget *parent, const style::InputField &st, const QString &ph); + + void setDigitsCountMax(int digitsCount); signals: void codeEntered(); protected: - void correctValue(const QString &was, QString &now); + void correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) override; + +private: + int _digitsCountMax = 5; }; -class IntroCode final : public IntroStep { +class CodeWidget : public Widget::Step { Q_OBJECT public: - IntroCode(IntroWidget *parent); - - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - - void step_error(float64 ms, bool timer); + CodeWidget(QWidget *parent, Widget::Data *data); bool hasBack() const override { return true; } + void setInnerFocus() override; void activate() override; void finished() override; void cancelled() override; - void onSubmit() override; - - void codeSubmitDone(const MTPauth_Authorization &result); - bool codeSubmitFail(const RPCError &error); + void submit() override; void updateDescText(); -public slots: - void onSubmitCode(); +protected: + void resizeEvent(QResizeEvent *e) override; + +private slots: void onNoTelegramCode(); void onInputChange(); void onSendCall(); void onCheckRequest(); private: - void showError(const QString &err); + void updateCallText(); + + void codeSubmitDone(const MTPauth_Authorization &result); + bool codeSubmitFail(const RPCError &error); + + void showCodeError(const QString &text); void callDone(const MTPauth_SentCode &v); void gotPassword(const MTPaccount_Password &result); @@ -83,23 +91,21 @@ private: void stopCheck(); - QString _error; - anim::fvalue a_errorAlpha; - Animation _a_error; - - ChildWidget _next; - - Text _desc; ChildWidget _noTelegramCode; - mtpRequestId _noTelegramCodeRequestId; - QRect _textRect; + mtpRequestId _noTelegramCodeRequestId = 0; ChildWidget _code; QString _sentCode; mtpRequestId _sentRequest = 0; + ChildObject _callTimer; - IntroWidget::CallStatus _callStatus; + Widget::Data::CallStatus _callStatus; + int _callTimeout; + mtpRequestId _callRequestId = 0; + ChildWidget _callLabel; ChildObject _checkRequest; }; + +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/introphone.cpp b/Telegram/SourceFiles/intro/introphone.cpp index 837781b6d..9b98d3cfc 100644 --- a/Telegram/SourceFiles/intro/introphone.cpp +++ b/Telegram/SourceFiles/intro/introphone.cpp @@ -27,20 +27,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_intro.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" +#include "ui/effects/widget_fade_wrap.h" +#include "core/click_handler_types.h" -IntroPhone::IntroPhone(IntroWidget *parent) : IntroStep(parent) -, a_errorAlpha(0) -, _a_error(animation(this, &IntroPhone::step_error)) -, _next(this, lang(lng_intro_next), st::introNextButton) +namespace Intro { + +PhoneWidget::PhoneWidget(QWidget *parent, Widget::Data *data) : Step(parent, data) , _country(this, st::introCountry) -, _phone(this, st::introPhone) , _code(this, st::introCountryCode) -, _signup(this, lng_phone_notreg(lt_signup_start, textcmdStartLink(1), lt_signup_end, textcmdStopLink()), Ui::FlatLabel::InitType::Rich, st::introErrorLabel, st::introErrorLabelTextStyle) +, _phone(this, st::introPhone) , _checkRequest(this) { - setVisible(false); - setGeometry(parent->innerRect()); - - connect(_next, SIGNAL(clicked()), this, SLOT(onSubmitPhone())); connect(_phone, SIGNAL(voidBackspace(QKeyEvent*)), _code, SLOT(startErasing(QKeyEvent*))); connect(_country, SIGNAL(codeChanged(const QString &)), _code, SLOT(codeSelected(const QString &))); connect(_code, SIGNAL(codeChanged(const QString &)), _country, SLOT(onChooseCode(const QString &))); @@ -49,146 +45,112 @@ IntroPhone::IntroPhone(IntroWidget *parent) : IntroStep(parent) connect(_code, SIGNAL(addedToNumber(const QString &)), _phone, SLOT(addedToNumber(const QString &))); connect(_phone, SIGNAL(changed()), this, SLOT(onInputChange())); connect(_code, SIGNAL(changed()), this, SLOT(onInputChange())); - connect(intro(), SIGNAL(countryChanged()), this, SLOT(countryChanged())); connect(_checkRequest, SIGNAL(timeout()), this, SLOT(onCheckRequest())); - _signup->setLink(1, MakeShared([this] { - toSignUp(); - })); - _signup->hide(); + setTitleText(lang(lng_phone_title)); + setDescriptionText(lang(lng_phone_desc)); + subscribe(getData()->updated, [this] { countryChanged(); }); + setErrorCentered(true); - _signupCache = myGrab(_signup); - - if (!_country->onChooseCountry(intro()->currentCountry())) { + if (!_country->onChooseCountry(getData()->country)) { _country->onChooseCountry(qsl("US")); } _changed = false; } -void IntroPhone::paintEvent(QPaintEvent *e) { - bool trivial = (rect() == e->rect()); +void PhoneWidget::resizeEvent(QResizeEvent *e) { + Step::resizeEvent(e); + _country->moveToLeft(contentLeft(), contentTop() + st::introStepFieldTop); + auto phoneTop = _country->y() + _country->height() + st::introPhoneTop; + _code->moveToLeft(contentLeft(), phoneTop); + _phone->moveToLeft(contentLeft() + _country->width() - st::introPhone.width, phoneTop); + updateSignupGeometry(); +} - QPainter p(this); - if (!trivial) { - p.setClipRect(e->rect()); - } - if (trivial || e->rect().intersects(_textRect)) { - p.setFont(st::introHeaderFont->f); - p.drawText(_textRect, lang(lng_phone_title), style::al_top); - p.setFont(st::introFont->f); - p.drawText(_textRect, lang(lng_phone_desc), style::al_bottom); - } - if (_a_error.animating() || _error.length()) { - int32 errorY = _showSignup ? ((_phone->y() + _phone->height() + _next->y() - st::introErrorFont->height) / 2) : (_next->y() + _next->height() + st::introErrorTop); - p.setOpacity(a_errorAlpha.current()); - p.setFont(st::introErrorFont); - p.setPen(st::introErrorFg); - p.drawText(QRect(_textRect.x(), errorY, _textRect.width(), st::introErrorFont->height), _error, style::al_top); - - if (_signup->isHidden() && _showSignup) { - p.drawPixmap(_signup->x(), _signup->y(), _signupCache); - } +void PhoneWidget::updateSignupGeometry() { + if (_signup) { + _signup->moveToLeft(contentLeft() + st::buttonRadius, contentTop() + st::introDescriptionTop); } } -void IntroPhone::resizeEvent(QResizeEvent *e) { - if (e->oldSize().width() != width()) { - _next->move((width() - _next->width()) / 2, st::introBtnTop); - _country->move((width() - _country->width()) / 2, st::introTextTop + st::introTextSize.height() + st::introCountry.top); - int phoneTop = _country->y() + _country->height() + st::introPhoneTop; - _phone->move((width() - _country->width()) / 2 + _country->width() - st::introPhone.width, phoneTop); - _code->move((width() - _country->width()) / 2, phoneTop); - } - _signup->move((width() - _signup->width()) / 2, _next->y() + _next->height() + st::introErrorTop - ((st::introErrorLabelTextStyle.lineHeight - st::introErrorFont->height) / 2)); - _textRect = QRect((width() - st::introTextSize.width()) / 2, st::introTextTop, st::introTextSize.width(), st::introTextSize.height()); +void PhoneWidget::showPhoneError(const QString &text) { + _phone->showError(); + showError(text); } -void IntroPhone::showError(const QString &error, bool signUp) { - if (!error.isEmpty()) { - _phone->notaBene(); - _showSignup = signUp; +void PhoneWidget::hidePhoneError() { + hideError(); + if (_signup) { + _signup->fadeOut(); + showDescription(); } - - if (!_a_error.animating() && error == _error) return; - - if (error.length()) { - _error = error; - a_errorAlpha.start(1); - } else { - a_errorAlpha.start(0); - } - _signup->hide(); - _a_error.start(); } -void IntroPhone::step_error(float64 ms, bool timer) { - float64 dt = ms / st::introErrorDuration; - - if (dt >= 1) { - _a_error.stop(); - a_errorAlpha.finish(); - if (!a_errorAlpha.current()) { - _error.clear(); - _signup->hide(); - } else if (!_error.isEmpty() && _showSignup) { - _signup->show(); - } - } else { - a_errorAlpha.update(dt, anim::linear); +void PhoneWidget::showSignup() { + showPhoneError(lang(lng_bad_phone_noreg)); + if (!_signup) { + auto signupText = lng_phone_notreg(lt_link_start, textcmdStartLink(1), lt_link_end, textcmdStopLink(), lt_signup_start, textcmdStartLink(2), lt_signup_end, textcmdStopLink()); + auto inner = new Ui::FlatLabel(this, signupText, Ui::FlatLabel::InitType::Rich, st::introDescription, st::introDescriptionTextStyle); + _signup.create(this, inner, base::lambda(), st::introErrorDuration); + _signup->entity()->setLink(1, MakeShared(qsl("https://telegram.org"), false)); + _signup->entity()->setLink(2, MakeShared([this] { + toSignUp(); + })); + _signup->hideFast(); + updateSignupGeometry(); } - if (timer) update(); + _signup->fadeIn(); + hideDescription(); } -void IntroPhone::countryChanged() { +void PhoneWidget::countryChanged() { if (!_changed) { - selectCountry(intro()->currentCountry()); + selectCountry(getData()->country); } } -void IntroPhone::onInputChange() { +void PhoneWidget::onInputChange() { _changed = true; - showError(QString()); + hidePhoneError(); } -void IntroPhone::disableAll() { - _next->setDisabled(true); +void PhoneWidget::disableAll() { _phone->setDisabled(true); _country->setDisabled(true); _code->setDisabled(true); setFocus(); } -void IntroPhone::enableAll(bool failed) { - _next->setDisabled(false); +void PhoneWidget::enableAll(bool failed) { _phone->setDisabled(false); _country->setDisabled(false); _code->setDisabled(false); if (failed) _phone->setFocus(); } -void IntroPhone::onSubmitPhone() { +void PhoneWidget::submit() { if (_sentRequest || isHidden()) return; if (!App::isValidPhone(fullNumber())) { - showError(lang(lng_bad_phone)); + showPhoneError(lang(lng_bad_phone)); _phone->setFocus(); return; } disableAll(); - showError(QString()); + hidePhoneError(); _checkRequest->start(1000); _sentPhone = fullNumber(); - _sentRequest = MTP::send(MTPauth_CheckPhone(MTP_string(_sentPhone)), rpcDone(&IntroPhone::phoneCheckDone), rpcFail(&IntroPhone::phoneSubmitFail)); + _sentRequest = MTP::send(MTPauth_CheckPhone(MTP_string(_sentPhone)), rpcDone(&PhoneWidget::phoneCheckDone), rpcFail(&PhoneWidget::phoneSubmitFail)); } -void IntroPhone::stopCheck() { +void PhoneWidget::stopCheck() { _checkRequest->stop(); } -void IntroPhone::onCheckRequest() { +void PhoneWidget::onCheckRequest() { int32 status = MTP::state(_sentRequest); if (status < 0) { int32 leftms = -status; @@ -202,66 +164,65 @@ void IntroPhone::onCheckRequest() { } } -void IntroPhone::phoneCheckDone(const MTPauth_CheckedPhone &result) { +void PhoneWidget::phoneCheckDone(const MTPauth_CheckedPhone &result) { stopCheck(); - const auto &d(result.c_auth_checkedPhone()); + auto &d = result.c_auth_checkedPhone(); if (mtpIsTrue(d.vphone_registered)) { disableAll(); - showError(QString()); + hidePhoneError(); _checkRequest->start(1000); MTPauth_SendCode::Flags flags = 0; - _sentRequest = MTP::send(MTPauth_SendCode(MTP_flags(flags), MTP_string(_sentPhone), MTPBool(), MTP_int(ApiId), MTP_string(ApiHash)), rpcDone(&IntroPhone::phoneSubmitDone), rpcFail(&IntroPhone::phoneSubmitFail)); + _sentRequest = MTP::send(MTPauth_SendCode(MTP_flags(flags), MTP_string(_sentPhone), MTPBool(), MTP_int(ApiId), MTP_string(ApiHash)), rpcDone(&PhoneWidget::phoneSubmitDone), rpcFail(&PhoneWidget::phoneSubmitFail)); } else { - showError(lang(lng_bad_phone_noreg), true); + showSignup(); enableAll(true); _sentRequest = 0; } } -void IntroPhone::phoneSubmitDone(const MTPauth_SentCode &result) { +void PhoneWidget::phoneSubmitDone(const MTPauth_SentCode &result) { stopCheck(); _sentRequest = 0; enableAll(true); if (result.type() != mtpc_auth_sentCode) { - showError(lang(lng_server_error)); + showPhoneError(lang(lng_server_error)); return; } - const auto &d(result.c_auth_sentCode()); - switch (d.vtype.type()) { - case mtpc_auth_sentCodeTypeApp: intro()->setCodeByTelegram(true); break; - case mtpc_auth_sentCodeTypeSms: - case mtpc_auth_sentCodeTypeCall: intro()->setCodeByTelegram(false); break; - case mtpc_auth_sentCodeTypeFlashCall: LOG(("Error: should not be flashcall!")); break; - } - intro()->setPhone(_sentPhone, d.vphone_code_hash.c_string().v.c_str(), d.is_phone_registered()); + auto &d = result.c_auth_sentCode(); + fillSentCodeData(d.vtype); + getData()->phone = _sentPhone; + getData()->phoneHash = d.vphone_code_hash.c_string().v.c_str(); + getData()->phoneIsRegistered = d.is_phone_registered(); if (d.has_next_type() && d.vnext_type.type() == mtpc_auth_codeTypeCall) { - intro()->setCallStatus({ IntroWidget::CallWaiting, d.has_timeout() ? d.vtimeout.v : 60 }); + getData()->callStatus = Widget::Data::CallStatus::Waiting; + getData()->callTimeout = d.has_timeout() ? d.vtimeout.v : 60; } else { - intro()->setCallStatus({ IntroWidget::CallDisabled, 0 }); + getData()->callStatus = Widget::Data::CallStatus::Disabled; + getData()->callTimeout = 0; } - intro()->nextStep(new IntroCode(intro())); + goNext(new Intro::CodeWidget(parentWidget(), getData())); } -void IntroPhone::toSignUp() { +void PhoneWidget::toSignUp() { disableAll(); - showError(QString()); + hideError(); // Hide error, but leave the signup label visible. _checkRequest->start(1000); MTPauth_SendCode::Flags flags = 0; - _sentRequest = MTP::send(MTPauth_SendCode(MTP_flags(flags), MTP_string(_sentPhone), MTPBool(), MTP_int(ApiId), MTP_string(ApiHash)), rpcDone(&IntroPhone::phoneSubmitDone), rpcFail(&IntroPhone::phoneSubmitFail)); + _sentRequest = MTP::send(MTPauth_SendCode(MTP_flags(flags), MTP_string(_sentPhone), MTPBool(), MTP_int(ApiId), MTP_string(ApiHash)), rpcDone(&PhoneWidget::phoneSubmitDone), rpcFail(&PhoneWidget::phoneSubmitFail)); } -bool IntroPhone::phoneSubmitFail(const RPCError &error) { +bool PhoneWidget::phoneSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { stopCheck(); _sentRequest = 0; - showError(lang(lng_flood_error)); + showPhoneError(lang(lng_flood_error)); enableAll(true); return true; } @@ -271,48 +232,50 @@ bool IntroPhone::phoneSubmitFail(const RPCError &error) { _sentRequest = 0; const QString &err = error.type(); if (err == qstr("PHONE_NUMBER_INVALID")) { // show error - showError(lang(lng_bad_phone)); + showPhoneError(lang(lng_bad_phone)); enableAll(true); return true; } if (cDebug()) { // internal server error - showError(err + ": " + error.description()); + showPhoneError(err + ": " + error.description()); } else { - showError(lang(lng_server_error)); + showPhoneError(lang(lng_server_error)); } enableAll(true); return false; } -QString IntroPhone::fullNumber() const { - return _code->text() + _phone->text(); +QString PhoneWidget::fullNumber() const { + return _code->getLastText() + _phone->getLastText(); } -void IntroPhone::selectCountry(const QString &c) { +void PhoneWidget::selectCountry(const QString &c) { _country->onChooseCountry(c); } -void IntroPhone::activate() { - IntroStep::activate(); +void PhoneWidget::setInnerFocus() { _phone->setFocus(); } -void IntroPhone::finished() { - IntroStep::finished(); +void PhoneWidget::activate() { + Step::activate(); + _country->show(); + _phone->show(); + _code->show(); + setInnerFocus(); +} + +void PhoneWidget::finished() { + Step::finished(); _checkRequest->stop(); rpcClear(); - _error.clear(); - a_errorAlpha = anim::fvalue(0); + cancelled(); enableAll(true); } -void IntroPhone::cancelled() { - if (_sentRequest) { - MTP::cancel(base::take(_sentRequest)); - } +void PhoneWidget::cancelled() { + MTP::cancel(base::take(_sentRequest)); } -void IntroPhone::onSubmit() { - onSubmitPhone(); -} +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/introphone.h b/Telegram/SourceFiles/intro/introphone.h index 1591977df..d7379405e 100644 --- a/Telegram/SourceFiles/intro/introphone.h +++ b/Telegram/SourceFiles/intro/introphone.h @@ -30,35 +30,41 @@ class RoundButton; class FlatLabel; } // namespace Ui -class IntroPhone final : public IntroStep { +namespace Intro { + +class PhoneWidget : public Widget::Step, private base::Subscriber { Q_OBJECT public: - IntroPhone(IntroWidget *parent); - - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - - void step_error(float64 ms, bool timer); + PhoneWidget(QWidget *parent, Widget::Data *data); void selectCountry(const QString &country); + void setInnerFocus() override; void activate() override; void finished() override; void cancelled() override; - void onSubmit() override; + void submit() override; + + bool hasBack() const override { + return true; + } + +protected: + void resizeEvent(QResizeEvent *e) override; + +private slots: + void onInputChange(); + void onCheckRequest(); + +private: + void updateSignupGeometry(); + void countryChanged(); void phoneCheckDone(const MTPauth_CheckedPhone &result); void phoneSubmitDone(const MTPauth_SentCode &result); bool phoneSubmitFail(const RPCError &error); -public slots: - void countryChanged(); - void onInputChange(); - void onSubmitPhone(); - void onCheckRequest(); - -private: void toSignUp(); QString fullNumber() const; @@ -66,24 +72,17 @@ private: void enableAll(bool failed); void stopCheck(); - void showError(const QString &err, bool signUp = false); - - QString _error; - anim::fvalue a_errorAlpha; - Animation _a_error; + void showPhoneError(const QString &text); + void hidePhoneError(); + void showSignup(); bool _changed = false; - ChildWidget _next; - - QRect _textRect; ChildWidget _country; - ChildWidget _phone; ChildWidget _code; + ChildWidget _phone; - ChildWidget _signup; - QPixmap _signupCache; - bool _showSignup = false; + ChildWidget> _signup = { nullptr }; QString _sentPhone; mtpRequestId _sentRequest = 0; @@ -91,3 +90,5 @@ private: ChildObject _checkRequest; }; + +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/intropwdcheck.cpp b/Telegram/SourceFiles/intro/intropwdcheck.cpp index f1e974cda..f4b80843c 100644 --- a/Telegram/SourceFiles/intro/intropwdcheck.cpp +++ b/Telegram/SourceFiles/intro/intropwdcheck.cpp @@ -30,117 +30,52 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "intro/introsignup.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" +#include "ui/widgets/labels.h" -IntroPwdCheck::IntroPwdCheck(IntroWidget *parent) : IntroStep(parent) -, a_errorAlpha(0) -, _a_error(animation(this, &IntroPwdCheck::step_error)) -, _next(this, lang(lng_intro_submit), st::introNextButton) -, _salt(parent->getPwdSalt()) -, _hasRecovery(parent->getHasRecovery()) -, _hint(parent->getPwdHint()) +namespace Intro { + +PwdCheckWidget::PwdCheckWidget(QWidget *parent, Widget::Data *data) : Step(parent, data) +, _salt(getData()->pwdSalt) +, _hasRecovery(getData()->hasRecovery) +, _hint(getData()->pwdHint) , _pwdField(this, st::introPassword, lang(lng_signin_password)) +, _pwdHint(this, st::introPasswordHint, st::introPasswordHintTextStyle) , _codeField(this, st::introPassword, lang(lng_signin_code)) , _toRecover(this, lang(lng_signin_recover)) , _toPassword(this, lang(lng_signin_try_password)) -, _reset(this, lang(lng_signin_reset_account), st::introResetLink) , _checkRequest(this) { - setVisible(false); - setGeometry(parent->innerRect()); - - connect(_next, SIGNAL(clicked()), this, SLOT(onSubmitPwd())); connect(_checkRequest, SIGNAL(timeout()), this, SLOT(onCheckRequest())); connect(_toRecover, SIGNAL(clicked()), this, SLOT(onToRecover())); connect(_toPassword, SIGNAL(clicked()), this, SLOT(onToPassword())); connect(_pwdField, SIGNAL(changed()), this, SLOT(onInputChange())); connect(_codeField, SIGNAL(changed()), this, SLOT(onInputChange())); - connect(_reset, SIGNAL(clicked()), this, SLOT(onReset())); - _pwdField->setEchoMode(QLineEdit::Password); + setTitleText(lang(lng_signin_title)); + updateDescriptionText(); + setErrorBelowLink(true); - if (!_hint.isEmpty()) { - _hintText.setText(st::introFont, lng_signin_hint(lt_password_hint, _hint)); + if (_hint.isEmpty()) { + _pwdHint->hide(); + } else { + _pwdHint->setText(lng_signin_hint(lt_password_hint, _hint)); } _codeField->hide(); _toPassword->hide(); - _toRecover->show(); - _reset->hide(); setMouseTracking(true); } -void IntroPwdCheck::paintEvent(QPaintEvent *e) { - bool trivial = (rect() == e->rect()); - - QPainter p(this); - if (!trivial) { - p.setClipRect(e->rect()); - } - if (trivial || e->rect().intersects(_textRect)) { - p.setFont(st::introHeaderFont->f); - p.drawText(_textRect, lang(lng_signin_title), style::al_top); - p.setFont(st::introFont->f); - p.drawText(_textRect, lang(_pwdField->isHidden() ? lng_signin_recover_desc : lng_signin_desc), style::al_bottom); - } - if (_pwdField->isHidden()) { - if (!_emailPattern.isEmpty()) { - p.drawText(QRect(_textRect.x(), _pwdField->y() + _pwdField->height() + st::introFinishSkip, _textRect.width(), st::introFont->height), _emailPattern, style::al_top); - } - } else if (!_hint.isEmpty()) { - _hintText.drawElided(p, _pwdField->x(), _pwdField->y() + _pwdField->height() + st::introFinishSkip, _pwdField->width(), 1, style::al_top); - } - if (_a_error.animating() || _error.length()) { - p.setOpacity(a_errorAlpha.current()); - - QRect errRect((width() - st::introErrorWidth) / 2, (_pwdField->y() + _pwdField->height() + st::introFinishSkip + st::introFont->height + _next->y() - st::introErrorHeight) / 2, st::introErrorWidth, st::introErrorHeight); - p.setFont(st::introErrorFont); - p.setPen(st::introErrorFg); - p.drawText(errRect, _error, QTextOption(style::al_center)); - - p.setOpacity(1); - } +void PwdCheckWidget::resizeEvent(QResizeEvent *e) { + Step::resizeEvent(e); + _pwdField->moveToLeft(contentLeft(), contentTop() + st::introPasswordTop); + _pwdHint->moveToLeft(contentLeft() + st::buttonRadius, contentTop() + st::introPasswordHintTop); + _codeField->moveToLeft(contentLeft(), contentTop() + st::introStepFieldTop); + auto linkTop = _codeField->y() + _codeField->height() + st::introLinkTop; + _toRecover->moveToLeft(contentLeft() + st::buttonRadius, linkTop); + _toPassword->moveToLeft(contentLeft() + st::buttonRadius, linkTop); } -void IntroPwdCheck::resizeEvent(QResizeEvent *e) { - if (e->oldSize().width() != width()) { - _next->move((width() - _next->width()) / 2, st::introBtnTop); - _pwdField->move((width() - _pwdField->width()) / 2, st::introTextTop + st::introTextSize.height() + st::introCountry.top); - _codeField->move((width() - _codeField->width()) / 2, st::introTextTop + st::introTextSize.height() + st::introCountry.top); - _toRecover->move(_next->x() + (_pwdField->width() - _toRecover->width()) / 2, _next->y() + _next->height() + st::introFinishSkip); - _toPassword->move(_next->x() + (_pwdField->width() - _toPassword->width()) / 2, _next->y() + _next->height() + st::introFinishSkip); - _reset->move((width() - _reset->width()) / 2, _toRecover->y() + _toRecover->height() + st::introFinishSkip); - } - _textRect = QRect((width() - st::introTextSize.width()) / 2, st::introTextTop, st::introTextSize.width(), st::introTextSize.height()); -} - -void IntroPwdCheck::showError(const QString &error) { - if (!_a_error.animating() && error == _error) return; - - if (error.length()) { - _error = error; - a_errorAlpha.start(1); - } else { - a_errorAlpha.start(0); - } - _a_error.start(); -} - -void IntroPwdCheck::step_error(float64 ms, bool timer) { - float64 dt = ms / st::introErrorDuration; - - if (dt >= 1) { - _a_error.stop(); - a_errorAlpha.finish(); - if (!a_errorAlpha.current()) { - _error.clear(); - } - } else { - a_errorAlpha.update(dt, anim::linear); - } - if (timer) update(); -} - -void IntroPwdCheck::activate() { - IntroStep::activate(); +void PwdCheckWidget::setInnerFocus() { if (_pwdField->isHidden()) { _codeField->setFocus(); } else { @@ -148,17 +83,25 @@ void IntroPwdCheck::activate() { } } -void IntroPwdCheck::cancelled() { - if (_sentRequest) { - MTP::cancel(base::take(_sentRequest)); +void PwdCheckWidget::activate() { + if (_pwdField->isHidden() && _codeField->isHidden()) { + Step::activate(); + _pwdField->show(); + _pwdHint->show(); + _toRecover->show(); } + setInnerFocus(); } -void IntroPwdCheck::stopCheck() { +void PwdCheckWidget::cancelled() { + MTP::cancel(base::take(_sentRequest)); +} + +void PwdCheckWidget::stopCheck() { _checkRequest->stop(); } -void IntroPwdCheck::onCheckRequest() { +void PwdCheckWidget::onCheckRequest() { int32 status = MTP::state(_sentRequest); if (status < 0) { int32 leftms = -status; @@ -176,7 +119,7 @@ void IntroPwdCheck::onCheckRequest() { } } -void IntroPwdCheck::pwdSubmitDone(bool recover, const MTPauth_Authorization &result) { +void PwdCheckWidget::pwdSubmitDone(bool recover, const MTPauth_Authorization &result) { _sentRequest = 0; stopCheck(); if (recover) { @@ -184,22 +127,22 @@ void IntroPwdCheck::pwdSubmitDone(bool recover, const MTPauth_Authorization &res } _pwdField->setDisabled(false); _codeField->setDisabled(false); - const auto &d(result.c_auth_authorization()); + auto &d = result.c_auth_authorization(); if (d.vuser.type() != mtpc_user || !d.vuser.c_user().is_self()) { // wtf? showError(lang(lng_server_error)); return; } - intro()->finish(d.vuser); + finish(d.vuser); } -bool IntroPwdCheck::pwdSubmitFail(const RPCError &error) { +bool PwdCheckWidget::pwdSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { _sentRequest = 0; stopCheck(); _codeField->setDisabled(false); showError(lang(lng_flood_error)); _pwdField->setDisabled(false); - _pwdField->notaBene(); + _pwdField->showError(); return true; } if (MTP::isDefaultHandledError(error)) return false; @@ -212,10 +155,10 @@ bool IntroPwdCheck::pwdSubmitFail(const RPCError &error) { if (err == qstr("PASSWORD_HASH_INVALID")) { showError(lang(lng_signin_bad_password)); _pwdField->selectAll(); - _pwdField->notaBene(); + _pwdField->showError(); return true; } else if (err == qstr("PASSWORD_EMPTY")) { - intro()->onBack(); + goBack(); } if (cDebug()) { // internal server error showError(err + ": " + error.description()); @@ -226,10 +169,10 @@ bool IntroPwdCheck::pwdSubmitFail(const RPCError &error) { return false; } -bool IntroPwdCheck::codeSubmitFail(const RPCError &error) { +bool PwdCheckWidget::codeSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { showError(lang(lng_flood_error)); - _codeField->notaBene(); + _codeField->showError(); return true; } if (MTP::isDefaultHandledError(error)) return false; @@ -240,7 +183,7 @@ bool IntroPwdCheck::codeSubmitFail(const RPCError &error) { _codeField->setDisabled(false); const QString &err = error.type(); if (err == qstr("PASSWORD_EMPTY")) { - intro()->onBack(); + goBack(); return true; } else if (err == qstr("PASSWORD_RECOVERY_NA")) { recoverStartFail(error); @@ -252,7 +195,7 @@ bool IntroPwdCheck::codeSubmitFail(const RPCError &error) { } else if (err == qstr("CODE_INVALID")) { showError(lang(lng_signin_wrong_code)); _codeField->selectAll(); - _codeField->notaBene(); + _codeField->showError(); return true; } if (cDebug()) { // internal server error @@ -264,39 +207,42 @@ bool IntroPwdCheck::codeSubmitFail(const RPCError &error) { return false; } -void IntroPwdCheck::recoverStarted(const MTPauth_PasswordRecovery &result) { - _emailPattern = st::introFont->elided(lng_signin_recover_hint(lt_recover_email, qs(result.c_auth_passwordRecovery().vemail_pattern)), _textRect.width()); - update(); +void PwdCheckWidget::recoverStarted(const MTPauth_PasswordRecovery &result) { + _emailPattern = qs(result.c_auth_passwordRecovery().vemail_pattern); + updateDescriptionText(); } -bool IntroPwdCheck::recoverStartFail(const RPCError &error) { +bool PwdCheckWidget::recoverStartFail(const RPCError &error) { stopCheck(); _pwdField->setDisabled(false); _codeField->setDisabled(false); _pwdField->show(); + _pwdHint->show(); _codeField->hide(); _pwdField->setFocus(); + updateDescriptionText(); update(); - showError(QString()); + hideError(); return true; } -void IntroPwdCheck::onToRecover() { +void PwdCheckWidget::onToRecover() { if (_hasRecovery) { if (_sentRequest) { MTP::cancel(base::take(_sentRequest)); } - showError(QString()); + hideError(); _toRecover->hide(); _toPassword->show(); _pwdField->hide(); + _pwdHint->hide(); _pwdField->setText(QString()); _codeField->show(); _codeField->setFocus(); + updateDescriptionText(); if (_emailPattern.isEmpty()) { - MTP::send(MTPauth_RequestPasswordRecovery(), rpcDone(&IntroPwdCheck::recoverStarted), rpcFail(&IntroPwdCheck::recoverStartFail)); + MTP::send(MTPauth_RequestPasswordRecovery(), rpcDone(&PwdCheckWidget::recoverStarted), rpcFail(&PwdCheckWidget::recoverStartFail)); } - update(); } else { ConfirmBox *box = new InformBox(lang(lng_signin_no_email_forgot)); Ui::showLayer(box); @@ -304,101 +250,63 @@ void IntroPwdCheck::onToRecover() { } } -void IntroPwdCheck::onToPassword() { +void PwdCheckWidget::onToPassword() { ConfirmBox *box = new InformBox(lang(lng_signin_cant_email_forgot)); Ui::showLayer(box); connect(box, SIGNAL(destroyed(QObject*)), this, SLOT(onToReset())); } -void IntroPwdCheck::onToReset() { +void PwdCheckWidget::onToReset() { if (_sentRequest) { MTP::cancel(base::take(_sentRequest)); } _toRecover->show(); _toPassword->hide(); _pwdField->show(); + _pwdHint->show(); _codeField->hide(); _codeField->setText(QString()); _pwdField->setFocus(); - _reset->show(); + showResetButton(); + updateDescriptionText(); update(); } -void IntroPwdCheck::onReset() { - if (_sentRequest) return; - ConfirmBox *box = new ConfirmBox(lang(lng_signin_sure_reset), lang(lng_signin_reset), st::attentionBoxButton); - connect(box, SIGNAL(confirmed()), this, SLOT(onResetSure())); - Ui::showLayer(box); +void PwdCheckWidget::updateDescriptionText() { + setDescriptionText(_pwdField->isHidden() ? lng_signin_recover_desc(lt_email, _emailPattern) : lang(lng_signin_desc)); } -void IntroPwdCheck::onResetSure() { - if (_sentRequest) return; - _sentRequest = MTP::send(MTPaccount_DeleteAccount(MTP_string("Forgot password")), rpcDone(&IntroPwdCheck::deleteDone), rpcFail(&IntroPwdCheck::deleteFail)); +void PwdCheckWidget::onInputChange() { + hideError(); } -bool IntroPwdCheck::deleteFail(const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; - - _sentRequest = 0; - - auto type = error.type(); - if (type.startsWith(qstr("2FA_CONFIRM_WAIT_"))) { - int seconds = type.mid(qstr("2FA_CONFIRM_WAIT_").size()).toInt(); - int days = (seconds + 59) / 86400; - int hours = ((seconds + 59) % 86400) / 3600; - int minutes = ((seconds + 59) % 3600) / 60; - QString when; - if (days > 0) { - when = lng_signin_reset_in_days(lt_count_days, days, lt_count_hours, hours, lt_count_minutes, minutes); - } else if (hours > 0) { - when = lng_signin_reset_in_hours(lt_count_hours, hours, lt_count_minutes, minutes); - } else { - when = lng_signin_reset_in_minutes(lt_count_minutes, minutes); - } - Ui::showLayer(new InformBox(lng_signin_reset_wait(lt_phone_number, App::formatPhone(intro()->getPhone()), lt_when, when))); - } else if (type == qstr("2FA_RECENT_CONFIRM")) { - Ui::showLayer(new InformBox(lang(lng_signin_reset_cancelled))); - } else { - Ui::hideLayer(); - showError(lang(lng_server_error)); - } - return true; -} - -void IntroPwdCheck::deleteDone(const MTPBool &v) { - Ui::hideLayer(); - intro()->replaceStep(new IntroSignup(intro())); -} - -void IntroPwdCheck::onInputChange() { - showError(QString()); -} - -void IntroPwdCheck::onSubmitPwd(bool force) { +void PwdCheckWidget::submit() { if (_sentRequest) return; if (_pwdField->isHidden()) { - if (!force && !_codeField->isEnabled()) return; - QString code = _codeField->text().trimmed(); + if (!_codeField->isEnabled()) return; + auto code = _codeField->getLastText().trimmed(); if (code.isEmpty()) { - _codeField->notaBene(); + _codeField->showError(); return; } - _sentRequest = MTP::send(MTPauth_RecoverPassword(MTP_string(code)), rpcDone(&IntroPwdCheck::pwdSubmitDone, true), rpcFail(&IntroPwdCheck::codeSubmitFail)); + _sentRequest = MTP::send(MTPauth_RecoverPassword(MTP_string(code)), rpcDone(&PwdCheckWidget::pwdSubmitDone, true), rpcFail(&PwdCheckWidget::codeSubmitFail)); } else { - if (!force && !_pwdField->isEnabled()) return; + if (!_pwdField->isEnabled()) return; _pwdField->setDisabled(true); setFocus(); - showError(QString()); + hideError(); - QByteArray pwdData = _salt + _pwdField->text().toUtf8() + _salt, pwdHash(32, Qt::Uninitialized); + QByteArray pwdData = _salt + _pwdField->getLastText().toUtf8() + _salt, pwdHash(32, Qt::Uninitialized); hashSha256(pwdData.constData(), pwdData.size(), pwdHash.data()); - _sentRequest = MTP::send(MTPauth_CheckPassword(MTP_bytes(pwdHash)), rpcDone(&IntroPwdCheck::pwdSubmitDone, false), rpcFail(&IntroPwdCheck::pwdSubmitFail)); + _sentRequest = MTP::send(MTPauth_CheckPassword(MTP_bytes(pwdHash)), rpcDone(&PwdCheckWidget::pwdSubmitDone, false), rpcFail(&PwdCheckWidget::pwdSubmitFail)); } } -void IntroPwdCheck::onSubmit() { - onSubmitPwd(); +QString PwdCheckWidget::nextButtonText() const { + return lang(lng_intro_submit); } + +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/intropwdcheck.h b/Telegram/SourceFiles/intro/intropwdcheck.h index b25fca221..359fe4c62 100644 --- a/Telegram/SourceFiles/intro/intropwdcheck.h +++ b/Telegram/SourceFiles/intro/intropwdcheck.h @@ -23,26 +23,37 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "intro/introwidget.h" namespace Ui { -class FlatInput; +class InputField; +class PasswordInput; class RoundButton; class LinkButton; } // namespace Ui -class IntroPwdCheck final : public IntroStep { +namespace Intro { + +class PwdCheckWidget : public Widget::Step { Q_OBJECT public: - IntroPwdCheck(IntroWidget *parent); - - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - - void step_error(float64 ms, bool timer); + PwdCheckWidget(QWidget *parent, Widget::Data *data); + void setInnerFocus() override; void activate() override; void cancelled() override; - void onSubmit() override; + void submit() override; + QString nextButtonText() const override; +protected: + void resizeEvent(QResizeEvent *e) override; + +private slots: + void onToRecover(); + void onToPassword(); + void onInputChange(); + void onCheckRequest(); + void onToReset(); + +private: void pwdSubmitDone(bool recover, const MTPauth_Authorization &result); bool pwdSubmitFail(const RPCError &error); bool codeSubmitFail(const RPCError &error); @@ -50,46 +61,24 @@ public: void recoverStarted(const MTPauth_PasswordRecovery &result); -public slots: - void onSubmitPwd(bool force = false); - void onToRecover(); - void onToPassword(); - void onInputChange(); - void onCheckRequest(); - void onToReset(); - void onReset(); - void onResetSure(); - -private: - void showError(const QString &error); + void updateDescriptionText(); void stopCheck(); - void deleteDone(const MTPBool &result); - bool deleteFail(const RPCError &error); - - QString _error; - anim::fvalue a_errorAlpha; - Animation _a_error; - - ChildWidget _next; - - QRect _textRect; - QByteArray _salt; bool _hasRecovery; QString _hint, _emailPattern; - ChildWidget _pwdField; - ChildWidget _codeField; + ChildWidget _pwdField; + ChildWidget _pwdHint; + ChildWidget _codeField; ChildWidget _toRecover; ChildWidget _toPassword; - ChildWidget _reset; mtpRequestId _sentRequest = 0; - Text _hintText; - QByteArray _pwdSalt; ChildObject _checkRequest; }; + +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/introsignup.cpp b/Telegram/SourceFiles/intro/introsignup.cpp index df3e6ed58..de2d36128 100644 --- a/Telegram/SourceFiles/intro/introsignup.cpp +++ b/Telegram/SourceFiles/intro/introsignup.cpp @@ -29,200 +29,106 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "application.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" +#include "ui/widgets/labels.h" +#include "ui/buttons/peer_avatar_button.h" -IntroSignup::IntroSignup(IntroWidget *parent) : IntroStep(parent) -, a_errorAlpha(0) -, a_photoOver(0) -, _a_error(animation(this, &IntroSignup::step_error)) -, _a_photo(animation(this, &IntroSignup::step_photo)) -, _next(this, lang(lng_intro_finish), st::introNextButton) +namespace Intro { + +SignupWidget::SignupWidget(QWidget *parent, Widget::Data *data) : Step(parent, data) +, _photo(this, st::introPhotoSize, st::introPhotoIconPosition) , _first(this, st::introName, lang(lng_signup_firstname)) , _last(this, st::introName, lang(lng_signup_lastname)) , _invertOrder(langFirstNameGoesSecond()) , _checkRequest(this) { - setVisible(false); - setGeometry(parent->innerRect()); - - connect(_next, SIGNAL(clicked()), this, SLOT(onSubmitName())); connect(_checkRequest, SIGNAL(timeout()), this, SLOT(onCheckRequest())); + _photo->setClickedCallback([this] { + auto imgExtensions = cImgExtensions(); + auto filter = qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + filedialogAllFilesFilter(); + _readPhotoFileQueryId = FileDialog::queryReadFile(lang(lng_choose_images), filter); + }); + subscribe(FileDialog::QueryDone(), [this](const FileDialog::QueryUpdate &update) { + notifyFileQueryUpdated(update); + }); + if (_invertOrder) { setTabOrder(_last, _first); } + setErrorCentered(true); + setTitleText(lang(lng_signup_title)); + setDescriptionText(lang(lng_signup_desc)); setMouseTracking(true); } -void IntroSignup::mouseMoveEvent(QMouseEvent *e) { - bool photoOver = QRect(_phLeft, _phTop, st::introPhotoSize, st::introPhotoSize).contains(e->pos()); - if (photoOver != _photoOver) { - _photoOver = photoOver; - if (_photoSmall.isNull()) { - a_photoOver.start(_photoOver ? 1 : 0); - _a_photo.start(); - } +void SignupWidget::notifyFileQueryUpdated(const FileDialog::QueryUpdate &update) { + if (_readPhotoFileQueryId != update.queryId) { + return; + } + _readPhotoFileQueryId = 0; + if (update.remoteContent.isEmpty() && update.filePaths.isEmpty()) { + return; } - setCursor(_photoOver ? style::cur_pointer : style::cur_default); -} - -void IntroSignup::mousePressEvent(QMouseEvent *e) { - mouseMoveEvent(e); - if (QRect(_phLeft, _phTop, st::introPhotoSize, st::introPhotoSize).contains(e->pos())) { - QStringList imgExtensions(cImgExtensions()); - QString filter(qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + filedialogAllFilesFilter()); - - QImage img; - QString file; - QByteArray remoteContent; - if (filedialogGetOpenFile(file, remoteContent, lang(lng_choose_images), filter)) { - if (!remoteContent.isEmpty()) { - img = App::readImage(remoteContent); - } else { - if (!file.isEmpty()) { - img = App::readImage(file); - } - } - } else { - return; - } - - if (img.isNull() || img.width() > 10 * img.height() || img.height() > 10 * img.width()) { - showError(lang(lng_bad_photo)); - return; - } - PhotoCropBox *box = new PhotoCropBox(img, PeerId(0)); - connect(box, SIGNAL(ready(const QImage &)), this, SLOT(onPhotoReady(const QImage &))); - Ui::showLayer(box); - } -} - -void IntroSignup::paintEvent(QPaintEvent *e) { - bool trivial = (rect() == e->rect()); - - Painter p(this); - if (!trivial) { - p.setClipRect(e->rect()); - } - if (trivial || e->rect().intersects(_textRect)) { - p.setFont(st::introHeaderFont->f); - p.drawText(_textRect, lang(lng_signup_title), style::al_top); - p.setFont(st::introFont->f); - p.drawText(_textRect, lang(lng_signup_desc), style::al_bottom); - } - if (_a_error.animating() || error.length()) { - p.setOpacity(a_errorAlpha.current()); - - QRect errRect; - if (_invertOrder) { - errRect = QRect((width() - st::introErrorWidth) / 2, (_first->y() + _first->height() + _next->y() - st::introErrorHeight) / 2, st::introErrorWidth, st::introErrorHeight); - } else { - errRect = QRect((width() - st::introErrorWidth) / 2, (_last->y() + _last->height() + _next->y() - st::introErrorHeight) / 2, st::introErrorWidth, st::introErrorHeight); - } - p.setFont(st::introErrorFont); - p.setPen(st::introErrorFg); - p.drawText(errRect, error, QTextOption(style::al_center)); - - p.setOpacity(1); - } - - if (_photoSmall.isNull()) { - float64 o = a_photoOver.current(); - QRect phRect(_phLeft, _phTop, st::introPhotoSize, st::introPhotoSize); - if (o > 0) { - if (o < 1) { - QColor c; - c.setRedF(st::newGroupPhotoBg->c.redF() * (1. - o) + st::newGroupPhotoBgOver->c.redF() * o); - c.setGreenF(st::newGroupPhotoBg->c.greenF() * (1. - o) + st::newGroupPhotoBgOver->c.greenF() * o); - c.setBlueF(st::newGroupPhotoBg->c.blueF() * (1. - o) + st::newGroupPhotoBgOver->c.blueF() * o); - p.fillRect(phRect, c); - } else { - p.fillRect(phRect, st::newGroupPhotoBgOver); - } - } else { - p.fillRect(phRect, st::newGroupPhotoBg); - } - st::newGroupPhotoIcon.paintInCenter(p, phRect); + QImage img; + if (!update.remoteContent.isEmpty()) { + img = App::readImage(update.remoteContent); } else { - p.drawPixmap(_phLeft, _phTop, _photoSmall); + img = App::readImage(update.filePaths.front()); } + + if (img.isNull() || img.width() > 10 * img.height() || img.height() > 10 * img.width()) { + showError(lang(lng_bad_photo)); + return; + } + auto box = new PhotoCropBox(img, PeerId(0)); + connect(box, SIGNAL(ready(const QImage &)), this, SLOT(onPhotoReady(const QImage &))); + Ui::showLayer(box); } -void IntroSignup::resizeEvent(QResizeEvent *e) { - _phLeft = (width() - _next->width()) / 2; - _phTop = st::introTextTop + st::introTextSize.height() + st::introCountry.top; - if (e->oldSize().width() != width()) { - _next->move((width() - _next->width()) / 2, st::introBtnTop); - if (_invertOrder) { - _last->move((width() - _next->width()) / 2 + _next->width() - _last->width(), _phTop); - _first->move((width() - _next->width()) / 2 + _next->width() - _first->width(), _last->y() + st::introCountry.height + st::introCountry.ptrSize.height() + st::introPhoneTop); - } else { - _first->move((width() - _next->width()) / 2 + _next->width() - _first->width(), _phTop); - _last->move((width() - _next->width()) / 2 + _next->width() - _last->width(), _first->y() + st::introCountry.height + st::introCountry.ptrSize.height() + st::introPhoneTop); - } - } - _textRect = QRect((width() - st::introTextSize.width()) / 2, st::introTextTop, st::introTextSize.width(), st::introTextSize.height()); -} +void SignupWidget::resizeEvent(QResizeEvent *e) { + Step::resizeEvent(e); -void IntroSignup::showError(const QString &err) { - if (!_a_error.animating() && err == error) return; + auto photoRight = contentLeft() + st::introNextButton.width; + auto photoTop = contentTop() + st::introPhotoTop; + _photo->moveToLeft(photoRight - _photo->width(), photoTop); - if (err.length()) { - error = err; - a_errorAlpha.start(1); - } else { - a_errorAlpha.start(0); - } - _a_error.start(); -} - -void IntroSignup::step_error(float64 ms, bool timer) { - float64 dt = ms / st::introErrorDuration; - - if (dt >= 1) { - _a_error.stop(); - a_errorAlpha.finish(); - if (!a_errorAlpha.current()) { - error.clear(); - } - } else { - a_errorAlpha.update(dt, anim::linear); - } - if (timer) update(); -} - -void IntroSignup::step_photo(float64 ms, bool timer) { - float64 dt = ms / st::introErrorDuration; - - if (dt >= 1) { - _a_photo.stop(); - a_photoOver.finish(); - } else { - a_photoOver.update(dt, anim::linear); - } - if (timer) update(); -} - -void IntroSignup::activate() { - IntroStep::activate(); + auto firstTop = contentTop() + st::introStepFieldTop; + auto secondTop = firstTop + st::introName.height + st::introPhoneTop; if (_invertOrder) { + _last->moveToLeft(contentLeft(), firstTop); + _first->moveToLeft(contentLeft(), secondTop); + } else { + _first->moveToLeft(contentLeft(), firstTop); + _last->moveToLeft(contentLeft(), secondTop); + } +} + +void SignupWidget::setInnerFocus() { + if (_invertOrder || _last->hasFocus()) { _last->setFocus(); } else { _first->setFocus(); } } -void IntroSignup::cancelled() { - if (_sentRequest) { - MTP::cancel(base::take(_sentRequest)); - } +void SignupWidget::activate() { + Step::activate(); + _first->show(); + _last->show(); + _photo->show(); + setInnerFocus(); } -void IntroSignup::stopCheck() { +void SignupWidget::cancelled() { + MTP::cancel(base::take(_sentRequest)); +} + +void SignupWidget::stopCheck() { _checkRequest->stop(); } -void IntroSignup::onCheckRequest() { +void SignupWidget::onCheckRequest() { int32 status = MTP::state(_sentRequest); if (status < 0) { int32 leftms = -status; @@ -244,13 +150,12 @@ void IntroSignup::onCheckRequest() { } } -void IntroSignup::onPhotoReady(const QImage &img) { - _photoBig = img; - _photoSmall = App::pixmapFromImageInPlace(img.scaled(st::introPhotoSize * cIntRetinaFactor(), st::introPhotoSize * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - _photoSmall.setDevicePixelRatio(cRetinaFactor()); +void SignupWidget::onPhotoReady(const QImage &img) { + _photoImage = img; + _photo->setImage(_photoImage); } -void IntroSignup::nameSubmitDone(const MTPauth_Authorization &result) { +void SignupWidget::nameSubmitDone(const MTPauth_Authorization &result) { stopCheck(); _first->setDisabled(false); _last->setDisabled(false); @@ -259,10 +164,10 @@ void IntroSignup::nameSubmitDone(const MTPauth_Authorization &result) { showError(lang(lng_server_error)); return; } - intro()->finish(d.vuser, _photoBig); + finish(d.vuser, _photoImage); } -bool IntroSignup::nameSubmitFail(const RPCError &error) { +bool SignupWidget::nameSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { stopCheck(); _first->setDisabled(false); @@ -284,7 +189,7 @@ bool IntroSignup::nameSubmitFail(const RPCError &error) { if (err == qstr("PHONE_NUMBER_INVALID") || err == qstr("PHONE_CODE_EXPIRED") || err == qstr("PHONE_CODE_EMPTY") || err == qstr("PHONE_CODE_INVALID") || err == qstr("PHONE_NUMBER_OCCUPIED")) { - intro()->onBack(); + goBack(); return true; } else if (err == "FIRSTNAME_INVALID") { showError(lang(lng_bad_name)); @@ -308,29 +213,29 @@ bool IntroSignup::nameSubmitFail(const RPCError &error) { return false; } -void IntroSignup::onInputChange() { +void SignupWidget::onInputChange() { showError(QString()); } -void IntroSignup::onSubmitName(bool force) { +void SignupWidget::submit() { if (_invertOrder) { - if ((_last->hasFocus() || _last->text().trimmed().length()) && !_first->text().trimmed().length()) { + if ((_last->hasFocus() || _last->getLastText().trimmed().length()) && !_first->getLastText().trimmed().length()) { _first->setFocus(); return; - } else if (!_last->text().trimmed().length()) { + } else if (!_last->getLastText().trimmed().length()) { _last->setFocus(); return; } } else { - if ((_first->hasFocus() || _first->text().trimmed().length()) && !_last->text().trimmed().length()) { + if ((_first->hasFocus() || _first->getLastText().trimmed().length()) && !_last->getLastText().trimmed().length()) { _last->setFocus(); return; - } else if (!_first->text().trimmed().length()) { + } else if (!_first->getLastText().trimmed().length()) { _first->setFocus(); return; } } - if (!force && !_first->isEnabled()) return; + if (!_first->isEnabled()) return; _first->setDisabled(true); _last->setDisabled(true); @@ -338,11 +243,13 @@ void IntroSignup::onSubmitName(bool force) { showError(QString()); - _firstName = _first->text().trimmed(); - _lastName = _last->text().trimmed(); - _sentRequest = MTP::send(MTPauth_SignUp(MTP_string(intro()->getPhone()), MTP_string(intro()->getPhoneHash()), MTP_string(intro()->getCode()), MTP_string(_firstName), MTP_string(_lastName)), rpcDone(&IntroSignup::nameSubmitDone), rpcFail(&IntroSignup::nameSubmitFail)); + _firstName = _first->getLastText().trimmed(); + _lastName = _last->getLastText().trimmed(); + _sentRequest = MTP::send(MTPauth_SignUp(MTP_string(getData()->phone), MTP_string(getData()->phoneHash), MTP_string(getData()->code), MTP_string(_firstName), MTP_string(_lastName)), rpcDone(&SignupWidget::nameSubmitDone), rpcFail(&SignupWidget::nameSubmitFail)); } -void IntroSignup::onSubmit() { - onSubmitName(); +QString SignupWidget::nextButtonText() const { + return lang(lng_intro_finish); } + +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/introsignup.h b/Telegram/SourceFiles/intro/introsignup.h index bbb60453a..88ab525a7 100644 --- a/Telegram/SourceFiles/intro/introsignup.h +++ b/Telegram/SourceFiles/intro/introsignup.h @@ -21,64 +21,58 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "intro/introwidget.h" +#include "ui/filedialog.h" namespace Ui { class RoundButton; -class FlatInput; +class InputField; +class NewAvatarButton; } // namespace Ui -class IntroSignup final : public IntroStep { +namespace Intro { + +class SignupWidget : public Widget::Step, private base::Subscriber { Q_OBJECT public: - IntroSignup(IntroWidget *parent); - - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - - void step_error(float64 ms, bool timer); - void step_photo(float64 ms, bool timer); + SignupWidget(QWidget *parent, Widget::Data *data); + void setInnerFocus() override; void activate() override; void cancelled() override; - void onSubmit() override; + void submit() override; + QString nextButtonText() const override; - void nameSubmitDone(const MTPauth_Authorization &result); - bool nameSubmitFail(const RPCError &error); +protected: + void resizeEvent(QResizeEvent *e) override; -public slots: - void onSubmitName(bool force = false); +private slots: void onInputChange(); void onCheckRequest(); void onPhotoReady(const QImage &img); private: - void showError(const QString &err); + void notifyFileQueryUpdated(const FileDialog::QueryUpdate &update); + + void nameSubmitDone(const MTPauth_Authorization &result); + bool nameSubmitFail(const RPCError &error); + void stopCheck(); - QString error; - anim::fvalue a_errorAlpha, a_photoOver; - Animation _a_error; - Animation _a_photo; + QImage _photoImage; - ChildWidget _next; - - QRect _textRect; - - bool _photoOver = false; - QImage _photoBig; - QPixmap _photoSmall; - int32 _phLeft, _phTop; - - ChildWidget _first; - ChildWidget _last; + ChildWidget _photo; + ChildWidget _first; + ChildWidget _last; QString _firstName, _lastName; mtpRequestId _sentRequest = 0; + FileDialog::QueryId _readPhotoFileQueryId = 0; + bool _invertOrder = false; ChildObject _checkRequest; }; + +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/introstart.cpp b/Telegram/SourceFiles/intro/introstart.cpp index e6007793a..285168a51 100644 --- a/Telegram/SourceFiles/intro/introstart.cpp +++ b/Telegram/SourceFiles/intro/introstart.cpp @@ -24,67 +24,24 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "lang.h" #include "application.h" #include "intro/introphone.h" -#include "langloaderplain.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" -IntroStart::IntroStart(IntroWidget *parent) : IntroStep(parent) -, _intro(this, lang(lng_intro), Ui::FlatLabel::InitType::Rich, st::introLabel, st::introLabelTextStyle) -, _changeLang(this, QString()) -, _next(this, lang(lng_start_msgs), st::introNextButton) { - _changeLang->hide(); - if (cLang() == languageDefault) { - int32 l = Sandbox::LangSystem(); - if (l != languageDefault) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[l].c_str() + qsl(".strings"), langLoaderRequest(lng_switch_to_this)); - QString text = loader.found().value(lng_switch_to_this); - if (!text.isEmpty()) { - _changeLang->setText(text); - parent->langChangeTo(l); - _changeLang->show(); - } - } - } else { - _changeLang->setText(langOriginal(lng_switch_to_this)); - parent->langChangeTo(languageDefault); - _changeLang->show(); - } - - _headerWidth = st::introHeaderFont->width(qsl("Telegram Desktop")); - - setGeometry(parent->innerRect()); - - connect(_next, SIGNAL(clicked()), parent, SLOT(onStepSubmit())); - - connect(_changeLang, SIGNAL(clicked()), parent, SLOT(onChangeLang())); +namespace Intro { +StartWidget::StartWidget(QWidget *parent, Widget::Data *data) : Step(parent, data, true) { setMouseTracking(true); + setTitleText(qsl("Telegram Desktop")); + setDescriptionText(lang(lng_intro_about)); + show(); } -void IntroStart::paintEvent(QPaintEvent *e) { - bool trivial = (rect() == e->rect()); - - Painter p(this); - if (!trivial) { - p.setClipRect(e->rect()); - } - int32 hy = _intro->y() - st::introHeaderFont->height - st::introHeaderSkip + st::introHeaderFont->ascent; - - p.setFont(st::introHeaderFont); - p.setPen(st::introHeaderFg); - p.drawText((width() - _headerWidth) / 2, hy, qsl("Telegram Desktop")); - - st::introIcon.paint(p, QPoint((width() - st::introIcon.width()) / 2, hy - st::introIconSkip - st::introIcon.height()), width()); +void StartWidget::submit() { + goNext(new Intro::PhoneWidget(parentWidget(), getData())); } -void IntroStart::resizeEvent(QResizeEvent *e) { - if (e->oldSize().width() != width()) { - _next->move((width() - _next->width()) / 2, st::introBtnTop); - _intro->move((width() - _intro->width()) / 2, _next->y() - _intro->height() - st::introSkip); - _changeLang->move((width() - _changeLang->width()) / 2, _next->y() + _next->height() + _changeLang->height()); - } +QString StartWidget::nextButtonText() const { + return lang(lng_start_msgs); } -void IntroStart::onSubmit() { - intro()->nextStep(new IntroPhone(intro())); -} +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/introstart.h b/Telegram/SourceFiles/intro/introstart.h index dc254535d..f29d12073 100644 --- a/Telegram/SourceFiles/intro/introstart.h +++ b/Telegram/SourceFiles/intro/introstart.h @@ -28,22 +28,15 @@ class LinkButton; class RoundButton; } // namespace Ui -class IntroStart final : public IntroStep { +namespace Intro { + +class StartWidget : public Widget::Step { public: - IntroStart(IntroWidget *parent); + StartWidget(QWidget *parent, Widget::Data *data); - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - - void onSubmit() override; - -private: - ChildWidget _intro; - - ChildWidget _changeLang; - - ChildWidget _next; - - int32 _headerWidth = 0; + void submit() override; + QString nextButtonText() const override; }; + +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/introwidget.cpp b/Telegram/SourceFiles/intro/introwidget.cpp index 435d4c61d..5e708e9a5 100644 --- a/Telegram/SourceFiles/intro/introwidget.cpp +++ b/Telegram/SourceFiles/intro/introwidget.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "lang.h" #include "localstorage.h" +#include "langloaderplain.h" #include "intro/introstart.h" #include "intro/introphone.h" #include "intro/introcode.h" @@ -31,39 +32,60 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "mainwindow.h" #include "application.h" +#include "boxes/confirmbox.h" #include "ui/text/text.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" #include "ui/effects/widget_fade_wrap.h" -#include "styles/style_intro.h" +#include "ui/effects/slide_animation.h" #include "autoupdater.h" -#include "window/slide_animation.h" +#include "window/window_slide_animation.h" #include "styles/style_boxes.h" +#include "styles/style_intro.h" +#include "styles/style_window.h" -IntroWidget::IntroWidget(QWidget *parent) : TWidget(parent) -, _a_stage(animation(this, &IntroWidget::step_stage)) -, _a_show(animation(this, &IntroWidget::step_show)) +namespace Intro { + +Widget::Widget(QWidget *parent) : TWidget(parent) +, _a_show(animation(this, &Widget::step_show)) , _back(this, new Ui::IconButton(this, st::introBackButton), base::lambda(), st::introSlideDuration) -, _settings(this, lang(lng_menu_settings), st::defaultBoxButton) { - _back->entity()->setClickedCallback([this] { onBack(); }); +, _settings(this, new Ui::RoundButton(this, lang(lng_menu_settings), st::defaultBoxButton), base::lambda(), st::introCoverDuration) +, _next(this, QString(), st::introNextButton) { + getData()->country = psCurrentCountry(); + + _back->entity()->setClickedCallback([this] { historyMove(Direction::Back); }); _back->hideFast(); - _settings->setClickedCallback([] { App::wnd()->showSettings(); }); + _next->setClickedCallback([this] { getStep()->submit(); }); - _countryForReg = psCurrentCountry(); + _settings->entity()->setClickedCallback([] { App::wnd()->showSettings(); }); - MTP::send(MTPhelp_GetNearestDc(), rpcDone(&IntroWidget::gotNearestDC)); + if (cLang() == languageDefault) { + auto systemLangId = Sandbox::LangSystem(); + if (systemLangId != languageDefault) { + LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[systemLangId].c_str() + qsl(".strings"), langLoaderRequest(lng_switch_to_this)); + QString text = loader.found().value(lng_switch_to_this); + if (!text.isEmpty()) { + _changeLanguage.create(this, new Ui::LinkButton(this, text), base::lambda(), st::introCoverDuration); + _changeLanguage->entity()->setClickedCallback([this, systemLangId] { changeLanguage(systemLangId); }); + } + } + } else { + _changeLanguage.create(this, new Ui::LinkButton(this, langOriginal(lng_switch_to_this)), base::lambda(), st::introCoverDuration); + _changeLanguage->entity()->setClickedCallback([this] { changeLanguage(languageDefault); }); + } - _stepHistory.push_back(new IntroStart(this)); - _back->raise(); - _settings->raise(); + MTP::send(MTPhelp_GetNearestDc(), rpcDone(&Widget::gotNearestDC)); + + appendStep(new StartWidget(this, getData())); + fixOrder(); show(); - setFocus(); + showControls(); + getStep()->showFast(); cSetPasswordRecovered(false); - _back->moveToLeft(0, 0); - #ifndef TDESKTOP_DISABLE_AUTOUPDATE Sandbox::connect(SIGNAL(updateLatest()), this, SLOT(onCheckUpdateStatus())); Sandbox::connect(SIGNAL(updateFailed()), this, SLOT(onCheckUpdateStatus())); @@ -74,12 +96,12 @@ IntroWidget::IntroWidget(QWidget *parent) : TWidget(parent) } #ifndef TDESKTOP_DISABLE_AUTOUPDATE -void IntroWidget::onCheckUpdateStatus() { +void Widget::onCheckUpdateStatus() { if (Sandbox::updatingState() == Application::UpdatingReady) { if (_update) return; - _update.create(this, lang(lng_menu_update).toUpper(), st::defaultBoxButton); + _update.create(this, new Ui::RoundButton(this, lang(lng_menu_update).toUpper(), st::defaultBoxButton), base::lambda(), st::introCoverDuration); if (!_a_show.animating()) _update->show(); - _update->setClickedCallback([] { + _update->entity()->setClickedCallback([] { checkReadyUpdate(); App::restart(); }); @@ -91,123 +113,204 @@ void IntroWidget::onCheckUpdateStatus() { } #endif // TDESKTOP_DISABLE_AUTOUPDATE -void IntroWidget::langChangeTo(int32 langId) { - _langChangeTo = langId; -} - -void IntroWidget::onChangeLang() { - cSetLang(_langChangeTo); +void Widget::changeLanguage(int32 languageId) { + cSetLang(languageId); Local::writeSettings(); App::restart(); } -void IntroWidget::onStepSubmit() { - step()->onSubmit(); +void Widget::setInnerFocus() { + if (getStep()->animating()) { + setFocus(); + } else { + getStep()->setInnerFocus(); + } } -void IntroWidget::onBack() { - historyMove(MoveBack); -} - -void IntroWidget::historyMove(MoveType type) { - if (_a_stage.animating()) return; +void Widget::historyMove(Direction direction) { + if (getStep()->animating()) return; t_assert(_stepHistory.size() > 1); - if (App::app()) App::app()->mtpPause(); - - switch (type) { - case MoveBack: { - _cacheHide = grabStep(); - - IntroStep *back = step(); - _backFrom = back->hasBack() ? 1 : 0; + auto wasStep = getStep((direction == Direction::Back) ? 0 : 1); + if (direction == Direction::Back) { _stepHistory.pop_back(); - back->cancelled(); - delete back; - } break; - - case MoveForward: { - _cacheHide = grabStep(1); - _backFrom = step(1)->hasBack() ? 1 : 0; - step(1)->finished(); - } break; - - case MoveReplace: { - _cacheHide = grabStep(1); - IntroStep *replaced = step(1); - _backFrom = replaced->hasBack() ? 1 : 0; + wasStep->cancelled(); + } else if (direction == Direction::Replace) { _stepHistory.removeAt(_stepHistory.size() - 2); - replaced->finished(); - delete replaced; - } break; + } + getStep()->prepareShowAnimated(wasStep); + if (wasStep->hasCover() != getStep()->hasCover()) { + _nextTopFrom = wasStep->contentTop() + st::introStepHeight; + _controlsTopFrom = wasStep->hasCover() ? st::introCoverHeight : 0; + _coverShownAnimation.start([this] { updateControlsGeometry(); }, 0., 1., st::introCoverDuration, anim::easeOutCirc); } - _cacheShow = grabStep(); - _backTo = step()->hasBack() ? 1 : 0; - - int32 m = (type == MoveBack) ? -1 : 1; - a_coordHide = anim::ivalue(0, -m * st::introSlideShift); - a_opacityHide = anim::fvalue(1, 0); - a_coordShow = anim::ivalue(m * st::introSlideShift, 0); - a_opacityShow = anim::fvalue(0, 1); - _a_stage.start(); - - _a_stage.step(); - if (_backTo) { + if (direction == Direction::Forward || direction == Direction::Replace) { + wasStep->finished(); + } + if (direction == Direction::Back || direction == Direction::Replace) { + delete base::take(wasStep); + } + if (getStep()->hasBack()) { _back->fadeIn(); } else { _back->fadeOut(); } - step()->hide(); + if (getStep()->hasCover()) { + _settings->fadeOut(); + if (_update) _update->fadeOut(); + if (_changeLanguage) _changeLanguage->fadeIn(); + } else { + _settings->fadeIn(); + if (_update) _update->fadeIn(); + if (_changeLanguage) _changeLanguage->fadeOut(); + } + _next->setText(getStep()->nextButtonText()); + if (_resetAccount) _resetAccount->fadeOut(); + getStep()->showAnimated(direction); + fixOrder(); } -void IntroWidget::pushStep(IntroStep *step, MoveType type) { - _stepHistory.push_back(step); +void Widget::fixOrder() { + _next->raise(); + if (_update) _update->raise(); + _settings->raise(); + _back->raise(); +} + +void Widget::moveToStep(Step *step, Direction direction) { + appendStep(step); _back->raise(); _settings->raise(); if (_update) { _update->raise(); } - _stepHistory.back()->hide(); - historyMove(type); + historyMove(direction); } -void IntroWidget::gotNearestDC(const MTPNearestDc &result) { - const auto &nearest(result.c_nearestDc()); +void Widget::appendStep(Step *step) { + _stepHistory.push_back(step); + step->setGeometry(calculateStepRect()); + step->setGoCallback([this](Step *step, Direction direction) { + if (direction == Direction::Back) { + historyMove(direction); + } else { + moveToStep(step, direction); + } + }); + step->setShowResetCallback([this] { + showResetButton(); + }); +} + +void Widget::showResetButton() { + if (!_resetAccount) { + _resetAccount.create(this, new Ui::RoundButton(this, lang(lng_signin_reset_account), st::introResetButton), base::lambda(), st::introErrorDuration); + _resetAccount->hideFast(); + _resetAccount->entity()->setClickedCallback([this] { resetAccount(); }); + updateControlsGeometry(); + } + _resetAccount->fadeIn(); +} + +void Widget::resetAccount() { + if (_resetRequest) return; + + auto box = new ConfirmBox(lang(lng_signin_sure_reset), lang(lng_signin_reset), st::attentionBoxButton); + box->setConfirmedCallback([this] { resetAccountSure(); }); + Ui::showLayer(box); +} + +void Widget::resetAccountSure() { + if (_resetRequest) return; + _resetRequest = MTP::send(MTPaccount_DeleteAccount(MTP_string("Forgot password")), rpcDone(&Widget::resetDone), rpcFail(&Widget::resetFail)); +} + +void Widget::resetDone(const MTPBool &result) { + Ui::hideLayer(); + moveToStep(new SignupWidget(this, getData()), Direction::Replace); +} + +bool Widget::resetFail(const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + + _resetRequest = 0; + + auto type = error.type(); + if (type.startsWith(qstr("2FA_CONFIRM_WAIT_"))) { + int seconds = type.mid(qstr("2FA_CONFIRM_WAIT_").size()).toInt(); + int days = (seconds + 59) / 86400; + int hours = ((seconds + 59) % 86400) / 3600; + int minutes = ((seconds + 59) % 3600) / 60; + QString when; + if (days > 0) { + when = lng_signin_reset_in_days(lt_count_days, days, lt_count_hours, hours, lt_count_minutes, minutes); + } else if (hours > 0) { + when = lng_signin_reset_in_hours(lt_count_hours, hours, lt_count_minutes, minutes); + } else { + when = lng_signin_reset_in_minutes(lt_count_minutes, minutes); + } + Ui::showLayer(new InformBox(lng_signin_reset_wait(lt_phone_number, App::formatPhone(getData()->phone), lt_when, when))); + } else if (type == qstr("2FA_RECENT_CONFIRM")) { + Ui::showLayer(new InformBox(lang(lng_signin_reset_cancelled))); + } else { + Ui::hideLayer(); + getStep()->showError(lang(lng_server_error)); + } + return true; +} + +void Widget::gotNearestDC(const MTPNearestDc &result) { + auto &nearest = result.c_nearestDc(); DEBUG_LOG(("Got nearest dc, country: %1, nearest: %2, this: %3").arg(nearest.vcountry.c_string().v.c_str()).arg(nearest.vnearest_dc.v).arg(nearest.vthis_dc.v)); - MTP::setdc(result.c_nearestDc().vnearest_dc.v, true); - if (_countryForReg != nearest.vcountry.c_string().v.c_str()) { - _countryForReg = nearest.vcountry.c_string().v.c_str(); - emit countryChanged(); + MTP::setdc(nearest.vnearest_dc.v, true); + auto nearestCountry = qs(nearest.vcountry); + if (getData()->country != nearestCountry) { + getData()->country = nearestCountry; + getData()->updated.notify(); } } -QPixmap IntroWidget::grabStep(int skip) { - return myGrab(step(skip), QRect(st::introSlideShift, 0, st::introSize.width(), st::introSize.height())); +void Widget::showControls() { + getStep()->show(); + _next->show(); + _next->setText(getStep()->nextButtonText()); + if (getStep()->hasCover()) { + _settings->hideFast(); + if (_update) _update->hideFast(); + if (_changeLanguage) _changeLanguage->showFast(); + } else { + _settings->showFast(); + if (_update) _update->showFast(); + if (_changeLanguage) _changeLanguage->hideFast(); + } + if (getStep()->hasBack()) { + _back->showFast(); + } else { + _back->hideFast(); + } } -void IntroWidget::animShow(const QPixmap &bgAnimCache, bool back) { +void Widget::hideControls() { + getStep()->hide(); + _next->hide(); + _settings->hideFast(); + if (_update) _update->hideFast(); + if (_changeLanguage) _changeLanguage->hideFast(); + _back->hideFast(); +} + +void Widget::animShow(const QPixmap &bgAnimCache, bool back) { if (App::app()) App::app()->mtpPause(); (back ? _cacheOver : _cacheUnder) = bgAnimCache; _a_show.stop(); - step()->show(); - _settings->show(); - if (_update) _update->show(); - if (step()->hasBack()) { - _back->showFast(); - } else { - _back->hideFast(); - } + showControls(); (back ? _cacheUnder : _cacheOver) = myGrab(this); - - step()->hide(); - _back->hideFast(); - _settings->hide(); - if (_update) _update->hide(); + hideControls(); a_coordUnder = back ? anim::ivalue(-st::slideShift, 0) : anim::ivalue(0, -st::slideShift); a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0); @@ -217,7 +320,7 @@ void IntroWidget::animShow(const QPixmap &bgAnimCache, bool back) { show(); } -void IntroWidget::step_show(float64 ms, bool timer) { +void Widget::step_show(float64 ms, bool timer) { float64 dt = ms / st::slideDuration; if (dt >= 1) { _a_show.stop(); @@ -228,13 +331,9 @@ void IntroWidget::step_show(float64 ms, bool timer) { _cacheUnder = _cacheOver = QPixmap(); - setFocus(); - step()->activate(); - if (step()->hasBack()) { - _back->showFast(); - } - _settings->show(); - if (_update) _update->show(); + showControls(); + getStep()->activate(); + if (App::app()) App::app()->mtpUnpause(); } else { a_coordUnder.update(dt, Window::SlideAnimation::transition()); @@ -244,37 +343,14 @@ void IntroWidget::step_show(float64 ms, bool timer) { if (timer) update(); } -void IntroWidget::stop_show() { - _a_show.stop(); -} - -void IntroWidget::step_stage(float64 ms, bool timer) { - float64 fullDuration = st::introSlideDelta + st::introSlideDuration, dt = ms / fullDuration; - float64 dt1 = (ms > st::introSlideDuration) ? 1 : (ms / st::introSlideDuration), dt2 = (ms > st::introSlideDelta) ? (ms - st::introSlideDelta) / (st::introSlideDuration) : 0; - if (dt >= 1) { - _a_stage.stop(); - - a_coordShow.finish(); - a_opacityShow.finish(); - - _cacheHide = _cacheShow = QPixmap(); - - setFocus(); - step()->activate(); - if (App::app()) App::app()->mtpUnpause(); - } else { - a_coordShow.update(dt2, anim::easeOutCirc); - a_opacityShow.update(dt2, anim::easeInCirc); - a_coordHide.update(dt1, anim::easeInCirc); - a_opacityHide.update(dt1, anim::easeOutCirc); - } - if (timer) update(); -} - -void IntroWidget::paintEvent(QPaintEvent *e) { +void Widget::paintEvent(QPaintEvent *e) { bool trivial = (rect() == e->rect()); setMouseTracking(true); + if (_coverShownAnimation.animating()) { + _coverShownAnimation.step(getms()); + } + QPainter p(this); if (!trivial) { p.setClipRect(e->rect()); @@ -290,130 +366,406 @@ void IntroWidget::paintEvent(QPaintEvent *e) { p.drawPixmap(a_coordOver.current(), 0, _cacheOver); p.setOpacity(a_shadow.current()); st::slideShadow.fill(p, QRect(a_coordOver.current() - st::slideShadow.width(), 0, st::slideShadow.width(), height())); - } else if (_a_stage.animating()) { - p.setOpacity(a_opacityHide.current()); - p.drawPixmap(step()->x() + st::introSlideShift + a_coordHide.current(), step()->y(), _cacheHide); - p.setOpacity(a_opacityShow.current()); - p.drawPixmap(step()->x() + st::introSlideShift + a_coordShow.current(), step()->y(), _cacheShow); } } -QRect IntroWidget::innerRect() const { - int innerWidth = st::introSize.width() + 2 * st::introSlideShift, innerHeight = st::introSize.height(); - return QRect((width() - innerWidth) / 2, (height() - innerHeight) / 2, innerWidth, (height() + innerHeight) / 2); +QRect Widget::calculateStepRect() const { + auto stepInnerTop = (height() - st::introHeight) / 2; + accumulate_max(stepInnerTop, st::introStepTopMin); + auto nextTop = stepInnerTop + st::introStepHeight; + auto additionalHeight = st::introStepHeightAdd; + auto stepWidth = width(); + auto stepHeight = nextTop + additionalHeight; + return QRect(0, 0, stepWidth, stepHeight); } -QString IntroWidget::currentCountry() const { - return _countryForReg; -} - -void IntroWidget::setPhone(const QString &phone, const QString &phone_hash, bool registered) { - _phone = phone; - _phone_hash = phone_hash; - _registered = registered; -} - -void IntroWidget::setCode(const QString &code) { - _code = code; -} - -void IntroWidget::setPwdSalt(const QByteArray &salt) { - _pwdSalt = salt; -} - -void IntroWidget::setHasRecovery(bool has) { - _hasRecovery = has; -} - -void IntroWidget::setPwdHint(const QString &hint) { - _pwdHint = hint; -} - -void IntroWidget::setCodeByTelegram(bool byTelegram) { - _codeByTelegram = byTelegram; -} - -void IntroWidget::setCallStatus(const CallStatus &status) { - _callStatus = status; -} - -const QString &IntroWidget::getPhone() const { - return _phone; -} - -const QString &IntroWidget::getPhoneHash() const { - return _phone_hash; -} - -const QString &IntroWidget::getCode() const { - return _code; -} - -const IntroWidget::CallStatus &IntroWidget::getCallStatus() const { - return _callStatus; -} - -const QByteArray &IntroWidget::getPwdSalt() const { - return _pwdSalt; -} - -bool IntroWidget::getHasRecovery() const { - return _hasRecovery; -} - -const QString &IntroWidget::getPwdHint() const { - return _pwdHint; -} - -bool IntroWidget::codeByTelegram() const { - return _codeByTelegram; -} - -void IntroWidget::resizeEvent(QResizeEvent *e) { - auto r = innerRect(); - for (auto step : _stepHistory) { - step->setGeometry(r); +void Widget::resizeEvent(QResizeEvent *e) { + auto stepRect = calculateStepRect(); + for_const (auto step, _stepHistory) { + step->setGeometry(stepRect); } + updateControlsGeometry(); } -void IntroWidget::updateControlsGeometry() { - _settings->moveToRight(st::boxButtonPadding.right(), st::boxButtonPadding.top()); +void Widget::moveControls() { +} + +void Widget::updateControlsGeometry() { + auto shown = _coverShownAnimation.current(1.); + + auto controlsTopTo = getStep()->hasCover() ? st::introCoverHeight : 0; + auto controlsTop = anim::interpolate(_controlsTopFrom, controlsTopTo, shown); + _settings->moveToRight(st::introSettingsSkip, controlsTop + st::introSettingsSkip); if (_update) { - _update->moveToRight(st::boxButtonPadding.right() + _settings->width() + st::boxButtonPadding.left(), _settings->y()); + _update->moveToRight(st::introSettingsSkip + _settings->width() + st::introSettingsSkip, _settings->y()); + } + _back->moveToLeft(0, controlsTop); + + auto nextTopTo = getStep()->contentTop() + st::introStepHeight; + auto nextTop = anim::interpolate(_nextTopFrom, nextTopTo, shown); + _next->moveToLeft((width() - _next->width()) / 2, nextTop); + if (_changeLanguage) { + _changeLanguage->moveToLeft((width() - _changeLanguage->width()) / 2, _next->y() + _next->height() + _changeLanguage->height()); + } + if (_resetAccount) { + _resetAccount->moveToLeft((width() - _resetAccount->width()) / 2, height() - st::introResetBottom - _resetAccount->height()); } } -void IntroWidget::finish(const MTPUser &user, const QImage &photo) { + +void Widget::keyPressEvent(QKeyEvent *e) { + if (_a_show.animating() || getStep()->animating()) return; + + if (e->key() == Qt::Key_Escape) { + if (getStep()->hasBack()) { + historyMove(Direction::Back); + } + } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) { + getStep()->submit(); + } +} + +Widget::~Widget() { + for (auto step : base::take(_stepHistory)) { + delete step; + } + if (App::wnd()) App::wnd()->noIntro(this); +} + +QString Widget::Step::nextButtonText() const { + return lang(lng_intro_next); +} + +void Widget::Step::finish(const MTPUser &user, QImage photo) { App::wnd()->setupMain(&user); + + // "this" is already deleted here by creating the main widget. if (!photo.isNull()) { App::app()->uploadProfilePhoto(photo, MTP::authedId()); } } -void IntroWidget::keyPressEvent(QKeyEvent *e) { - if (_a_show.animating() || _a_stage.animating()) return; +void Widget::Step::paintEvent(QPaintEvent *e) { + Painter p(this); + paintAnimated(p, e->rect()); +} - if (e->key() == Qt::Key_Escape) { - if (step()->hasBack()) { - onBack(); +void Widget::Step::resizeEvent(QResizeEvent *e) { + updateLabelsPosition(); +} + +void Widget::Step::updateLabelsPosition() { + myEnsureResized(_description->entity()); + if (hasCover()) { + _title->moveToLeft((width() - _title->width()) / 2, contentTop() + st::introCoverTitleTop); + _description->moveToLeft((width() - _description->width()) / 2, contentTop() + st::introCoverDescriptionTop); + } else { + _title->moveToLeft(contentLeft() + st::buttonRadius, contentTop() + st::introTitleTop); + _description->moveToLeft(contentLeft() + st::buttonRadius, contentTop() + st::introDescriptionTop); + } + if (_error) { + if (_errorCentered) { + _error->entity()->resizeToWidth(width()); } - } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) { - onStepSubmit(); + myEnsureResized(_error->entity()); + auto errorLeft = _errorCentered ? 0 : (contentLeft() + st::buttonRadius); + auto errorTop = contentTop() + (_errorBelowLink ? st::introErrorBelowLinkTop : st::introErrorTop); + _error->moveToLeft(errorLeft, errorTop); } } -void IntroWidget::rpcClear() { - for (IntroStep *step : _stepHistory) { - step->rpcClear(); +void Widget::Step::setTitleText(QString richText) { + _title->setRichText(richText); + updateLabelsPosition(); +} + +void Widget::Step::setDescriptionText(QString richText) { + _description->entity()->setRichText(richText); + updateLabelsPosition(); +} + +void Widget::Step::showFinished() { + _a_show.finish(); + _coverAnimation = CoverAnimation(); + _slideAnimation.reset(); + prepareCoverMask(); + activate(); + if (App::app()) App::app()->mtpUnpause(); +} + +bool Widget::Step::paintAnimated(Painter &p, QRect clip) { + if (_slideAnimation) { + _slideAnimation->paintFrame(p, (width() - st::introStepWidth) / 2, contentTop(), width(), getms()); + if (!_slideAnimation->animating()) { + showFinished(); + return false; + } + return true; + } + + auto guard = base::scope_guard([this, &p] { + if (hasCover()) paintCover(p, 0); + }); + + auto dt = _a_show.current(getms(), 1.); + if (!_a_show.animating()) { + if (_coverAnimation.title) { + showFinished(); + } + if (!QRect(0, contentTop(), width(), st::introStepHeight).intersects(clip)) { + return true; + } + return false; + } + + auto easeOut = anim::easeOutCirc(1., dt); + auto arrivingAlpha = easeOut; + auto departingAlpha = 1. - easeOut; + auto showCoverMethod = easeOut; + auto hideCoverMethod = easeOut; + auto coverTop = (hasCover() ? anim::interpolate(-st::introCoverHeight, 0, showCoverMethod) : anim::interpolate(0, -st::introCoverHeight, hideCoverMethod)); + + paintCover(p, coverTop); + guard.dismiss(); + + auto positionReady = hasCover() ? showCoverMethod : hideCoverMethod; + _coverAnimation.title->paintFrame(p, positionReady, departingAlpha, arrivingAlpha); + _coverAnimation.description->paintFrame(p, positionReady, departingAlpha, arrivingAlpha); + + paintContentSnapshot(p, _coverAnimation.contentSnapshotWas, departingAlpha, showCoverMethod); + paintContentSnapshot(p, _coverAnimation.contentSnapshotNow, arrivingAlpha, 1. - hideCoverMethod); + + return true; +} + +void Widget::Step::fillSentCodeData(const MTPauth_SentCodeType &type) { + switch (type.type()) { + case mtpc_auth_sentCodeTypeApp: { + getData()->codeByTelegram = true; + getData()->codeLength = type.c_auth_sentCodeTypeApp().vlength.v; + } break; + case mtpc_auth_sentCodeTypeSms: { + getData()->codeByTelegram = false; + getData()->codeLength = type.c_auth_sentCodeTypeSms().vlength.v; + } break; + case mtpc_auth_sentCodeTypeCall: { + getData()->codeByTelegram = false; + getData()->codeLength = type.c_auth_sentCodeTypeCall().vlength.v; + } break; + case mtpc_auth_sentCodeTypeFlashCall: LOG(("Error: should not be flashcall!")); break; } } -IntroWidget::~IntroWidget() { - while (!_stepHistory.isEmpty()) { - IntroStep *back = _stepHistory.back(); - _stepHistory.pop_back(); - delete back; - } - if (App::wnd()) App::wnd()->noIntro(this); +void Widget::Step::showDescription() { + _description->fadeIn(); } + +void Widget::Step::hideDescription() { + _description->fadeOut(); +} + +void Widget::Step::paintContentSnapshot(Painter &p, const QPixmap &snapshot, float64 alpha, float64 howMuchHidden) { + if (!snapshot.isNull()) { + auto contentTop = anim::interpolate(height() - (snapshot.height() / cIntRetinaFactor()), height(), howMuchHidden); + if (contentTop < height()) { + p.setOpacity(alpha); + p.drawPixmap(QPoint(contentLeft(), contentTop), snapshot, QRect(0, 0, snapshot.width(), (height() - contentTop) * cIntRetinaFactor())); + } + } +} + +void Widget::Step::prepareCoverMask() { + if (!_coverMask.isNull()) return; + + auto maskWidth = cIntRetinaFactor(); + auto maskHeight = st::introCoverHeight * cIntRetinaFactor(); + auto mask = QImage(maskWidth, maskHeight, QImage::Format_ARGB32_Premultiplied); + auto maskInts = reinterpret_cast(mask.bits()); + t_assert(mask.depth() == (sizeof(uint32) << 3)); + auto maskIntsPerLineAdded = (mask.bytesPerLine() >> 2) - maskWidth; + t_assert(maskIntsPerLineAdded >= 0); + auto realHeight = static_cast(maskHeight - 1); + for (auto y = 0; y != maskHeight; ++y) { + auto color = anim::color(st::introCoverTopBg, st::introCoverBottomBg, y / realHeight); + auto colorInt = anim::getPremultiplied(color); + for (auto x = 0; x != maskWidth; ++x) { + *maskInts++ = colorInt; + } + maskInts += maskIntsPerLineAdded; + } + _coverMask = App::pixmapFromImageInPlace(std_::move(mask)); +} + +void Widget::Step::paintCover(Painter &p, int top) { + auto coverHeight = top + st::introCoverHeight; + if (coverHeight > 0) { + p.drawPixmap(QRect(0, 0, width(), coverHeight), _coverMask, QRect(0, -top * cIntRetinaFactor(), _coverMask.width(), coverHeight * cIntRetinaFactor())); + } + + auto left = 0; + auto right = 0; + if (width() < st::introCoverMaxWidth) { + auto iconsMaxSkip = st::introCoverMaxWidth - st::introCoverLeft.width() - st::introCoverRight.width(); + auto iconsSkip = st::introCoverIconsMinSkip + (iconsMaxSkip - st::introCoverIconsMinSkip) * (width() - st::introStepWidth) / (st::introCoverMaxWidth - st::introStepWidth); + auto outside = iconsSkip + st::introCoverLeft.width() + st::introCoverRight.width() - width(); + left = -outside / 2; + right = -outside - left; + } + if (top < 0) { + auto shown = float64(coverHeight) / st::introCoverHeight; + auto leftShown = qRound(shown * (left + st::introCoverLeft.width())); + left = leftShown - st::introCoverLeft.width(); + auto rightShown = qRound(shown * (right + st::introCoverRight.width())); + right = rightShown - st::introCoverRight.width(); + } + st::introCoverLeft.paint(p, left, coverHeight - st::introCoverLeft.height(), width()); + st::introCoverRight.paint(p, width() - right - st::introCoverRight.width(), coverHeight - st::introCoverRight.height(), width()); + + auto planeLeft = (width() - st::introCoverIcon.width()) / 2 - st::introCoverIconLeft; + auto planeTop = top + st::introCoverIconTop; + if (top < 0 && !_hasCover) { + auto deltaLeft = -qRound(float64(st::introPlaneWidth / st::introPlaneHeight) * top); +// auto deltaTop = top; + planeLeft += deltaLeft; + // planeTop += top; + } + st::introCoverIcon.paint(p, planeLeft, planeTop, width()); +} + +int Widget::Step::contentLeft() const { + return (width() - st::introNextButton.width) / 2; +} + +int Widget::Step::contentTop() const { + auto result = height() - st::introStepHeight - st::introStepHeightAdd; + if (_hasCover) { + auto added = 1. - snap(float64(height() - st::windowMinHeight) / (st::introStepHeightFull - st::windowMinHeight), 0., 1.); + result += qRound(added * st::introStepHeightAdd); + } + return result; +} + +void Widget::Step::setErrorCentered(bool centered) { + _errorCentered = centered; + _error.destroy(); +} + +void Widget::Step::setErrorBelowLink(bool below) { + _errorBelowLink = below; + if (_error) { + updateLabelsPosition(); + } +} + +void Widget::Step::showError(const QString &text) { + _errorText = text; + if (_errorText.isEmpty()) { + if (_error) _error->fadeOut(); + } else { + if (!_error) { + auto &st = _errorCentered ? st::introErrorCentered : st::introError; + _error.create(this, new Ui::FlatLabel(this, st, st::introErrorTextStyle), base::lambda(), st::introErrorDuration); + _error->hideFast(); + } + _error->entity()->setText(text); + updateLabelsPosition(); + _error->fadeIn(); + } +} + +Widget::Step::Step(QWidget *parent, Data *data, bool hasCover) : TWidget(parent) +, _data(data) +, _hasCover(hasCover) +, _title(this, _hasCover ? st::introCoverTitle : st::introTitle, st::defaultTextStyle) +, _description(this, new Ui::FlatLabel(this, _hasCover ? st::introCoverDescription : st::introDescription, _hasCover ? st::introCoverDescriptionTextStyle : st::introDescriptionTextStyle), base::lambda(), st::introErrorDuration) { + hide(); +} + +void Widget::Step::prepareShowAnimated(Step *after) { + if (hasCover() || after->hasCover()) { + _coverAnimation = prepareCoverAnimation(after); + prepareCoverMask(); + } else { + auto leftSnapshot = after->prepareSlideAnimation(); + auto rightSnapshot = prepareSlideAnimation(); + _slideAnimation = std_::make_unique(); + _slideAnimation->setSnapshots(std_::move(leftSnapshot), std_::move(rightSnapshot)); + _slideAnimation->setOverflowHidden(false); + } +} + +Widget::Step::CoverAnimation Widget::Step::prepareCoverAnimation(Step *after) { + auto result = CoverAnimation(); + result.title = Ui::FlatLabel::CrossFade(after->_title, _title, st::introBg); + result.description = Ui::FlatLabel::CrossFade(after->_description->entity(), _description->entity(), st::introBg, after->_description->pos(), _description->pos()); + result.contentSnapshotWas = after->prepareContentSnapshot(); + result.contentSnapshotNow = prepareContentSnapshot(); + return std_::move(result); +} + +QPixmap Widget::Step::prepareContentSnapshot() { + auto otherTop = _description->y() + _description->height(); + auto otherRect = myrtlrect(contentLeft(), otherTop, st::introStepWidth, height() - otherTop); + return myGrab(this, otherRect); +} + +QPixmap Widget::Step::prepareSlideAnimation() { + auto grabLeft = (width() - st::introStepWidth) / 2; + auto grabTop = contentTop(); + return myGrab(this, QRect(grabLeft, grabTop, st::introStepWidth, st::introStepHeight)); +} + +void Widget::Step::showAnimated(Direction direction) { + show(); + if (App::app()) App::app()->mtpPause(); + hideChildren(); + if (_slideAnimation) { + auto slideLeft = (direction == Direction::Back); + _slideAnimation->start(slideLeft, [this] { update(0, contentTop(), width(), st::introStepHeight); }, st::introSlideDuration); + } else { + _a_show.start([this] { update(); }, 0., 1., st::introCoverDuration); + } +} + +void Widget::Step::setGoCallback(base::lambda &&callback) { + _goCallback = std_::move(callback); +} + +void Widget::Step::setShowResetCallback(base::lambda &&callback) { + _showResetCallback = std_::move(callback); +} + +void Widget::Step::showFast() { + show(); + showFinished(); +} + +bool Widget::Step::animating() const { + return (_slideAnimation && _slideAnimation->animating()) || _a_show.animating(); +} + +bool Widget::Step::hasCover() const { + return _hasCover; +} + +bool Widget::Step::hasBack() const { + return false; +} + +void Widget::Step::activate() { + _title->show(); + _description->show(); + if (!_errorText.isEmpty()) { + _error->showFast(); + } +} + +void Widget::Step::cancelled() { +} + +void Widget::Step::finished() { + hide(); +} + +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/introwidget.h b/Telegram/SourceFiles/intro/introwidget.h index ed639e01b..07c0addd4 100644 --- a/Telegram/SourceFiles/intro/introwidget.h +++ b/Telegram/SourceFiles/intro/introwidget.h @@ -23,77 +23,32 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Ui { class IconButton; class RoundButton; +class LinkButton; +class SlideAnimation; +class CrossFadeAnimation; +class FlatLabel; template class WidgetFadeWrap; } // namespace Ui -class IntroStep; -class IntroWidget : public TWidget, public RPCSender { +namespace Intro { + +class Widget : public TWidget, public RPCSender { Q_OBJECT public: - IntroWidget(QWidget *window); + Widget(QWidget *parent); void animShow(const QPixmap &bgAnimCache, bool back = false); - void step_show(float64 ms, bool timer); - void stop_show(); + void setInnerFocus(); - void step_stage(float64 ms, bool timer); - - QRect innerRect() const; - QString currentCountry() const; - - enum CallStatusType { - CallWaiting, - CallCalling, - CallCalled, - CallDisabled, - }; - struct CallStatus { - CallStatusType type; - int timeout; - }; - void setPhone(const QString &phone, const QString &phone_hash, bool registered); - void setCode(const QString &code); - void setCallStatus(const CallStatus &status); - void setPwdSalt(const QByteArray &salt); - void setHasRecovery(bool hasRecovery); - void setPwdHint(const QString &hint); - void setCodeByTelegram(bool byTelegram); - - const QString &getPhone() const; - const QString &getPhoneHash() const; - const QString &getCode() const; - const CallStatus &getCallStatus() const; - const QByteArray &getPwdSalt() const; - bool getHasRecovery() const; - const QString &getPwdHint() const; - bool codeByTelegram() const; - - void finish(const MTPUser &user, const QImage &photo = QImage()); - - void rpcClear() override; - void langChangeTo(int32 langId); - - void nextStep(IntroStep *step) { - pushStep(step, MoveForward); - } - void replaceStep(IntroStep *step) { - pushStep(step, MoveReplace); - } - - ~IntroWidget(); + ~Widget(); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; -public slots: - void onStepSubmit(); - void onBack(); - void onChangeLang(); - signals: void countryChanged(); @@ -102,86 +57,199 @@ private slots: void onCheckUpdateStatus(); #endif // TDESKTOP_DISABLE_AUTOUPDATE + // Internal interface. +public: + struct Data { + QString country; + QString phone; + QString phoneHash; + bool phoneIsRegistered = false; + + enum class CallStatus { + Waiting, + Calling, + Called, + Disabled, + }; + CallStatus callStatus = CallStatus::Disabled; + int callTimeout = 0; + + QString code; + int codeLength = 5; + bool codeByTelegram = false; + + QByteArray pwdSalt; + bool hasRecovery = false; + QString pwdHint; + + base::Observable updated; + + }; + + enum class Direction { + Back, + Forward, + Replace, + }; + class Step : public TWidget, public RPCSender { + public: + Step(QWidget *parent, Data *data, bool hasCover = false); + + virtual void setInnerFocus() { + setFocus(); + } + + void setGoCallback(base::lambda &&callback); + void setShowResetCallback(base::lambda &&callback); + + void prepareShowAnimated(Step *after); + void showAnimated(Direction direction); + void showFast(); + bool animating() const; + + bool hasCover() const; + virtual bool hasBack() const; + virtual void activate(); + virtual void cancelled(); + virtual void finished(); + + virtual void submit() = 0; + virtual QString nextButtonText() const; + + int contentLeft() const; + int contentTop() const; + + void setErrorCentered(bool centered); + void setErrorBelowLink(bool below); + void showError(const QString &text); + void hideError() { + showError(QString()); + } + + protected: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + + void setTitleText(QString richText); + void setDescriptionText(QString richText); + bool paintAnimated(Painter &p, QRect clip); + + void fillSentCodeData(const MTPauth_SentCodeType &type); + + void showDescription(); + void hideDescription(); + + Data *getData() const { + return _data; + } + void finish(const MTPUser &user, QImage photo = QImage()); + + void goBack() { + if (_goCallback) _goCallback(nullptr, Direction::Back); + } + void goNext(Step *step) { + if (_goCallback) _goCallback(step, Direction::Forward); + } + void goReplace(Step *step) { + if (_goCallback) _goCallback(step, Direction::Replace); + } + void showResetButton() { + if (_showResetCallback) _showResetCallback(); + } + + private: + struct CoverAnimation { + std_::unique_ptr title; + std_::unique_ptr description; + + // From content top till the next button top. + QPixmap contentSnapshotWas; + QPixmap contentSnapshotNow; + }; + void updateLabelsPosition(); + void paintContentSnapshot(Painter &p, const QPixmap &snapshot, float64 alpha, float64 howMuchHidden); + + CoverAnimation prepareCoverAnimation(Step *step); + QPixmap prepareContentSnapshot(); + QPixmap prepareSlideAnimation(); + void showFinished(); + + void prepareCoverMask(); + void paintCover(Painter &p, int top); + + Data *_data = nullptr; + bool _hasCover = false; + base::lambda _goCallback; + base::lambda _showResetCallback; + + ChildWidget _title; + ChildWidget> _description; + + bool _errorCentered = false; + bool _errorBelowLink = false; + QString _errorText; + ChildWidget> _error = { nullptr }; + + FloatAnimation _a_show; + CoverAnimation _coverAnimation; + std_::unique_ptr _slideAnimation; + QPixmap _coverMask; + + }; + private: + void step_show(float64 ms, bool timer); + + void changeLanguage(int32 languageId); void updateControlsGeometry(); - QPixmap grabStep(int skip = 0); + Data *getData() { + return &_data; + } - int _langChangeTo = 0; + void fixOrder(); + void showControls(); + void hideControls(); + void moveControls(); + QRect calculateStepRect() const; - Animation _a_stage; - QPixmap _cacheHide, _cacheShow; - int _cacheHideIndex = 0; - int _cacheShowIndex = 0; - anim::ivalue a_coordHide, a_coordShow; - anim::fvalue a_opacityHide, a_opacityShow; + void showResetButton(); + void resetAccount(); + void resetAccountSure(); + void resetDone(const MTPBool &result); + bool resetFail(const RPCError &error); Animation _a_show; QPixmap _cacheUnder, _cacheOver; anim::ivalue a_coordUnder, a_coordOver; anim::fvalue a_shadow; - QVector _stepHistory; - IntroStep *step(int skip = 0) { + QVector _stepHistory; + Step *getStep(int skip = 0) { t_assert(_stepHistory.size() + skip > 0); return _stepHistory.at(_stepHistory.size() - skip - 1); } - enum MoveType { - MoveBack, - MoveForward, - MoveReplace, - }; - void historyMove(MoveType type); - void pushStep(IntroStep *step, MoveType type); + void historyMove(Direction direction); + void moveToStep(Step *step, Direction direction); + void appendStep(Step *step); void gotNearestDC(const MTPNearestDc &dc); - QString _countryForReg; + Data _data; - QString _phone, _phone_hash; - CallStatus _callStatus = { CallDisabled, 0 }; - bool _registered = false; - - QString _code; - - QByteArray _pwdSalt; - bool _hasRecovery = false; - bool _codeByTelegram = false; - QString _pwdHint; - - QString _firstname, _lastname; + FloatAnimation _coverShownAnimation; + int _nextTopFrom = 0; + int _controlsTopFrom = 0; ChildWidget> _back; - ChildWidget _settings; - ChildWidget _update = { nullptr }; + ChildWidget> _update = { nullptr }; + ChildWidget> _settings; - float64 _backFrom = 0.; - float64 _backTo = 0.; + ChildWidget _next; + ChildWidget> _changeLanguage = { nullptr }; + ChildWidget> _resetAccount = { nullptr }; + + mtpRequestId _resetRequest = 0; }; -class IntroStep : public TWidget, public RPCSender { -public: - IntroStep(IntroWidget *parent) : TWidget(parent) { - } - - virtual bool hasBack() const { - return false; - } - virtual void activate() { - show(); - } - virtual void cancelled() { - } - virtual void finished() { - hide(); - } - virtual void onSubmit() = 0; - -protected: - IntroWidget *intro() { - IntroWidget *result = qobject_cast(parentWidget()); - t_assert(result != nullptr); - return result; - } - -}; +} // namespace Intro diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h index d871cc80a..6cadf5a41 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -173,7 +173,6 @@ class Manager : public QObject { Q_OBJECT public: - Manager(); void writeMap(bool fast); @@ -182,13 +181,11 @@ public: void writingLocations(); void finish(); - public slots: - +public slots: void mapWriteTimeout(); void locationsWriteTimeout(); private: - QTimer _mapWriteTimer; QTimer _locationsWriteTimer; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 46c7b2ae9..994738a65 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -2269,7 +2269,7 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, Ui::Show _topBar->hide(); _history->hide(); if (!_a_show.animating()) { - if (!animationParams.oldContentCache.isNull()) { + if (!animationParams.oldContentCache.isNull() && !App::passcoded()) { _dialogs->showAnimated(back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight, animationParams); } else { _dialogs->showFast(); diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index b9d1853c8..7ea3b6d6c 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -213,11 +213,7 @@ void MainWindow::clearWidgets() { Ui::hideLayer(true); _passcode.destroyDelayed(); _main.destroy(); - if (_intro) { - _intro->stop_show(); - _intro->rpcClear(); - _intro.destroyDelayed(); - } + _intro.destroy(); if (_mediaView) { hideMediaview(); _mediaView->rpcClear(); @@ -229,10 +225,10 @@ QPixmap MainWindow::grabInner() { QPixmap result; if (_intro) { result = myGrab(_intro); - } else if (_main) { - result = myGrab(_main); } else if (_passcode) { result = myGrab(_passcode); + } else if (_main) { + result = myGrab(_main); } return result; } @@ -240,10 +236,9 @@ QPixmap MainWindow::grabInner() { void MainWindow::clearPasscode() { if (!_passcode) return; - QPixmap bg = grabInner(); + auto bg = grabInner(); - _passcode->stop_show(); - _passcode.destroyDelayed(); + _passcode.destroy(); if (_intro) { _intro->animShow(bg, true); } else { @@ -260,10 +255,6 @@ void MainWindow::clearPasscode() { void MainWindow::setupPasscode() { auto animated = (_main || _intro); auto bg = animated ? grabInner() : QPixmap(); - if (_passcode) { - _passcode->stop_show(); - _passcode.destroyDelayed(); - } _passcode.create(bodyWidget()); updateControlsGeometry(); @@ -446,10 +437,6 @@ void MainWindow::updateConnectingStatus() { } } -IntroWidget *MainWindow::introWidget() { - return _intro; -} - MainWidget *MainWindow::mainWidget() { return _main; } @@ -650,6 +637,8 @@ void MainWindow::setInnerFocus() { _settings->setInnerFocus(); } else if (_main) { _main->setInnerFocus(); + } else if (_intro) { + _intro->setInnerFocus(); } } @@ -834,7 +823,7 @@ void MainWindow::activate() { } } -void MainWindow::noIntro(IntroWidget *was) { +void MainWindow::noIntro(Intro::Widget *was) { if (was == _intro) { _intro = nullptr; } diff --git a/Telegram/SourceFiles/mainwindow.h b/Telegram/SourceFiles/mainwindow.h index 452b41611..aadff4956 100644 --- a/Telegram/SourceFiles/mainwindow.h +++ b/Telegram/SourceFiles/mainwindow.h @@ -27,11 +27,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org class MediaView; class PasscodeWidget; -class IntroWidget; class MainWidget; class LayerStackWidget; class LayerWidget; +namespace Intro { +class Widget; +} // namespace Intro + namespace Local { class ClearManager; } // namespace Local @@ -99,7 +102,6 @@ public: void mtpStateChanged(int32 dc, int32 state); - IntroWidget *introWidget(); MainWidget *mainWidget(); PasscodeWidget *passcodeWidget(); @@ -112,7 +114,7 @@ public: void activate(); - void noIntro(IntroWidget *was); + void noIntro(Intro::Widget *was); void noMain(MainWidget *was); void noLayerStack(LayerStackWidget *was); void layerFinishedHide(LayerStackWidget *was); @@ -239,7 +241,7 @@ private: mtpRequestId _serviceHistoryRequest = 0; ChildWidget _passcode = { nullptr }; - ChildWidget _intro = { nullptr }; + ChildWidget _intro = { nullptr }; ChildWidget _main = { nullptr }; ChildWidget _settings = { nullptr }; ChildWidget _layerBg = { nullptr }; diff --git a/Telegram/SourceFiles/mtproto/connection_http.h b/Telegram/SourceFiles/mtproto/connection_http.h index 54f3af11d..e52feaae7 100644 --- a/Telegram/SourceFiles/mtproto/connection_http.h +++ b/Telegram/SourceFiles/mtproto/connection_http.h @@ -30,7 +30,6 @@ class HTTPConnection : public AbstractConnection { Q_OBJECT public: - HTTPConnection(QThread *thread); void sendData(mtpBuffer &buffer) override; @@ -46,15 +45,13 @@ public: QString transport() const override; - public slots: - +public slots: void requestFinished(QNetworkReply *reply); static mtpBuffer handleResponse(QNetworkReply *reply); static bool handleError(QNetworkReply *reply); // returnes "maybe bad key" private: - enum Status { WaitingHttp = 0, UsingHttp, diff --git a/Telegram/SourceFiles/mtproto/facade.cpp b/Telegram/SourceFiles/mtproto/facade.cpp index a09c9842c..c1f10068b 100644 --- a/Telegram/SourceFiles/mtproto/facade.cpp +++ b/Telegram/SourceFiles/mtproto/facade.cpp @@ -754,7 +754,7 @@ void ping() { } void cancel(mtpRequestId requestId) { - if (!_started) return; + if (!_started || !requestId) return; mtpMsgId msgId = 0; requestsDelays.remove(requestId); diff --git a/Telegram/SourceFiles/passcodewidget.cpp b/Telegram/SourceFiles/passcodewidget.cpp index 2da778aa9..f508036f2 100644 --- a/Telegram/SourceFiles/passcodewidget.cpp +++ b/Telegram/SourceFiles/passcodewidget.cpp @@ -29,14 +29,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "styles/style_boxes.h" -#include "window/slide_animation.h" +#include "window/window_slide_animation.h" PasscodeWidget::PasscodeWidget(QWidget *parent) : TWidget(parent) , _a_show(animation(this, &PasscodeWidget::step_show)) , _passcode(this, st::passcodeInput) , _submit(this, lang(lng_passcode_submit), st::passcodeSubmit) , _logout(this, lang(lng_passcode_logout)) { - _passcode->setEchoMode(QLineEdit::Password); connect(_passcode, SIGNAL(changed()), this, SLOT(onChanged())); connect(_passcode, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); @@ -49,12 +48,12 @@ PasscodeWidget::PasscodeWidget(QWidget *parent) : TWidget(parent) void PasscodeWidget::onSubmit() { if (_passcode->text().isEmpty()) { - _passcode->notaBene(); + _passcode->showError(); return; } if (!passcodeCanTry()) { _error = lang(lng_flood_error); - _passcode->notaBene(); + _passcode->showError(); update(); return; } @@ -62,7 +61,8 @@ void PasscodeWidget::onSubmit() { if (App::main()) { if (Local::checkPasscode(_passcode->text().toUtf8())) { cSetPasscodeBadTries(0); - App::wnd()->clearPasscode(); + App::wnd()->clearPasscode(); // Destroys this widget. + return; } else { cSetPasscodeBadTries(cPasscodeBadTries() + 1); cSetPasscodeLastTry(getms(true)); @@ -93,7 +93,7 @@ void PasscodeWidget::onSubmit() { void PasscodeWidget::onError() { _error = lang(lng_passcode_wrong); _passcode->selectAll(); - _passcode->notaBene(); + _passcode->showError(); update(); } diff --git a/Telegram/SourceFiles/passcodewidget.h b/Telegram/SourceFiles/passcodewidget.h index ab41cde0a..79a789e44 100644 --- a/Telegram/SourceFiles/passcodewidget.h +++ b/Telegram/SourceFiles/passcodewidget.h @@ -21,7 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once namespace Ui { -class FlatInput; +class PasswordInput; class LinkButton; class RoundButton; } // namespace Ui @@ -56,7 +56,7 @@ private: anim::ivalue a_coordUnder, a_coordOver; anim::fvalue a_shadow; - ChildWidget _passcode; + ChildWidget _passcode; ChildWidget _submit; ChildWidget _logout; QString _error; diff --git a/Telegram/SourceFiles/settings/settings_privacy_widget.cpp b/Telegram/SourceFiles/settings/settings_privacy_widget.cpp index 673c73e72..dbc6608c2 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_widget.cpp @@ -144,7 +144,7 @@ void CloudPasswordState::getPasswordDone(const MTPaccount_Password &result) { void CloudPasswordState::paintEvent(QPaintEvent *e) { Painter p(this); - auto text = st::linkFont->elided(_waitingConfirm, width() - _turnOff->width()); + auto text = st::boxTextFont->elided(_waitingConfirm, width() - _turnOff->width() - st::boxTextFont->spacew); if (!text.isEmpty()) { p.setPen(st::windowFg); p.setFont(st::boxTextFont); diff --git a/Telegram/SourceFiles/stickers/stickers.style b/Telegram/SourceFiles/stickers/stickers.style index e4efb2a69..17e742b40 100644 --- a/Telegram/SourceFiles/stickers/stickers.style +++ b/Telegram/SourceFiles/stickers/stickers.style @@ -149,7 +149,6 @@ emojiPanShowDuration: 200; emojiPanDuration: 200; emojiPanHover: windowBgOver; emojiPanSlideDuration: 200; -emojiPanSlideDelta: 0; // between hide start and show start emojiPanHeader: 42px; emojiPanHeaderFont: semiboldFont; diff --git a/Telegram/SourceFiles/ui/abstract_button.cpp b/Telegram/SourceFiles/ui/abstract_button.cpp index 0ce112850..3bc62ce8a 100644 --- a/Telegram/SourceFiles/ui/abstract_button.cpp +++ b/Telegram/SourceFiles/ui/abstract_button.cpp @@ -73,8 +73,9 @@ void AbstractButton::mouseReleaseEvent(QMouseEvent *e) { _modifiers = e->modifiers(); if (_clickedCallback) { _clickedCallback(); + } else { + emit clicked(); } - emit clicked(); } else { leaveEvent(e); } diff --git a/Telegram/SourceFiles/ui/animation.h b/Telegram/SourceFiles/ui/animation.h index 05118319e..f27f1006f 100644 --- a/Telegram/SourceFiles/ui/animation.h +++ b/Telegram/SourceFiles/ui/animation.h @@ -269,6 +269,14 @@ FORCE_INLINE Shifted shifted(QColor color) { return reshifted(components * alpha); } +FORCE_INLINE uint32 getPremultiplied(QColor color) { + // Make it premultiplied. + auto alpha = static_cast((color.alpha() & 0xFF) + 1); + auto components = Shifted(static_cast(color.blue() & 0xFF) | (static_cast(color.green() & 0xFF) << 16), + static_cast(color.red() & 0xFF) | (static_cast(255) << 16)); + return unshifted(components * alpha); +} + FORCE_INLINE uint32 getAlpha(Shifted components) { return (components.high & 0x00FF0000U) >> 16; } @@ -344,6 +352,16 @@ FORCE_INLINE Shifted shifted(QColor color) { return reshifted(components * alpha); } +FORCE_INLINE uint32 getPremultiplied(QColor color) { + // Make it premultiplied. + auto alpha = static_cast((color.alpha() & 0xFF) + 1); + auto components = static_cast(color.blue() & 0xFF) + | (static_cast(color.green() & 0xFF) << 16) + | (static_cast(color.red() & 0xFF) << 32) + | (static_cast(255) << 48); + return unshifted(components * alpha); +} + FORCE_INLINE uint32 getAlpha(Shifted components) { return (components.value & 0x00FF000000000000ULL) >> 48; } diff --git a/Telegram/SourceFiles/ui/buttons/peer_avatar_button.cpp b/Telegram/SourceFiles/ui/buttons/peer_avatar_button.cpp index 246ecab7f..10b69d6ec 100644 --- a/Telegram/SourceFiles/ui/buttons/peer_avatar_button.cpp +++ b/Telegram/SourceFiles/ui/buttons/peer_avatar_button.cpp @@ -40,7 +40,8 @@ void PeerAvatarButton::paintEvent(QPaintEvent *e) { } } -NewAvatarButton::NewAvatarButton(QWidget *parent, int size) : RippleButton(parent, st::defaultActiveButton.ripple) { +NewAvatarButton::NewAvatarButton(QWidget *parent, int size, QPoint position) : RippleButton(parent, st::defaultActiveButton.ripple) +, _position(position) { resize(size, size); } @@ -58,7 +59,7 @@ void NewAvatarButton::paintEvent(QPaintEvent *e) { paintRipple(p, 0, 0, getms()); - st::newGroupPhotoIcon.paint(p, st::newGroupPhotoIconPosition, width()); + st::newGroupPhotoIcon.paint(p, _position, width()); } void NewAvatarButton::setImage(const QImage &image) { diff --git a/Telegram/SourceFiles/ui/buttons/peer_avatar_button.h b/Telegram/SourceFiles/ui/buttons/peer_avatar_button.h index dbd5357b5..d2f485085 100644 --- a/Telegram/SourceFiles/ui/buttons/peer_avatar_button.h +++ b/Telegram/SourceFiles/ui/buttons/peer_avatar_button.h @@ -47,7 +47,7 @@ private: class NewAvatarButton : public RippleButton { public: - NewAvatarButton(QWidget *parent, int size); + NewAvatarButton(QWidget *parent, int size, QPoint position); void setImage(const QImage &image); @@ -58,6 +58,7 @@ protected: private: QPixmap _image; + QPoint _position; }; diff --git a/Telegram/SourceFiles/ui/countryinput.cpp b/Telegram/SourceFiles/ui/countryinput.cpp index 50e68012a..da9d3a94f 100644 --- a/Telegram/SourceFiles/ui/countryinput.cpp +++ b/Telegram/SourceFiles/ui/countryinput.cpp @@ -28,6 +28,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/contactsbox.h" #include "countries.h" #include "styles/style_boxes.h" +#include "styles/style_intro.h" namespace { @@ -89,50 +90,33 @@ QString findValidCode(QString fullCode) { return ""; } -CountryInput::CountryInput(QWidget *parent, const style::countryInput &st) : QWidget(parent), _st(st), _active(false), _text(lang(lng_country_code)) { +CountryInput::CountryInput(QWidget *parent, const style::InputField &st) : TWidget(parent) +, _st(st) +, _text(lang(lng_country_code)) { initCountries(); - - resize(_st.width, _st.height + _st.ptrSize.height()); - QImage trImage(_st.ptrSize.width(), _st.ptrSize.height(), QImage::Format_ARGB32_Premultiplied); - { - static const QPoint trPoints[3] = { - QPoint(0, 0), - QPoint(_st.ptrSize.width(), 0), - QPoint(qCeil(trImage.width() / 2.), trImage.height()) - }; - QPainter p(&trImage); - p.setRenderHint(QPainter::Antialiasing); - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(0, 0, trImage.width(), trImage.height(), Qt::transparent); - - p.setPen(Qt::NoPen); - p.setBrush(_st.bgColor); - p.drawPolygon(trPoints, 3); - } - _arrow = App::pixmapFromImageInPlace(std_::move(trImage)); - _inner = QRect(0, 0, _st.width, _st.height); - _arrowRect = QRect((st::introCountryCode.width - _arrow.width() - 1) / 2, _st.height, _arrow.width(), _arrow.height()); + resize(_st.width, _st.height); } void CountryInput::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); - p.setRenderHint(QPainter::HighQualityAntialiasing); - p.setBrush(_st.bgColor); - p.setPen(Qt::NoPen); - p.drawRoundedRect(_inner, st::buttonRadius, st::buttonRadius); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); + QRect r(rect().intersected(e->rect())); + if (_st.textBg->c.alphaF() > 0.) { + p.fillRect(r, _st.textBg); + } + if (_st.border) { + p.fillRect(0, height() - _st.border, width(), _st.border, _st.borderFg->b); + } - p.drawPixmap(_arrowRect.x(), _arrowRect.top(), _arrow); + st::introCountryIcon.paint(p, width() - st::introCountryIcon.width() - st::introCountryIconPosition.x(), st::introCountryIconPosition.y(), width()); p.setFont(_st.font); - p.setPen(st::windowFg); - - p.drawText(rect().marginsRemoved(_st.textMrg), _text, QTextOption(_st.align)); + p.setPen(_st.textFg); + p.drawText(rect().marginsRemoved(_st.textMargins), _text, _st.textAlign); } void CountryInput::mouseMoveEvent(QMouseEvent *e) { - bool newActive = _inner.contains(e->pos()) || _arrowRect.contains(e->pos()); + bool newActive = rect().contains(e->pos()); if (_active != newActive) { _active = newActive; setCursor(_active ? style::cur_pointer : style::cur_default); @@ -192,7 +176,7 @@ bool CountryInput::onChooseCountry(const QString &iso) { } void CountryInput::setText(const QString &newText) { - _text = _st.font->elided(newText, width() - _st.textMrg.left() - _st.textMrg.right()); + _text = _st.font->elided(newText, width() - _st.textMargins.left() - _st.textMargins.right()); } CountrySelectBox::CountrySelectBox() : ItemListBox(st::countriesScroll, st::boxWidth) diff --git a/Telegram/SourceFiles/ui/countryinput.h b/Telegram/SourceFiles/ui/countryinput.h index 4e0503019..ae3b03bbc 100644 --- a/Telegram/SourceFiles/ui/countryinput.h +++ b/Telegram/SourceFiles/ui/countryinput.h @@ -22,7 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/effects/rect_shadow.h" #include "boxes/abstractbox.h" -#include "styles/style_intro.h" +#include "styles/style_widgets.h" QString findValidCode(QString fullCode); @@ -30,11 +30,11 @@ namespace Ui { class MultiSelect; } // namespace Ui -class CountryInput : public QWidget { +class CountryInput : public TWidget { Q_OBJECT public: - CountryInput(QWidget *parent, const style::countryInput &st); + CountryInput(QWidget *parent, const style::InputField &st); public slots: void onChooseCode(const QString &code); @@ -53,10 +53,8 @@ protected: private: void setText(const QString &newText); - QPixmap _arrow; - QRect _inner, _arrowRect; - const style::countryInput &_st; - bool _active; + const style::InputField &_st; + bool _active = false; QString _text; }; diff --git a/Telegram/SourceFiles/ui/effects/slide_animation.cpp b/Telegram/SourceFiles/ui/effects/slide_animation.cpp new file mode 100644 index 000000000..9ebdfc002 --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/slide_animation.cpp @@ -0,0 +1,67 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "ui/effects/slide_animation.h" + +namespace Ui { + +void SlideAnimation::setSnapshots(QPixmap leftSnapshot, QPixmap rightSnapshot) { + _leftSnapshot = std_::move(leftSnapshot); + _rightSnapshot = std_::move(rightSnapshot); + t_assert(!_leftSnapshot.isNull()); + t_assert(!_rightSnapshot.isNull()); + _leftSnapshot.setDevicePixelRatio(cRetinaFactor()); + _rightSnapshot.setDevicePixelRatio(cRetinaFactor()); +} + +void SlideAnimation::paintFrame(Painter &p, int x, int y, int outerWidth, uint64 ms) { + auto dt = _animation.current(ms, 1.); + if (!animating()) return; + + auto easeOut = anim::easeOutCirc(1., dt); + auto easeIn = anim::easeInCirc(1., dt); + auto arrivingAlpha = easeIn; + auto departingAlpha = 1. - easeOut; + auto leftCoord = (_slideLeft ? anim::interpolate(-_leftSnapshotWidth, 0, easeOut) : anim::interpolate(0, -_leftSnapshotWidth, easeIn)); + auto leftAlpha = (_slideLeft ? arrivingAlpha : departingAlpha); + auto rightCoord = (_slideLeft ? anim::interpolate(0, _rightSnapshotWidth, easeIn) : anim::interpolate(_rightSnapshotWidth, 0, easeOut)); + auto rightAlpha = (_slideLeft ? departingAlpha : arrivingAlpha); + + if (_overflowHidden) { + auto leftWidth = (_leftSnapshotWidth + leftCoord); + if (leftWidth > 0) { + p.setOpacity(leftAlpha); + p.drawPixmap(x, y, leftWidth, _leftSnapshotHeight, _leftSnapshot, (_leftSnapshot.width() - leftWidth * cIntRetinaFactor()), 0, leftWidth * cIntRetinaFactor(), _leftSnapshot.height()); + } + auto rightWidth = _rightSnapshotWidth - rightCoord; + if (rightWidth > 0) { + p.setOpacity(rightAlpha); + p.drawPixmap(x + rightCoord, y, _rightSnapshot, 0, 0, rightWidth * cIntRetinaFactor(), _rightSnapshot.height()); + } + } else { + p.setOpacity(leftAlpha); + p.drawPixmap(x + leftCoord, y, _leftSnapshot); + p.setOpacity(rightAlpha); + p.drawPixmap(x + rightCoord, y, _rightSnapshot); + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/slide_animation.h b/Telegram/SourceFiles/ui/effects/slide_animation.h new file mode 100644 index 000000000..ddbb5c4b8 --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/slide_animation.h @@ -0,0 +1,66 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +namespace Ui { + +class SlideAnimation { +public: + void setSnapshots(QPixmap leftSnapshot, QPixmap rightSnapshot); + + void setOverflowHidden(bool hidden) { + _overflowHidden = hidden; + } + + template + void start(bool slideLeft, Lambda &&updateCallback, float64 duration); + + void paintFrame(Painter &p, int x, int y, int outerWidth, uint64 ms); + + bool animating() const { + return _animation.animating(); + } + +private: + FloatAnimation _animation; + QPixmap _leftSnapshot; + QPixmap _rightSnapshot; + bool _slideLeft = false; + bool _overflowHidden = true; + int _leftSnapshotWidth = 0; + int _leftSnapshotHeight = 0; + int _rightSnapshotWidth = 0; + +}; + +template +void SlideAnimation::start(bool slideLeft, Lambda &&updateCallback, float64 duration) { + _slideLeft = slideLeft; + if (_slideLeft) { + std_::swap_moveable(_leftSnapshot, _rightSnapshot); + } + _leftSnapshotWidth = _leftSnapshot.width() / cIntRetinaFactor(); + _leftSnapshotHeight = _leftSnapshot.height() / cIntRetinaFactor(); + _rightSnapshotWidth = _rightSnapshot.width() / cIntRetinaFactor(); + _animation.start(std_::forward(updateCallback), 0., 1., duration); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/widget_fade_wrap.cpp b/Telegram/SourceFiles/ui/effects/widget_fade_wrap.cpp index f309937f6..6dfb34f2a 100644 --- a/Telegram/SourceFiles/ui/effects/widget_fade_wrap.cpp +++ b/Telegram/SourceFiles/ui/effects/widget_fade_wrap.cpp @@ -90,6 +90,7 @@ void FadeAnimation::fadeOut(int duration) { void FadeAnimation::startAnimation(int duration) { if (_cache.isNull()) { + _widget->showChildren(); _cache = myGrab(_widget); _widget->hideChildren(); } diff --git a/Telegram/SourceFiles/ui/twidget.cpp b/Telegram/SourceFiles/ui/twidget.cpp index 4eec33cda..e31d3a339 100644 --- a/Telegram/SourceFiles/ui/twidget.cpp +++ b/Telegram/SourceFiles/ui/twidget.cpp @@ -67,7 +67,7 @@ QPixmap myGrab(TWidget *target, QRect rect) { myEnsureResized(target); if (rect.isNull()) rect = target->rect(); - QPixmap result(rect.size() * cRetinaFactor()); + auto result = QPixmap(rect.size() * cRetinaFactor()); result.setDevicePixelRatio(cRetinaFactor()); result.fill(Qt::transparent); @@ -75,7 +75,24 @@ QPixmap myGrab(TWidget *target, QRect rect) { target->render(&result, QPoint(0, 0), rect, QWidget::DrawChildren | QWidget::IgnoreMask); target->grabFinish(); - return result; + return std_::move(result); +} + +QImage myGrabImage(TWidget *target, QRect rect) { + myEnsureResized(target); + if (rect.isNull()) rect = target->rect(); + + auto result = QImage(rect.size() * cRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(cRetinaFactor()); + if (!target->testAttribute(Qt::WA_OpaquePaintEvent)) { + result.fill(Qt::transparent); + } + + target->grabStart(); + target->render(&result, QPoint(0, 0), rect, QWidget::DrawChildren | QWidget::IgnoreMask); + target->grabFinish(); + + return std_::move(result); } void sendSynteticMouseEvent(QWidget *widget, QEvent::Type type, Qt::MouseButton button, const QPoint &globalPoint) { diff --git a/Telegram/SourceFiles/ui/twidget.h b/Telegram/SourceFiles/ui/twidget.h index 9970640fc..f3b4123cd 100644 --- a/Telegram/SourceFiles/ui/twidget.h +++ b/Telegram/SourceFiles/ui/twidget.h @@ -207,6 +207,7 @@ protected: void myEnsureResized(QWidget *target); QPixmap myGrab(TWidget *target, QRect rect = QRect()); +QImage myGrabImage(TWidget *target, QRect rect = QRect()); class SingleDelayedCall : public QObject { Q_OBJECT diff --git a/Telegram/SourceFiles/ui/widgets/buttons.cpp b/Telegram/SourceFiles/ui/widgets/buttons.cpp index 1514126b1..d4ba1b90a 100644 --- a/Telegram/SourceFiles/ui/widgets/buttons.cpp +++ b/Telegram/SourceFiles/ui/widgets/buttons.cpp @@ -255,6 +255,7 @@ void RoundButton::updateText() { _secondaryTextWidth = _secondaryText.isEmpty() ? 0 : _st.font->width(_secondaryText); resizeToText(); + update(); } void RoundButton::resizeToText() { diff --git a/Telegram/SourceFiles/ui/widgets/input_fields.cpp b/Telegram/SourceFiles/ui/widgets/input_fields.cpp index 552af652b..7b63a3bca 100644 --- a/Telegram/SourceFiles/ui/widgets/input_fields.cpp +++ b/Telegram/SourceFiles/ui/widgets/input_fields.cpp @@ -1798,199 +1798,6 @@ void FlatInput::notaBene() { _a_appearance.start(); } -CountryCodeInput::CountryCodeInput(QWidget *parent, const style::FlatInput &st) : FlatInput(parent, st) -, _nosignal(false) { -} - -void CountryCodeInput::startErasing(QKeyEvent *e) { - setFocus(); - keyPressEvent(e); -} - -void CountryCodeInput::codeSelected(const QString &code) { - QString wasText(getLastText()), newText = '+' + code; - setText(newText); - _nosignal = true; - correctValue(wasText, newText); - _nosignal = false; - emit changed(); -} - -void CountryCodeInput::correctValue(const QString &was, QString &now) { - QString newText, addToNumber; - int oldPos(cursorPosition()), newPos(-1), oldLen(now.length()), start = 0, digits = 5; - newText.reserve(oldLen + 1); - newText += '+'; - if (oldLen && now[0] == '+') { - ++start; - } - for (int i = start; i < oldLen; ++i) { - QChar ch(now[i]); - if (ch.isDigit()) { - if (!digits || !--digits) { - addToNumber += ch; - } else { - newText += ch; - } - } - if (i == oldPos) { - newPos = newText.length(); - } - } - if (!addToNumber.isEmpty()) { - QString validCode = findValidCode(newText.mid(1)); - addToNumber = newText.mid(1 + validCode.length()) + addToNumber; - newText = '+' + validCode; - } - if (newPos < 0 || newPos > newText.length()) { - newPos = newText.length(); - } - if (newText != now) { - now = newText; - setText(newText); - updatePlaceholder(); - if (newPos != oldPos) { - setCursorPosition(newPos); - } - } - if (!_nosignal && was != newText) { - emit codeChanged(newText.mid(1)); - } - if (!addToNumber.isEmpty()) { - emit addedToNumber(addToNumber); - } -} - -PhonePartInput::PhonePartInput(QWidget *parent, const style::FlatInput &st) : FlatInput(parent, st, lang(lng_phone_ph)) { -} - -void PhonePartInput::paintEvent(QPaintEvent *e) { - FlatInput::paintEvent(e); - - Painter p(this); - auto t = text(); - if (!_pattern.isEmpty() && !t.isEmpty()) { - auto ph = placeholder().mid(t.size()); - if (!ph.isEmpty()) { - p.setClipRect(rect()); - auto phRect = placeholderRect(); - int tw = phFont()->width(t); - if (tw < phRect.width()) { - phRect.setLeft(phRect.left() + tw); - phPrepare(p); - p.drawText(phRect, ph, style::al_left); - } - } - } -} - -void PhonePartInput::keyPressEvent(QKeyEvent *e) { - if (e->key() == Qt::Key_Backspace && text().isEmpty()) { - emit voidBackspace(e); - } else { - FlatInput::keyPressEvent(e); - } -} - -void PhonePartInput::correctValue(const QString &was, QString &now) { - QString newText; - int oldPos(cursorPosition()), newPos(-1), oldLen(now.length()), digitCount = 0; - for (int i = 0; i < oldLen; ++i) { - if (now[i].isDigit()) { - ++digitCount; - } - } - if (digitCount > MaxPhoneTailLength) digitCount = MaxPhoneTailLength; - - bool inPart = !_pattern.isEmpty(); - int curPart = -1, leftInPart = 0; - newText.reserve(oldLen); - for (int i = 0; i < oldLen; ++i) { - if (i == oldPos && newPos < 0) { - newPos = newText.length(); - } - - QChar ch(now[i]); - if (ch.isDigit()) { - if (!digitCount--) { - break; - } - if (inPart) { - if (leftInPart) { - --leftInPart; - } else { - newText += ' '; - ++curPart; - inPart = curPart < _pattern.size(); - leftInPart = inPart ? (_pattern.at(curPart) - 1) : 0; - - ++oldPos; - } - } - newText += ch; - } else if (ch == ' ' || ch == '-' || ch == '(' || ch == ')') { - if (inPart) { - if (leftInPart) { - } else { - newText += ch; - ++curPart; - inPart = curPart < _pattern.size(); - leftInPart = inPart ? _pattern.at(curPart) : 0; - } - } else { - newText += ch; - } - } - } - int32 newlen = newText.size(); - while (newlen > 0 && newText.at(newlen - 1).isSpace()) { - --newlen; - } - if (newlen < newText.size()) newText = newText.mid(0, newlen); - if (newPos < 0) { - newPos = newText.length(); - } - if (newText != now) { - now = newText; - setText(now); - updatePlaceholder(); - setCursorPosition(newPos); - } -} - -void PhonePartInput::addedToNumber(const QString &added) { - setFocus(); - QString wasText(getLastText()), newText = added + wasText; - setText(newText); - setCursorPosition(added.length()); - correctValue(wasText, newText); - updatePlaceholder(); -} - -void PhonePartInput::onChooseCode(const QString &code) { - _pattern = phoneNumberParse(code); - if (!_pattern.isEmpty() && _pattern.at(0) == code.size()) { - _pattern.pop_front(); - } else { - _pattern.clear(); - } - if (_pattern.isEmpty()) { - setPlaceholder(lang(lng_phone_ph)); - } else { - QString ph; - ph.reserve(20); - for (int i = 0, l = _pattern.size(); i < l; ++i) { - ph.append(' '); - ph.append(QString(_pattern.at(i), QChar(0x2212))); - } - setPlaceholder(ph); - } - auto newText = getLastText(); - correctValue(newText, newText); - setPlaceholderFast(!_pattern.isEmpty()); - updatePlaceholder(); -} - InputArea::InputArea(QWidget *parent, const style::InputArea &st, const QString &ph, const QString &val) : TWidget(parent) , _maxLength(-1) , _inner(this) @@ -3533,6 +3340,23 @@ MaskedInputField::MaskedInputField(QWidget *parent, const style::InputField &st, updatePlaceholder(); } +void MaskedInputField::setCorrectedText(QString &now, int &nowCursor, const QString &newText, int newPos) { + if (newPos < 0 || newPos > newText.size()) { + newPos = newText.size(); + } + auto updateText = (newText != now); + if (updateText) { + now = newText; + setText(now); + updatePlaceholder(); + } + auto updateCursorPosition = (newPos != nowCursor) || updateText; + if (updateCursorPosition) { + nowCursor = newPos; + setCursorPosition(nowCursor); + } +} + void MaskedInputField::customUpDown(bool custom) { _customUpDown = custom; } @@ -3770,9 +3594,6 @@ QRect MaskedInputField::placeholderRect() const { return rect().marginsRemoved(_st.textMargins + _st.placeholderMargins); } -void MaskedInputField::correctValue(const QString &was, int32 wasCursor, QString &now, int32 &nowCursor) { -} - void MaskedInputField::paintPlaceholder(Painter &p) { bool drawPlaceholder = _placeholderVisible; if (_a_placeholderShift.animating()) { @@ -3860,6 +3681,195 @@ void MaskedInputField::onCursorPositionChanged(int oldPosition, int position) { _oldcursor = position; } +CountryCodeInput::CountryCodeInput(QWidget *parent, const style::InputField &st) : MaskedInputField(parent, st) +, _nosignal(false) { +} + +void CountryCodeInput::startErasing(QKeyEvent *e) { + setFocus(); + keyPressEvent(e); +} + +void CountryCodeInput::codeSelected(const QString &code) { + auto wasText = getLastText(); + auto wasCursor = cursorPosition(); + auto newText = '+' + code; + auto newCursor = newText.size(); + setText(newText); + _nosignal = true; + correctValue(wasText, wasCursor, newText, newCursor); + _nosignal = false; + emit changed(); +} + +void CountryCodeInput::correctValue(const QString &was, int32 wasCursor, QString &now, int32 &nowCursor) { + QString newText, addToNumber; + int oldPos(nowCursor), newPos(-1), oldLen(now.length()), start = 0, digits = 5; + newText.reserve(oldLen + 1); + if (oldLen && now[0] == '+') { + if (start == oldPos) { + newPos = newText.length(); + } + ++start; + } + newText += '+'; + for (int i = start; i < oldLen; ++i) { + if (i == oldPos) { + newPos = newText.length(); + } + auto ch = now[i]; + if (ch.isDigit()) { + if (!digits || !--digits) { + addToNumber += ch; + } else { + newText += ch; + } + } + } + if (!addToNumber.isEmpty()) { + auto validCode = findValidCode(newText.mid(1)); + addToNumber = newText.mid(1 + validCode.length()) + addToNumber; + newText = '+' + validCode; + } + setCorrectedText(now, nowCursor, newText, newPos); + + if (!_nosignal && was != newText) { + emit codeChanged(newText.mid(1)); + } + if (!addToNumber.isEmpty()) { + emit addedToNumber(addToNumber); + } +} + +PhonePartInput::PhonePartInput(QWidget *parent, const style::InputField &st) : MaskedInputField(parent, st, lang(lng_phone_ph)) { +} + +void PhonePartInput::paintPlaceholder(Painter &p) { + auto t = getLastText(); + if (!_pattern.isEmpty() && !t.isEmpty()) { + auto ph = placeholder().mid(t.size()); + if (!ph.isEmpty()) { + p.setClipRect(rect()); + auto phRect = placeholderRect(); + int tw = phFont()->width(t); + if (tw < phRect.width()) { + phRect.setLeft(phRect.left() + tw); + placeholderPreparePaint(p); + p.drawText(phRect, ph, style::al_topleft); + } + } + } else { + MaskedInputField::paintPlaceholder(p); + } +} + +void PhonePartInput::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Backspace && getLastText().isEmpty()) { + emit voidBackspace(e); + } else { + MaskedInputField::keyPressEvent(e); + } +} + +void PhonePartInput::correctValue(const QString &was, int32 wasCursor, QString &now, int32 &nowCursor) { + QString newText; + int oldPos(nowCursor), newPos(-1), oldLen(now.length()), digitCount = 0; + for (int i = 0; i < oldLen; ++i) { + if (now[i].isDigit()) { + ++digitCount; + } + } + if (digitCount > MaxPhoneTailLength) digitCount = MaxPhoneTailLength; + + bool inPart = !_pattern.isEmpty(); + int curPart = -1, leftInPart = 0; + newText.reserve(oldLen); + for (int i = 0; i < oldLen; ++i) { + if (i == oldPos && newPos < 0) { + newPos = newText.length(); + } + + auto ch = now[i]; + if (ch.isDigit()) { + if (!digitCount--) { + break; + } + if (inPart) { + if (leftInPart) { + --leftInPart; + } else { + newText += ' '; + ++curPart; + inPart = curPart < _pattern.size(); + leftInPart = inPart ? (_pattern.at(curPart) - 1) : 0; + + ++oldPos; + } + } + newText += ch; + } else if (ch == ' ' || ch == '-' || ch == '(' || ch == ')') { + if (inPart) { + if (leftInPart) { + } else { + newText += ch; + ++curPart; + inPart = curPart < _pattern.size(); + leftInPart = inPart ? _pattern.at(curPart) : 0; + } + } else { + newText += ch; + } + } + } + auto newlen = newText.size(); + while (newlen > 0 && newText.at(newlen - 1).isSpace()) { + --newlen; + } + if (newlen < newText.size()) { + newText = newText.mid(0, newlen); + } + setCorrectedText(now, nowCursor, newText, newPos); +} + +void PhonePartInput::addedToNumber(const QString &added) { + setFocus(); + auto wasText = getLastText(); + auto wasCursor = cursorPosition(); + auto newText = added + wasText; + auto newCursor = newText.size(); + setText(newText); + setCursorPosition(added.length()); + correctValue(wasText, wasCursor, newText, newCursor); + updatePlaceholder(); +} + +void PhonePartInput::onChooseCode(const QString &code) { + _pattern = phoneNumberParse(code); + if (!_pattern.isEmpty() && _pattern.at(0) == code.size()) { + _pattern.pop_front(); + } else { + _pattern.clear(); + } + if (_pattern.isEmpty()) { + setPlaceholder(lang(lng_phone_ph)); + } else { + QString ph; + ph.reserve(20); + for (int i = 0, l = _pattern.size(); i < l; ++i) { + ph.append(' '); + ph.append(QString(_pattern.at(i), QChar(0x2212))); + } + setPlaceholder(ph); + } + auto wasText = getLastText(); + auto wasCursor = cursorPosition(); + auto newText = getLastText(); + auto newCursor = newText.size(); + correctValue(wasText, wasCursor, newText, newCursor); + setPlaceholderFast(!_pattern.isEmpty()); + updatePlaceholder(); +} + PasswordInput::PasswordInput(QWidget *parent, const style::InputField &st, const QString &ph, const QString &val) : MaskedInputField(parent, st, ph, val) { setEchoMode(QLineEdit::Password); } @@ -3873,29 +3883,22 @@ PortInput::PortInput(QWidget *parent, const style::InputField &st, const QString void PortInput::correctValue(const QString &was, int32 wasCursor, QString &now, int32 &nowCursor) { QString newText; newText.reserve(now.size()); - int32 newCursor = nowCursor; - for (int32 i = 0, l = now.size(); i < l; ++i) { + auto newPos = nowCursor; + for (auto i = 0, l = now.size(); i < l; ++i) { if (now.at(i).isDigit()) { newText.append(now.at(i)); } else if (i < nowCursor) { - --newCursor; + --newPos; } } if (!newText.toInt()) { newText = QString(); - newCursor = 0; + newPos = 0; } else if (newText.toInt() > 65535) { newText = was; - newCursor = wasCursor; - } - if (newText != now) { - now = newText; - setText(newText); - } - if (newCursor != nowCursor) { - nowCursor = newCursor; - setCursorPosition(newCursor); + newPos = wasCursor; } + setCorrectedText(now, nowCursor, newText, newPos); } UsernameInput::UsernameInput(QWidget *parent, const style::InputField &st, const QString &ph, const QString &val, bool isLink) : MaskedInputField(parent, st, ph, val), @@ -3916,13 +3919,13 @@ void UsernameInput::paintPlaceholder(Painter &p) { } void UsernameInput::correctValue(const QString &was, int32 wasCursor, QString &now, int32 &nowCursor) { - QString newText; - int32 newCursor = nowCursor, from, len = now.size(); - for (from = 0; from < len; ++from) { + auto newPos = nowCursor; + auto from = 0, len = now.size(); + for (; from < len; ++from) { if (!now.at(from).isSpace()) { break; } - if (newCursor > 0) --newCursor; + if (newPos > 0) --newPos; } len -= from; if (len > MaxUsernameLength) len = MaxUsernameLength + (now.at(from) == '@' ? 1 : 0); @@ -3933,18 +3936,7 @@ void UsernameInput::correctValue(const QString &was, int32 wasCursor, QString &n } --len; } - newText = now.mid(from, len); - if (newCursor > len) { - newCursor = len; - } - if (newText != now) { - now = newText; - setText(newText); - } - if (newCursor != nowCursor) { - nowCursor = newCursor; - setCursorPosition(newCursor); - } + setCorrectedText(now, nowCursor, now.mid(from, len), newPos); } PhoneInput::PhoneInput(QWidget *parent, const style::InputField &st, const QString &ph, const QString &val) : MaskedInputField(parent, st, ph, val) @@ -3996,7 +3988,7 @@ void PhoneInput::paintPlaceholder(Painter &p) { } void PhoneInput::correctValue(const QString &was, int32 wasCursor, QString &now, int32 &nowCursor) { - QString digits(now); + auto digits = now; digits.replace(QRegularExpression(qsl("[^\\d]")), QString()); _pattern = phoneNumberParse(digits); @@ -4075,16 +4067,10 @@ void PhoneInput::correctValue(const QString &was, int32 wasCursor, QString &now, while (newlen > 0 && newText.at(newlen - 1).isSpace()) { --newlen; } - if (newlen < newText.size()) newText = newText.mid(0, newlen); - if (newPos < 0) { - newPos = newText.length(); - } - if (newText != now) { - now = newText; - setText(newText); - updatePlaceholder(); - setCursorPosition(newPos); + if (newlen < newText.size()) { + newText = newText.mid(0, newlen); } + setCorrectedText(now, nowCursor, newText, newPos); } } // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/input_fields.h b/Telegram/SourceFiles/ui/widgets/input_fields.h index 908e70dbc..abbc36f89 100644 --- a/Telegram/SourceFiles/ui/widgets/input_fields.h +++ b/Telegram/SourceFiles/ui/widgets/input_fields.h @@ -114,7 +114,7 @@ public: }; void setTagMimeProcessor(std_::unique_ptr &&processor); - public slots: +public slots: void onTouchTimer(); void onDocumentContentsChange(int position, int charsRemoved, int charsAdded); @@ -267,7 +267,7 @@ public: return _oldtext; } - public slots: +public slots: void onTextChange(const QString &text); void onTextEdited(); @@ -330,52 +330,6 @@ private: QPoint _touchStart; }; -class CountryCodeInput : public FlatInput { - Q_OBJECT - -public: - CountryCodeInput(QWidget *parent, const style::FlatInput &st); - - public slots: - void startErasing(QKeyEvent *e); - void codeSelected(const QString &code); - -signals: - void codeChanged(const QString &code); - void addedToNumber(const QString &added); - -protected: - void correctValue(const QString &was, QString &now) override; - -private: - bool _nosignal; - -}; - -class PhonePartInput : public FlatInput { - Q_OBJECT - -public: - PhonePartInput(QWidget *parent, const style::FlatInput &st); - - public slots: - void addedToNumber(const QString &added); - void onChooseCode(const QString &code); - -signals: - void voidBackspace(QKeyEvent *e); - -protected: - void paintEvent(QPaintEvent *e) override; - void keyPressEvent(QKeyEvent *e) override; - - void correctValue(const QString &was, QString &now) override; - -private: - QVector _pattern; - -}; - enum CtrlEnterSubmit { CtrlEnterSubmitEnter, CtrlEnterSubmitCtrlEnter, @@ -439,7 +393,7 @@ public: _inner.clearFocus(); } - public slots: +public slots: void onTouchTimer(); void onDocumentContentsChange(int position, int charsRemoved, int charsAdded); @@ -610,7 +564,7 @@ public: _inner.setTextCursor(c); } - public slots: +public slots: void onTouchTimer(); void onDocumentContentsChange(int position, int charsRemoved, int charsAdded); @@ -715,20 +669,11 @@ private: class MaskedInputField : public QLineEdit { Q_OBJECT - T_WIDGET + T_WIDGET public: MaskedInputField(QWidget *parent, const style::InputField &st, const QString &placeholder = QString(), const QString &val = QString()); - bool event(QEvent *e) override; - void touchEvent(QTouchEvent *e); - void paintEvent(QPaintEvent *e) override; - void focusInEvent(QFocusEvent *e) override; - void focusOutEvent(QFocusEvent *e) override; - void keyPressEvent(QKeyEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - void contextMenuEvent(QContextMenuEvent *e) override; - void showError(); bool setPlaceholder(const QString &ph); @@ -757,7 +702,7 @@ public: updatePlaceholder(); } - public slots: +public slots: void onTextChange(const QString &text); void onCursorPositionChanged(int oldPosition, int position); @@ -773,6 +718,15 @@ signals: void blurred(); protected: + bool event(QEvent *e) override; + void touchEvent(QTouchEvent *e); + void paintEvent(QPaintEvent *e) override; + void focusInEvent(QFocusEvent *e) override; + void focusOutEvent(QFocusEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; + void enterEventHook(QEvent *e) { return QLineEdit::enterEvent(e); } @@ -780,7 +734,10 @@ protected: return QLineEdit::leaveEvent(e); } - virtual void correctValue(const QString &was, int32 wasCursor, QString &now, int32 &nowCursor); + virtual void correctValue(const QString &was, int32 wasCursor, QString &now, int32 &nowCursor) { + } + void setCorrectedText(QString &now, int &nowCursor, const QString &newText, int newPos); + virtual void paintPlaceholder(Painter &p); style::font phFont() { @@ -828,6 +785,52 @@ private: QPoint _touchStart; }; +class CountryCodeInput : public MaskedInputField { + Q_OBJECT + +public: + CountryCodeInput(QWidget *parent, const style::InputField &st); + +public slots: + void startErasing(QKeyEvent *e); + void codeSelected(const QString &code); + +signals: + void codeChanged(const QString &code); + void addedToNumber(const QString &added); + +protected: + void correctValue(const QString &was, int32 wasCursor, QString &now, int32 &nowCursor) override; + +private: + bool _nosignal; + +}; + +class PhonePartInput : public MaskedInputField { + Q_OBJECT + +public: + PhonePartInput(QWidget *parent, const style::InputField &st); + +public slots: + void addedToNumber(const QString &added); + void onChooseCode(const QString &code); + +signals: + void voidBackspace(QKeyEvent *e); + +protected: + void keyPressEvent(QKeyEvent *e) override; + + void correctValue(const QString &was, int32 wasCursor, QString &now, int32 &nowCursor) override; + void paintPlaceholder(Painter &p) override; + +private: + QVector _pattern; + +}; + class PasswordInput : public MaskedInputField { public: PasswordInput(QWidget *parent, const style::InputField &st, const QString &ph = QString(), const QString &val = QString()); diff --git a/Telegram/SourceFiles/ui/widgets/labels.cpp b/Telegram/SourceFiles/ui/widgets/labels.cpp index ae1be814e..84d36b6dc 100644 --- a/Telegram/SourceFiles/ui/widgets/labels.cpp +++ b/Telegram/SourceFiles/ui/widgets/labels.cpp @@ -43,6 +43,57 @@ TextParseOptions _labelMarkedOptions = { } // namespace +CrossFadeAnimation::CrossFadeAnimation(const style::color &bg) : _bg(bg) { +} + +void CrossFadeAnimation::addLine(Part was, Part now) { + _lines.push_back(Line(std_::move(was), std_::move(now))); +} + +void CrossFadeAnimation::paintFrame(Painter &p, float64 positionReady, float64 alphaWas, float64 alphaNow) { + if (_lines.isEmpty()) return; + + for_const (auto &line, _lines) { + paintLine(p, line, positionReady, alphaWas, alphaNow); + } +} + +void CrossFadeAnimation::paintLine(Painter &p, const Line &line, float64 positionReady, float64 alphaWas, float64 alphaNow) { + auto &snapshotWas = line.was.snapshot; + auto &snapshotNow = line.now.snapshot; + t_assert(!snapshotWas.isNull() || !snapshotNow.isNull()); + + auto positionWas = line.was.position; + auto positionNow = line.now.position; + auto left = anim::interpolate(positionWas.x(), positionNow.x(), positionReady); + auto topDelta = (snapshotNow.height() / cIntRetinaFactor()) - (snapshotWas.height() / cIntRetinaFactor()); + auto widthDelta = (snapshotNow.width() / cIntRetinaFactor()) - (snapshotWas.width() / cIntRetinaFactor()); + auto topWas = anim::interpolate(positionWas.y(), positionNow.y() + topDelta, positionReady); + auto topNow = topWas - topDelta; + + p.setOpacity(alphaWas); + if (!snapshotWas.isNull()) { + p.drawPixmap(left, topWas, snapshotWas); + if (topDelta > 0) { + p.fillRect(left, topWas - topDelta, snapshotWas.width() / cIntRetinaFactor(), topDelta, _bg); + } + } + if (widthDelta > 0) { + p.fillRect(left + (snapshotWas.width() / cIntRetinaFactor()), topNow, widthDelta, snapshotNow.height() / cIntRetinaFactor(), _bg); + } + + p.setOpacity(alphaNow); + if (!snapshotNow.isNull()) { + p.drawPixmap(left, topNow, snapshotNow); + if (topDelta < 0) { + p.fillRect(left, topNow + topDelta, snapshotNow.width() / cIntRetinaFactor(), -topDelta, _bg); + } + } + if (widthDelta < 0) { + p.fillRect(left + (snapshotNow.width() / cIntRetinaFactor()), topWas, -widthDelta, snapshotWas.height() / cIntRetinaFactor(), _bg); + } +} + LabelSimple::LabelSimple(QWidget *parent, const style::LabelSimple &st, const QString &value) : TWidget(parent) , _st(st) { setText(value); @@ -548,6 +599,72 @@ void FlatLabel::clickHandlerPressedChanged(const ClickHandlerPtr &action, bool a update(); } +std_::unique_ptr FlatLabel::CrossFade(FlatLabel *from, FlatLabel *to, const style::color &bg, QPoint fromPosition, QPoint toPosition) { + auto result = std_::make_unique(bg); + + struct Data { + QImage full; + QVector lineWidths; + int lineHeight = 0; + int lineAddTop = 0; + }; + auto prepareData = [&bg](FlatLabel *label) { + auto result = Data(); + result.full = QImage(label->size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + result.full.setDevicePixelRatio(cRetinaFactor()); + result.full.fill(bg->c); + Painter(&result.full).drawImage(0, 0, myGrabImage(label)); + auto textWidth = label->width() - label->_st.margin.left() - label->_st.margin.right(); + label->_text.countLineWidths(textWidth, &result.lineWidths); + result.lineHeight = label->_st.font->height; + auto addedHeight = (label->_tst.lineHeight - result.lineHeight); + if (addedHeight > 0) { + result.lineAddTop = addedHeight / 2; + result.lineHeight += addedHeight; + } + return std_::move(result); + }; + auto was = prepareData(from); + auto now = prepareData(to); + + auto maxLines = qMax(was.lineWidths.size(), now.lineWidths.size()); + auto fillDataTill = [maxLines](Data &data) { + for (auto i = data.lineWidths.size(); i != maxLines; ++i) { + data.lineWidths.push_back(-1); + } + }; + fillDataTill(was); + fillDataTill(now); + auto preparePart = [](FlatLabel *label, QPoint position, Data &data, int index, Data &other) { + auto result = CrossFadeAnimation::Part(); + auto lineWidth = data.lineWidths[index]; + if (lineWidth < 0) { + lineWidth = other.lineWidths[index]; + } + auto fullWidth = data.full.width() / cIntRetinaFactor(); + auto top = index * data.lineHeight + data.lineAddTop; + auto left = 0; + if (label->_st.align & Qt::AlignHCenter) { + left += (fullWidth - lineWidth) / 2; + } else if (label->_st.align & Qt::AlignRight) { + left += (fullWidth - lineWidth); + } + auto snapshotRect = data.full.rect().intersected(QRect(left * cIntRetinaFactor(), top * cIntRetinaFactor(), lineWidth * cIntRetinaFactor(), label->_st.font->height * cIntRetinaFactor())); + if (!snapshotRect.isEmpty()) { + result.snapshot = App::pixmapFromImageInPlace(data.full.copy(snapshotRect)); + result.snapshot.setDevicePixelRatio(cRetinaFactor()); + } + auto positionBase = position + label->pos(); + result.position = positionBase + QPoint(label->_st.margin.left() + left, label->_st.margin.top() + top); + return std_::move(result); + }; + for (int i = 0; i != maxLines; ++i) { + result->addLine(preparePart(from, fromPosition, was, i, now), preparePart(to, toPosition, now, i, was)); + } + + return std_::move(result); +} + Text::StateResult FlatLabel::dragActionUpdate() { auto m = mapFromGlobal(_lastMousePos); auto state = getTextState(m); diff --git a/Telegram/SourceFiles/ui/widgets/labels.h b/Telegram/SourceFiles/ui/widgets/labels.h index f3abfe607..d0da4da7e 100644 --- a/Telegram/SourceFiles/ui/widgets/labels.h +++ b/Telegram/SourceFiles/ui/widgets/labels.h @@ -26,6 +26,37 @@ namespace Ui { class PopupMenu; +class CrossFadeAnimation { +public: + CrossFadeAnimation(const style::color &bg); + + struct Part { + QPixmap snapshot; + QPoint position; + }; + void addLine(Part was, Part now); + + void paintFrame(Painter &p, float64 dt) { + auto progress = anim::linear(1., dt); + paintFrame(p, progress, 1. - progress, progress); + } + + void paintFrame(Painter &p, float64 positionReady, float64 alphaWas, float64 alphaNow); + +private: + struct Line { + Line(Part was, Part now) : was(std_::move(was)), now(std_::move(now)) { + } + Part was; + Part now; + }; + void paintLine(Painter &p, const Line &line, float64 positionReady, float64 alphaWas, float64 alphaNow); + + const style::color &_bg; + QList _lines; + +}; + class LabelSimple : public TWidget { public: LabelSimple(QWidget *parent, const style::LabelSimple &st = st::defaultLabelSimple, const QString &value = QString()); @@ -81,6 +112,8 @@ public: void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pressed) override; + static std_::unique_ptr CrossFade(FlatLabel *from, FlatLabel *to, const style::color &bg, QPoint fromPosition = QPoint(), QPoint toPosition = QPoint()); + protected: void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; diff --git a/Telegram/SourceFiles/ui/widgets/scroll_area.h b/Telegram/SourceFiles/ui/widgets/scroll_area.h index 8a5479acb..aefb37527 100644 --- a/Telegram/SourceFiles/ui/widgets/scroll_area.h +++ b/Telegram/SourceFiles/ui/widgets/scroll_area.h @@ -160,7 +160,7 @@ private: class SplittedWidgetOther; class ScrollArea : public QScrollArea { Q_OBJECT - T_WIDGET + T_WIDGET public: ScrollArea(QWidget *parent, const style::FlatScroll &st = st::defaultFlatScroll, bool handleTouch = true); diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h index ca72b0dd7..4a4e6d924 100644 --- a/Telegram/SourceFiles/window/section_widget.h +++ b/Telegram/SourceFiles/window/section_widget.h @@ -21,7 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "ui/twidget.h" -#include "window/slide_animation.h" +#include "window/window_slide_animation.h" namespace Window { diff --git a/Telegram/SourceFiles/window/slide_animation.cpp b/Telegram/SourceFiles/window/window_slide_animation.cpp similarity index 98% rename from Telegram/SourceFiles/window/slide_animation.cpp rename to Telegram/SourceFiles/window/window_slide_animation.cpp index bca18bfe7..6510908cf 100644 --- a/Telegram/SourceFiles/window/slide_animation.cpp +++ b/Telegram/SourceFiles/window/window_slide_animation.cpp @@ -19,7 +19,7 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "window/slide_animation.h" +#include "window/window_slide_animation.h" #include "styles/style_window.h" diff --git a/Telegram/SourceFiles/window/slide_animation.h b/Telegram/SourceFiles/window/window_slide_animation.h similarity index 100% rename from Telegram/SourceFiles/window/slide_animation.h rename to Telegram/SourceFiles/window/window_slide_animation.h diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 4747387c0..f709915f1 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -466,6 +466,8 @@ '<(src_loc)/ui/effects/ripple_animation.h', '<(src_loc)/ui/effects/round_checkbox.cpp', '<(src_loc)/ui/effects/round_checkbox.h', + '<(src_loc)/ui/effects/slide_animation.cpp', + '<(src_loc)/ui/effects/slide_animation.h', '<(src_loc)/ui/effects/widget_fade_wrap.cpp', '<(src_loc)/ui/effects/widget_fade_wrap.h', '<(src_loc)/ui/effects/widget_slide_wrap.cpp', @@ -546,8 +548,8 @@ '<(src_loc)/window/player_wrap_widget.h', '<(src_loc)/window/section_widget.cpp', '<(src_loc)/window/section_widget.h', - '<(src_loc)/window/slide_animation.cpp', - '<(src_loc)/window/slide_animation.h', + '<(src_loc)/window/window_slide_animation.cpp', + '<(src_loc)/window/window_slide_animation.h', '<(src_loc)/window/top_bar_widget.cpp', '<(src_loc)/window/top_bar_widget.h', '<(src_loc)/window/window_main_menu.cpp',