diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 78983bab6..58094bfa5 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -179,8 +179,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_photos_comment" = "Comment"; "lng_intro_qr_title" = "Scan From Mobile Telegram"; -"lng_intro_qr_description" = "Please scan this code from your Telegram on iOS or Android."; -"lng_intro_qr_skip" = "Log in using phone"; +"lng_intro_qr_step1" = "**1.** Open Telegram on your phone"; +"lng_intro_qr_step2" = "**2.** Go to **Settings** and tap \"Scan QR code\""; +"lng_intro_qr_step3" = "**3.** Scan this image to Log In"; +"lng_intro_qr_skip" = "Or log in using your phone number"; "lng_phone_title" = "Your Phone Number"; "lng_phone_desc" = "Please confirm your country code and\nenter your mobile phone number."; diff --git a/Telegram/SourceFiles/intro/intro.style b/Telegram/SourceFiles/intro/intro.style index 01852c497..95af195b7 100644 --- a/Telegram/SourceFiles/intro/intro.style +++ b/Telegram/SourceFiles/intro/intro.style @@ -74,8 +74,9 @@ introPlaneHeight: 38px; introHeight: 406px; introStepTopMin: 76px; introStepWidth: 380px; -introStepHeight: 266px; -introStepHeightAdd: 30px; +introNextTop: 266px; +introStepHeight: 384px; +introContentTopAdd: 30px; introStepHeightFull: 590px; introSlideDuration: 200; introCoverDuration: 200; @@ -160,6 +161,20 @@ introBackButton: IconButton(defaultIconButton) { } } -introQrTop: 90px; +introQrTop: 0px; introQrPixel: 50px; // large enough introQrMaxSize: 170px; +introQrLabelsWidth: 292px; +introQrTitle: FlatLabel(defaultFlatLabel) { + textFg: introTitleFg; + style: TextStyle(defaultTextStyle) { + font: font(20px semibold); + linkFont: font(20px semibold); + linkFontOver: font(20px semibold underline); + } +} +introQrTitleTop: 196px; +introQrStep: defaultFlatLabel; +introQrStepsTop: 232px; +introQrStepMargins: margins(0px, 8px, 0px, 0px); +introQrSkipTop: 360px; diff --git a/Telegram/SourceFiles/intro/intro_qr.cpp b/Telegram/SourceFiles/intro/intro_qr.cpp index 07770fcd4..a7b6c1679 100644 --- a/Telegram/SourceFiles/intro/intro_qr.cpp +++ b/Telegram/SourceFiles/intro/intro_qr.cpp @@ -7,11 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "intro/intro_qr.h" -#include "lang/lang_keys.h" #include "intro/introphone.h" +#include "lang/lang_keys.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/wrap/fade_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/text/text_utilities.h" #include "main/main_account.h" #include "boxes/confirm_box.h" #include "core/application.h" @@ -20,14 +22,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_intro.h" namespace Intro { +namespace details { namespace { [[nodiscard]] QImage TelegramLogoImage(int size) { - return Core::App().logo().scaled( - size, - size, + constexpr auto kScale = 0.8; + const auto used = int(size * kScale); + const auto adjusted = used + ((used % 2) + (size % 2)) % 2; + const auto image = Core::App().logo().scaled( + adjusted, + adjusted, Qt::KeepAspectRatio, Qt::SmoothTransformation); + auto result = QImage(size, size, QImage::Format_ARGB32_Premultiplied); + result.fill(Qt::transparent); + { + QPainter p(&result); + p.drawImage( + QRect( + (size - adjusted) / 2, + (size - adjusted) / 2, + adjusted, + adjusted), + image); + } + return result; } [[nodiscard]] QImage TelegramQrExact(const Qr::Data &data, int pixel) { @@ -46,17 +65,22 @@ namespace { } [[nodiscard]] QImage TelegramQr(const QString &text, int pixel, int max) { - return TelegramQr(Qr::Encode(text), pixel, max); + return TelegramQr( + Qr::Encode(text, Qr::Redundancy::Quartile), + pixel, + max); } [[nodiscard]] not_null PrepareQrWidget( not_null parent, - rpl::producer images) { + rpl::producer codes) { auto result = Ui::CreateChild(parent.get()); auto current = result->lifetime().make_state(); std::move( - images - ) | rpl::start_with_next([=](QImage &&image) { + codes + ) | rpl::map([](const QByteArray &code) { + return TelegramQr(code, st::introQrPixel, st::introQrMaxSize); + }) | rpl::start_with_next([=](QImage &&image) { result->resize(image.size() / cIntRetinaFactor()); *current = std::move(image); result->update(); @@ -77,12 +101,11 @@ namespace { QrWidget::QrWidget( QWidget *parent, not_null account, - not_null data) -: Step(parent, account, data) -, _code(PrepareQrWidget(this, _qrImages.events())) -, _refreshTimer([=] { refreshCode(); }) { - setTitleText(tr::lng_intro_qr_title()); - setDescriptionText(tr::lng_intro_qr_description()); + not_null data) + : Step(parent, account, data) + , _refreshTimer([=] { refreshCode(); }) { + setTitleText(rpl::single(QString())); + setDescriptionText(rpl::single(QString())); setErrorCentered(true); account->destroyStaleAuthorizationKeys(); @@ -91,20 +114,10 @@ QrWidget::QrWidget( checkForTokenUpdate(updates); }, lifetime()); - _code->widthValue( - ) | rpl::start_with_next([=] { - updateCodeGeometry(); - }, _code->lifetime()); - _code->show(); - + setupControls(); refreshCode(); } -void QrWidget::resizeEvent(QResizeEvent *e) { - Step::resizeEvent(e); - updateCodeGeometry(); -} - void QrWidget::checkForTokenUpdate(const MTPUpdates &updates) { updates.match([&](const MTPDupdateShort &data) { checkForTokenUpdate(data.vupdate()); @@ -130,18 +143,77 @@ void QrWidget::checkForTokenUpdate(const MTPUpdate &update) { }, [](const auto &) {}); } -void QrWidget::updateCodeGeometry() { - _code->moveToLeft( - (width() - _code->width()) / 2, - contentTop() + st::introQrTop); -} - void QrWidget::submit() { goReplace(); } rpl::producer QrWidget::nextButtonText() const { - return tr::lng_intro_qr_skip(); + return rpl::single(QString()); +} + +void QrWidget::setupControls() { + const auto code = PrepareQrWidget(this, _qrCodes.events()); + rpl::combine( + sizeValue(), + code->widthValue() + ) | rpl::start_with_next([=](QSize size, int codeWidth) { + code->moveToLeft( + (size.width() - codeWidth) / 2, + contentTop() + st::introQrTop); + }, code->lifetime()); + + const auto title = Ui::CreateChild( + this, + tr::lng_intro_qr_title(), + st::introQrTitle); + rpl::combine( + sizeValue(), + title->widthValue() + ) | rpl::start_with_next([=](QSize size, int titleWidth) { + title->moveToLeft( + (size.width() - st::introQrLabelsWidth) / 2, + contentTop() + st::introQrTitleTop); + }, title->lifetime()); + + const auto steps = Ui::CreateChild(this); + const auto texts = { + tr::lng_intro_qr_step1, + tr::lng_intro_qr_step2, + tr::lng_intro_qr_step3, + }; + for (const auto &text : texts) { + steps->add( + object_ptr( + this, + text(Ui::Text::RichLangValue), + st::introQrStep), + st::introQrStepMargins); + } + steps->resizeToWidth(st::introQrLabelsWidth); + rpl::combine( + sizeValue(), + steps->widthValue() + ) | rpl::start_with_next([=](QSize size, int stepsWidth) { + steps->moveToLeft( + (size.width() - stepsWidth) / 2, + contentTop() + st::introQrStepsTop); + }, steps->lifetime()); + + const auto skip = Ui::CreateChild( + this, + tr::lng_intro_qr_skip(tr::now)); + rpl::combine( + sizeValue(), + skip->widthValue() + ) | rpl::start_with_next([=](QSize size, int skipWidth) { + skip->moveToLeft( + (size.width() - skipWidth) / 2, + contentTop() + st::introQrSkipTop); + }, skip->lifetime()); + + skip->setClickedCallback([=] { + goNext(); + }); } void QrWidget::refreshCode() { @@ -188,8 +260,7 @@ void QrWidget::showTokenError(const RPCError &error) { void QrWidget::showToken(const QByteArray &token) { const auto encoded = token.toBase64(QByteArray::Base64UrlEncoding); - const auto text = "tg_login/" + encoded; - _qrImages.fire(TelegramQr(text, st::introQrPixel, st::introQrMaxSize)); + _qrCodes.fire_copy("tg_login/" + encoded); } void QrWidget::importTo(MTP::DcId dcId, const QByteArray &token) { @@ -223,7 +294,7 @@ void QrWidget::done(const MTPauth_Authorization &authorization) { void QrWidget::activate() { Step::activate(); - _code->show(); + showChildren(); } void QrWidget::finished() { @@ -237,4 +308,5 @@ void QrWidget::cancelled() { _api.request(base::take(_requestId)).cancel(); } +} // namespace details } // namespace Intro diff --git a/Telegram/SourceFiles/intro/intro_qr.h b/Telegram/SourceFiles/intro/intro_qr.h index af2d3f7fc..fbcf1f5a9 100644 --- a/Telegram/SourceFiles/intro/intro_qr.h +++ b/Telegram/SourceFiles/intro/intro_qr.h @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/countryinput.h" -#include "intro/introwidget.h" +#include "intro/intro_step.h" #include "mtproto/sender.h" #include "base/timer.h" @@ -20,13 +20,14 @@ class FlatLabel; } // namespace Ui namespace Intro { +namespace details { -class QrWidget : public Widget::Step { +class QrWidget : public Step { public: QrWidget( QWidget *parent, not_null account, - not_null data); + not_null data); void activate() override; void finished() override; @@ -38,12 +39,9 @@ public: return true; } -protected: - void resizeEvent(QResizeEvent *e) override; - private: + void setupControls(); void refreshCode(); - void updateCodeGeometry(); void checkForTokenUpdate(const MTPUpdates &updates); void checkForTokenUpdate(const MTPUpdate &update); void handleTokenResult(const MTPauth_LoginToken &result); @@ -52,8 +50,7 @@ private: void showToken(const QByteArray &token); void done(const MTPauth_Authorization &authorization); - rpl::event_stream _qrImages; - not_null _code; + rpl::event_stream _qrCodes; base::Timer _refreshTimer; MTP::Sender _api; mtpRequestId _requestId = 0; @@ -61,4 +58,5 @@ private: }; +} // namespace details } // namespace Intro diff --git a/Telegram/SourceFiles/intro/intro_step.cpp b/Telegram/SourceFiles/intro/intro_step.cpp new file mode 100644 index 000000000..99100704c --- /dev/null +++ b/Telegram/SourceFiles/intro/intro_step.cpp @@ -0,0 +1,508 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "intro/intro_step.h" + +#include "intro/introwidget.h" +#include "storage/localstorage.h" +#include "lang/lang_keys.h" +#include "lang/lang_cloud_manager.h" +#include "main/main_account.h" +#include "apiwrap.h" +#include "mainwindow.h" +#include "boxes/confirm_box.h" +#include "ui/text/text_utilities.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/fade_wrap.h" +#include "ui/effects/slide_animation.h" +#include "data/data_user.h" +#include "data/data_auto_download.h" +#include "window/themes/window_theme.h" +#include "facades.h" +#include "app.h" +#include "styles/style_intro.h" +#include "styles/style_window.h" + +namespace Intro { +namespace details { +namespace { + +void PrepareSupportMode() { + using ::Data::AutoDownload::Full; + + anim::SetDisabled(true); + Local::writeSettings(); + + Global::SetDesktopNotify(false); + Global::SetSoundNotify(false); + Auth().settings().autoDownload() = Full::FullDisabled(); + Local::writeUserSettings(); +} + +} // namespace + +Step::CoverAnimation::~CoverAnimation() = default; + +Step::Step( + QWidget *parent, + not_null account, + not_null data, + bool hasCover) +: RpWidget(parent) +, _account(account) +, _data(data) +, _hasCover(hasCover) +, _title(this, _hasCover ? st::introCoverTitle : st::introTitle) +, _description( + this, + object_ptr( + this, + _hasCover + ? st::introCoverDescription + : st::introDescription)) { + hide(); + subscribe(Window::Theme::Background(), [this]( + const Window::Theme::BackgroundUpdate &update) { + if (update.paletteChanged()) { + if (!_coverMask.isNull()) { + _coverMask = QPixmap(); + prepareCoverMask(); + } + } + }); + + _errorText.value( + ) | rpl::start_with_next([=](const QString &text) { + refreshError(text); + }, lifetime()); + + _titleText.value( + ) | rpl::start_with_next([=](const QString &text) { + _title->setText(text); + updateLabelsPosition(); + }, lifetime()); + + _descriptionText.value( + ) | rpl::start_with_next([=](const TextWithEntities &text) { + _description->entity()->setMarkedText(text); + updateLabelsPosition(); + }, lifetime()); +} + +Step::~Step() = default; + +rpl::producer Step::nextButtonText() const { + return tr::lng_intro_next(); +} + +void Step::goBack() { + if (_goCallback) { + _goCallback(nullptr, Direction::Back); + } +} + +void Step::goNext(Step *step) { + if (_goCallback) { + _goCallback(step, Direction::Forward); + } +} + +void Step::goReplace(Step *step) { + if (_goCallback) { + _goCallback(step, Direction::Replace); + } +} + +void Step::finish(const MTPUser &user, QImage &&photo) { + if (user.type() != mtpc_user + || !user.c_user().is_self() + || !user.c_user().vid().v) { + // No idea what to do here. + // We could've reset intro and MTP, but this really should not happen. + Ui::show(Box("Internal error: bad user.is_self() after sign in.")); + return; + } + + // Save the default language if we've suggested some other and user ignored it. + const auto currentId = Lang::Current().id(); + const auto defaultId = Lang::DefaultLanguageId(); + const auto suggested = Lang::CurrentCloudManager().suggestedLanguage(); + if (currentId.isEmpty() && !suggested.isEmpty() && suggested != defaultId) { + Lang::Current().switchToId(Lang::DefaultLanguage()); + Local::writeLangPack(); + } + + const auto account = _account; + const auto weak = base::make_weak(account.get()); + account->createSession(user); + Local::writeMtpData(); + App::wnd()->setupMain(); + + // "this" is already deleted here by creating the main widget. + if (weak && account->sessionExists()) { + auto &session = account->session(); + if (!photo.isNull()) { + session.api().uploadPeerPhoto(session.user(), std::move(photo)); + } + if (session.supportMode()) { + PrepareSupportMode(); + } + } +} + +void Step::paintEvent(QPaintEvent *e) { + Painter p(this); + paintAnimated(p, e->rect()); +} + +void Step::resizeEvent(QResizeEvent *e) { + updateLabelsPosition(); +} + +void Step::updateLabelsPosition() { + Ui::SendPendingMoveResizeEvents(_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->resizeToWidth(st::introDescription.minWidth); + _description->moveToLeft(contentLeft() + st::buttonRadius, contentTop() + st::introDescriptionTop); + } + if (_error) { + if (_errorCentered) { + _error->entity()->resizeToWidth(width()); + } + Ui::SendPendingMoveResizeEvents(_error->entity()); + auto errorLeft = _errorCentered ? 0 : (contentLeft() + st::buttonRadius); + auto errorTop = contentTop() + (_errorBelowLink ? st::introErrorBelowLinkTop : st::introErrorTop); + _error->moveToLeft(errorLeft, errorTop); + } +} + +void Step::setTitleText(rpl::producer titleText) { + _titleText = std::move(titleText); +} + +void Step::setDescriptionText( + rpl::producer descriptionText) { + setDescriptionText( + std::move(descriptionText) | Ui::Text::ToWithEntities()); +} + +void Step::setDescriptionText( + rpl::producer richDescriptionText) { + _descriptionText = std::move(richDescriptionText); +} + +void Step::showFinished() { + _a_show.stop(); + _coverAnimation = CoverAnimation(); + _slideAnimation.reset(); + prepareCoverMask(); + activate(); +} + +bool Step::paintAnimated(Painter &p, QRect clip) { + if (_slideAnimation) { + _slideAnimation->paintFrame(p, (width() - st::introStepWidth) / 2, contentTop(), width()); + if (!_slideAnimation->animating()) { + showFinished(); + return false; + } + return true; + } + + auto dt = _a_show.value(1.); + if (!_a_show.animating()) { + if (hasCover()) { + paintCover(p, 0); + } + if (_coverAnimation.title) { + showFinished(); + } + if (!QRect(0, contentTop(), width(), st::introStepHeight).intersects(clip)) { + return true; + } + return false; + } + + auto progress = (hasCover() ? anim::easeOutCirc(1., dt) : anim::linear(1., dt)); + auto arrivingAlpha = progress; + auto departingAlpha = 1. - progress; + auto showCoverMethod = progress; + auto hideCoverMethod = progress; + auto coverTop = (hasCover() ? anim::interpolate(-st::introCoverHeight, 0, showCoverMethod) : anim::interpolate(0, -st::introCoverHeight, hideCoverMethod)); + + paintCover(p, coverTop); + + 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 Step::fillSentCodeData(const MTPDauth_sentCode &data) { + const auto &type = data.vtype(); + 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; + } +} + +void Step::showDescription() { + _description->show(anim::type::normal); +} + +void Step::hideDescription() { + _description->hide(anim::type::normal); +} + +void 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 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()); + Assert(mask.depth() == (sizeof(uint32) << 3)); + auto maskIntsPerLineAdded = (mask.bytesPerLine() >> 2) - maskWidth; + 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 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 Step::contentLeft() const { + return (width() - st::introNextButton.width) / 2; +} + +int Step::contentTop() const { + auto result = (height() - st::introHeight) / 2; + accumulate_max(result, st::introStepTopMin); + if (_hasCover) { + auto added = 1. - snap(float64(height() - st::windowMinHeight) / (st::introStepHeightFull - st::windowMinHeight), 0., 1.); + result += qRound(added * st::introContentTopAdd); + } + return result; +} + +void Step::setErrorCentered(bool centered) { + _errorCentered = centered; + _error.destroy(); +} + +void Step::setErrorBelowLink(bool below) { + _errorBelowLink = below; + if (_error) { + updateLabelsPosition(); + } +} + +void Step::showError(rpl::producer text) { + _errorText = std::move(text); +} + +void Step::refreshError(const QString &text) { + if (text.isEmpty()) { + if (_error) _error->hide(anim::type::normal); + } else { + if (!_error) { + _error.create( + this, + object_ptr( + this, + _errorCentered + ? st::introErrorCentered + : st::introError)); + _error->hide(anim::type::instant); + } + _error->entity()->setText(text); + updateLabelsPosition(); + _error->show(anim::type::normal); + } +} + +void Step::prepareShowAnimated(Step *after) { + setInnerFocus(); + 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); + } +} + +Step::CoverAnimation 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 result; +} + +QPixmap Step::prepareContentSnapshot() { + auto otherTop = _description->y() + _description->height(); + auto otherRect = myrtlrect(contentLeft(), otherTop, st::introStepWidth, height() - otherTop); + return Ui::GrabWidget(this, otherRect); +} + +QPixmap Step::prepareSlideAnimation() { + auto grabLeft = (width() - st::introStepWidth) / 2; + auto grabTop = contentTop(); + return Ui::GrabWidget( + this, + QRect(grabLeft, grabTop, st::introStepWidth, st::introStepHeight)); +} + +void Step::showAnimated(Direction direction) { + setFocus(); + show(); + hideChildren(); + if (_slideAnimation) { + auto slideLeft = (direction == Direction::Back); + _slideAnimation->start( + slideLeft, + [=] { update(0, contentTop(), width(), st::introStepHeight); }, + st::introSlideDuration); + } else { + _a_show.start([this] { update(); }, 0., 1., st::introCoverDuration); + } +} + +void Step::setGoCallback(Fn callback) { + _goCallback = std::move(callback); +} + +void Step::setShowResetCallback(Fn callback) { + _showResetCallback = std::move(callback); +} + +void Step::setShowTermsCallback(Fn callback) { + _showTermsCallback = std::move(callback); +} + +void Step::setAcceptTermsCallback( + Fn callback)> callback) { + _acceptTermsCallback = std::move(callback); +} + +void Step::showFast() { + show(); + showFinished(); +} + +bool Step::animating() const { + return (_slideAnimation && _slideAnimation->animating()) || _a_show.animating(); +} + +bool Step::hasCover() const { + return _hasCover; +} + +bool Step::hasBack() const { + return false; +} + +void Step::activate() { + _title->show(); + _description->show(anim::type::instant); + if (!_errorText.current().isEmpty()) { + _error->show(anim::type::instant); + } +} + +void Step::cancelled() { +} + +void Step::finished() { + hide(); +} + +} // namespace details +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/intro_step.h b/Telegram/SourceFiles/intro/intro_step.h new file mode 100644 index 000000000..81ed97259 --- /dev/null +++ b/Telegram/SourceFiles/intro/intro_step.h @@ -0,0 +1,192 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/object_ptr.h" +#include "mtproto/sender.h" +#include "ui/rp_widget.h" +#include "ui/effects/animations.h" + +namespace Main { +class Account; +} // namespace Main; + +namespace Ui { +class SlideAnimation; +class CrossFadeAnimation; +class FlatLabel; +template +class FadeWrap; +} // namespace Ui + +namespace Intro { +namespace details { + +struct Data; +enum class Direction; + +class Step + : public Ui::RpWidget + , public RPCSender + , protected base::Subscriber { +public: + Step( + QWidget *parent, + not_null account, + not_null data, + bool hasCover = false); + ~Step(); + + [[nodiscard]] Main::Account &account() const { + return *_account; + } + + virtual void finishInit() { + } + virtual void setInnerFocus() { + setFocus(); + } + + void setGoCallback( + Fn callback); + void setShowResetCallback(Fn callback); + void setShowTermsCallback( + Fn callback); + void setAcceptTermsCallback( + Fn callback)> callback); + + void prepareShowAnimated(Step *after); + void showAnimated(Direction direction); + void showFast(); + [[nodiscard]] bool animating() const; + + [[nodiscard]] bool hasCover() const; + [[nodiscard]] virtual bool hasBack() const; + virtual void activate(); + virtual void cancelled(); + virtual void finished(); + + virtual void submit() = 0; + [[nodiscard]] virtual rpl::producer nextButtonText() const; + + [[nodiscard]] int contentLeft() const; + [[nodiscard]] int contentTop() const; + + void setErrorCentered(bool centered); + void setErrorBelowLink(bool below); + void showError(rpl::producer text); + void hideError() { + showError(rpl::single(QString())); + } + +protected: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + + void setTitleText(rpl::producer titleText); + void setDescriptionText(rpl::producer descriptionText); + void setDescriptionText( + rpl::producer richDescriptionText); + bool paintAnimated(Painter &p, QRect clip); + + void fillSentCodeData(const MTPDauth_sentCode &type); + + void showDescription(); + void hideDescription(); + + [[nodiscard]] not_null getData() const { + return _data; + } + void finish(const MTPUser &user, QImage &&photo = QImage()); + + void goBack(); + + template + void goNext() { + goNext(new StepType(parentWidget(), _account, _data)); + } + + template + void goReplace() { + goReplace(new StepType(parentWidget(), _account, _data)); + } + + void showResetButton() { + if (_showResetCallback) _showResetCallback(); + } + void showTerms() { + if (_showTermsCallback) _showTermsCallback(); + } + void acceptTerms(Fn callback) { + if (_acceptTermsCallback) { + _acceptTermsCallback(callback); + } + } + +private: + struct CoverAnimation { + CoverAnimation() = default; + CoverAnimation(CoverAnimation &&other) = default; + CoverAnimation &operator=(CoverAnimation &&other) = default; + ~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); + void refreshError(const QString &text); + + void goNext(Step *step); + void goReplace(Step *step); + + [[nodiscard]] CoverAnimation prepareCoverAnimation(Step *step); + [[nodiscard]] QPixmap prepareContentSnapshot(); + [[nodiscard]] QPixmap prepareSlideAnimation(); + void showFinished(); + + void prepareCoverMask(); + void paintCover(Painter &p, int top); + + const not_null _account; + const not_null _data; + + bool _hasCover = false; + Fn _goCallback; + Fn _showResetCallback; + Fn _showTermsCallback; + Fn callback)> _acceptTermsCallback; + + rpl::variable _titleText; + object_ptr _title; + rpl::variable _descriptionText; + object_ptr> _description; + + bool _errorCentered = false; + bool _errorBelowLink = false; + rpl::variable _errorText; + object_ptr> _error = { nullptr }; + + Ui::Animations::Simple _a_show; + CoverAnimation _coverAnimation; + std::unique_ptr _slideAnimation; + QPixmap _coverMask; + +}; + + +} // namespace details +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/introcode.cpp b/Telegram/SourceFiles/intro/introcode.cpp index 32eee158b..d8d6fce51 100644 --- a/Telegram/SourceFiles/intro/introcode.cpp +++ b/Telegram/SourceFiles/intro/introcode.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_intro.h" namespace Intro { +namespace details { CodeInput::CodeInput( QWidget *parent, @@ -77,7 +78,7 @@ void CodeInput::correctValue(const QString &was, int wasCursor, QString &now, in CodeWidget::CodeWidget( QWidget *parent, not_null account, - not_null data) + not_null data) : Step(parent, account, data) , _noTelegramCode(this, tr::lng_code_no_telegram(tr::now), st::introLink) , _code(this, st::introCode, tr::lng_code_ph()) @@ -118,7 +119,7 @@ void CodeWidget::updateDescText() { _noTelegramCode->hide(); _callStatus = getData()->callStatus; _callTimeout = getData()->callTimeout; - if (_callStatus == Widget::Data::CallStatus::Waiting && !_callTimer->isActive()) { + if (_callStatus == CallStatus::Waiting && !_callTimer->isActive()) { _callTimer->start(1000); } } @@ -131,7 +132,7 @@ void CodeWidget::updateCallText() { return QString(); } switch (_callStatus) { - case Widget::Data::CallStatus::Waiting: { + case CallStatus::Waiting: { if (_callTimeout >= 3600) { return tr::lng_code_call( tr::now, @@ -150,9 +151,9 @@ void CodeWidget::updateCallText() { qsl("%1").arg(_callTimeout % 60, 2, 10, QChar('0'))); } } break; - case Widget::Data::CallStatus::Calling: + case CallStatus::Calling: return tr::lng_code_calling(tr::now); - case Widget::Data::CallStatus::Called: + case CallStatus::Called: return tr::lng_code_called(tr::now); } return QString(); @@ -295,9 +296,9 @@ void CodeWidget::onInputChange() { } void CodeWidget::onSendCall() { - if (_callStatus == Widget::Data::CallStatus::Waiting) { + if (_callStatus == CallStatus::Waiting) { if (--_callTimeout <= 0) { - _callStatus = Widget::Data::CallStatus::Calling; + _callStatus = CallStatus::Calling; _callTimer->stop(); _callRequestId = MTP::send(MTPauth_ResendCode(MTP_string(getData()->phone), MTP_bytes(getData()->phoneHash)), rpcDone(&CodeWidget::callDone)); } else { @@ -313,8 +314,8 @@ void CodeWidget::callDone(const MTPauth_SentCode &v) { fillSentCodeData(v.c_auth_sentCode()); _code->setDigitsCountMax(getData()->codeLength); } - if (_callStatus == Widget::Data::CallStatus::Calling) { - _callStatus = Widget::Data::CallStatus::Called; + if (_callStatus == CallStatus::Calling) { + _callStatus = CallStatus::Called; getData()->callStatus = _callStatus; getData()->callTimeout = _callTimeout; updateCallText(); @@ -406,10 +407,10 @@ void CodeWidget::noTelegramCodeDone(const MTPauth_SentCode &result) { _code->setDigitsCountMax(getData()->codeLength); const auto next = d.vnext_type(); if (next && next->type() == mtpc_auth_codeTypeCall) { - getData()->callStatus = Widget::Data::CallStatus::Waiting; + getData()->callStatus = CallStatus::Waiting; getData()->callTimeout = d.vtimeout().value_or(60); } else { - getData()->callStatus = Widget::Data::CallStatus::Disabled; + getData()->callStatus = CallStatus::Disabled; getData()->callTimeout = 0; } getData()->codeByTelegram = false; @@ -435,4 +436,5 @@ bool CodeWidget::noTelegramCodeFail(const RPCError &error) { return false; } +} // namespace details } // namespace Intro diff --git a/Telegram/SourceFiles/intro/introcode.h b/Telegram/SourceFiles/intro/introcode.h index 527715035..5aaaeb352 100644 --- a/Telegram/SourceFiles/intro/introcode.h +++ b/Telegram/SourceFiles/intro/introcode.h @@ -7,8 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "intro/introwidget.h" +#include "intro/intro_step.h" #include "ui/widgets/input_fields.h" +#include "intro/introwidget.h" namespace Ui { class RoundButton; @@ -17,6 +18,9 @@ class FlatLabel; } // namespace Ui namespace Intro { +namespace details { + +enum class CallStatus; class CodeInput final : public Ui::MaskedInputField { public: @@ -35,14 +39,14 @@ private: }; -class CodeWidget : public Widget::Step { +class CodeWidget : public Step { Q_OBJECT public: CodeWidget( QWidget *parent, not_null account, - not_null data); + not_null data); bool hasBack() const override { return true; @@ -89,7 +93,7 @@ private: mtpRequestId _sentRequest = 0; object_ptr _callTimer; - Widget::Data::CallStatus _callStatus; + CallStatus _callStatus = CallStatus(); int _callTimeout; mtpRequestId _callRequestId = 0; object_ptr _callLabel; @@ -98,4 +102,5 @@ private: }; +} // namespace details } // namespace Intro diff --git a/Telegram/SourceFiles/intro/introphone.cpp b/Telegram/SourceFiles/intro/introphone.cpp index ea838a170..e6bee5332 100644 --- a/Telegram/SourceFiles/intro/introphone.cpp +++ b/Telegram/SourceFiles/intro/introphone.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" namespace Intro { +namespace details { namespace { bool AllowPhoneAttempt(const QString &phone) { @@ -34,7 +35,7 @@ bool AllowPhoneAttempt(const QString &phone) { PhoneWidget::PhoneWidget( QWidget *parent, not_null account, - not_null data) + not_null data) : Step(parent, account, data) , _country(this, st::introCountry) , _code(this, st::introCountryCode) @@ -52,7 +53,7 @@ PhoneWidget::PhoneWidget( setTitleText(tr::lng_phone_title()); setDescriptionText(tr::lng_phone_desc()); - subscribe(getData()->updated, [this] { countryChanged(); }); + subscribe(getData()->updated, [=] { countryChanged(); }); setErrorCentered(true); if (!_country->onChooseCountry(getData()->country)) { @@ -149,10 +150,10 @@ void PhoneWidget::phoneSubmitDone(const MTPauth_SentCode &result) { getData()->phoneHash = qba(d.vphone_code_hash()); const auto next = d.vnext_type(); if (next && next->type() == mtpc_auth_codeTypeCall) { - getData()->callStatus = Widget::Data::CallStatus::Waiting; + getData()->callStatus = CallStatus::Waiting; getData()->callTimeout = d.vtimeout().value_or(60); } else { - getData()->callStatus = Widget::Data::CallStatus::Disabled; + getData()->callStatus = CallStatus::Disabled; getData()->callTimeout = 0; } goNext(); @@ -220,4 +221,5 @@ void PhoneWidget::cancelled() { MTP::cancel(base::take(_sentRequest)); } +} // namespace details } // namespace Intro diff --git a/Telegram/SourceFiles/intro/introphone.h b/Telegram/SourceFiles/intro/introphone.h index 71316cdfe..17ce9abce 100644 --- a/Telegram/SourceFiles/intro/introphone.h +++ b/Telegram/SourceFiles/intro/introphone.h @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/countryinput.h" -#include "intro/introwidget.h" +#include "intro/intro_step.h" namespace Ui { class PhonePartInput; @@ -18,15 +18,16 @@ class FlatLabel; } // namespace Ui namespace Intro { +namespace details { -class PhoneWidget : public Widget::Step { +class PhoneWidget : public Step { Q_OBJECT public: PhoneWidget( QWidget *parent, not_null account, - not_null data); + not_null data); void selectCountry(const QString &country); @@ -72,4 +73,5 @@ private: }; +} // namespace details } // namespace Intro diff --git a/Telegram/SourceFiles/intro/intropwdcheck.cpp b/Telegram/SourceFiles/intro/intropwdcheck.cpp index b8d31d8ab..db2e807b2 100644 --- a/Telegram/SourceFiles/intro/intropwdcheck.cpp +++ b/Telegram/SourceFiles/intro/intropwdcheck.cpp @@ -7,8 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "intro/intropwdcheck.h" -#include "styles/style_intro.h" -#include "styles/style_boxes.h" +#include "intro/introwidget.h" #include "core/file_utilities.h" #include "core/core_cloud_password.h" #include "boxes/confirm_box.h" @@ -18,13 +17,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/input_fields.h" #include "ui/widgets/labels.h" #include "base/openssl_help.h" +#include "styles/style_intro.h" +#include "styles/style_boxes.h" namespace Intro { +namespace details { PwdCheckWidget::PwdCheckWidget( QWidget *parent, not_null account, - not_null data) + not_null data) : Step(parent, account, data) , _request(getData()->pwdRequest) , _hasRecovery(getData()->hasRecovery) @@ -389,4 +391,5 @@ rpl::producer PwdCheckWidget::nextButtonText() const { return tr::lng_intro_submit(); } +} // namespace details } // namespace Intro diff --git a/Telegram/SourceFiles/intro/intropwdcheck.h b/Telegram/SourceFiles/intro/intropwdcheck.h index a0fc7b555..5113781b5 100644 --- a/Telegram/SourceFiles/intro/intropwdcheck.h +++ b/Telegram/SourceFiles/intro/intropwdcheck.h @@ -7,7 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "intro/introwidget.h" +#include "intro/intro_step.h" +#include "core/core_cloud_password.h" #include "mtproto/sender.h" namespace Ui { @@ -18,15 +19,16 @@ class LinkButton; } // namespace Ui namespace Intro { +namespace details { -class PwdCheckWidget : public Widget::Step, private MTP::Sender { +class PwdCheckWidget : public Step, private MTP::Sender { Q_OBJECT public: PwdCheckWidget( QWidget *parent, not_null account, - not_null data); + not_null data); void setInnerFocus() override; void activate() override; @@ -81,4 +83,5 @@ private: }; +} // namespace details } // namespace Intro diff --git a/Telegram/SourceFiles/intro/introsignup.cpp b/Telegram/SourceFiles/intro/introsignup.cpp index 01d71d369..9594820b9 100644 --- a/Telegram/SourceFiles/intro/introsignup.cpp +++ b/Telegram/SourceFiles/intro/introsignup.cpp @@ -7,8 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "intro/introsignup.h" -#include "styles/style_intro.h" -#include "styles/style_boxes.h" +#include "intro/introwidget.h" #include "core/file_utilities.h" #include "boxes/photo_crop_box.h" #include "boxes/confirm_box.h" @@ -17,23 +16,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/input_fields.h" #include "ui/widgets/labels.h" #include "ui/special_buttons.h" +#include "styles/style_intro.h" +#include "styles/style_boxes.h" namespace Intro { +namespace details { SignupWidget::SignupWidget( QWidget *parent, not_null account, - not_null data) -: Step(parent, account, data) -, _photo( - this, - tr::lng_settings_crop_profile(tr::now), - Ui::UserpicButton::Role::ChangePhoto, - st::defaultUserpicButton) -, _first(this, st::introName, tr::lng_signup_firstname()) -, _last(this, st::introName, tr::lng_signup_lastname()) -, _invertOrder(langFirstNameGoesSecond()) -, _checkRequest(this) { + not_null data) + : Step(parent, account, data) + , _photo( + this, + tr::lng_settings_crop_profile(tr::now), + Ui::UserpicButton::Role::ChangePhoto, + st::defaultUserpicButton) + , _first(this, st::introName, tr::lng_signup_firstname()) + , _last(this, st::introName, tr::lng_signup_lastname()) + , _invertOrder(langFirstNameGoesSecond()) + , _checkRequest(this) { subscribe(Lang::Current().updated(), [this] { refreshLang(); }); if (_invertOrder) { setTabOrder(_last, _first); @@ -236,4 +238,5 @@ rpl::producer SignupWidget::nextButtonText() const { return tr::lng_intro_finish(); } +} // namespace details } // namespace Intro diff --git a/Telegram/SourceFiles/intro/introsignup.h b/Telegram/SourceFiles/intro/introsignup.h index 7eadd5ae8..f9969d6d4 100644 --- a/Telegram/SourceFiles/intro/introsignup.h +++ b/Telegram/SourceFiles/intro/introsignup.h @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "intro/introwidget.h" +#include "intro/intro_step.h" namespace Ui { class RoundButton; @@ -16,15 +16,16 @@ class UserpicButton; } // namespace Ui namespace Intro { +namespace details { -class SignupWidget : public Widget::Step { +class SignupWidget : public Step { Q_OBJECT public: SignupWidget( QWidget *parent, not_null account, - not_null data); + not_null data); void finishInit() override; void setInnerFocus() override; @@ -62,4 +63,5 @@ private: }; +} // namespace details } // namespace Intro diff --git a/Telegram/SourceFiles/intro/introstart.cpp b/Telegram/SourceFiles/intro/introstart.cpp index cd70a996e..e5aab0612 100644 --- a/Telegram/SourceFiles/intro/introstart.cpp +++ b/Telegram/SourceFiles/intro/introstart.cpp @@ -13,11 +13,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" namespace Intro { +namespace details { StartWidget::StartWidget( QWidget *parent, not_null account, - not_null data) + not_null data) : Step(parent, account, data, true) { setMouseTracking(true); setTitleText(rpl::single(qsl("Telegram Desktop"))); @@ -33,4 +34,5 @@ rpl::producer StartWidget::nextButtonText() const { return tr::lng_start_msgs(); } +} // namespace details } // namespace Intro diff --git a/Telegram/SourceFiles/intro/introstart.h b/Telegram/SourceFiles/intro/introstart.h index 9f7048f6f..4b3f072cd 100644 --- a/Telegram/SourceFiles/intro/introstart.h +++ b/Telegram/SourceFiles/intro/introstart.h @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "intro/introwidget.h" +#include "intro/intro_step.h" namespace Ui { class FlatLabel; @@ -16,17 +16,19 @@ class RoundButton; } // namespace Ui namespace Intro { +namespace details { -class StartWidget : public Widget::Step { +class StartWidget : public Step { public: StartWidget( QWidget *parent, not_null account, - not_null data); + not_null data); void submit() override; rpl::producer nextButtonText() const override; }; +} // namespace details } // namespace Intro diff --git a/Telegram/SourceFiles/intro/introwidget.cpp b/Telegram/SourceFiles/intro/introwidget.cpp index 6ffe8b060..c183069f6 100644 --- a/Telegram/SourceFiles/intro/introwidget.cpp +++ b/Telegram/SourceFiles/intro/introwidget.cpp @@ -7,55 +7,34 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "intro/introwidget.h" -#include "lang/lang_keys.h" -#include "lang/lang_file_parser.h" -#include "storage/localstorage.h" #include "intro/introstart.h" #include "intro/introphone.h" #include "intro/introcode.h" #include "intro/introsignup.h" #include "intro/intropwdcheck.h" +#include "lang/lang_keys.h" +#include "lang/lang_cloud_manager.h" +#include "storage/localstorage.h" #include "main/main_account.h" -#include "mainwidget.h" -#include "apiwrap.h" #include "mainwindow.h" -#include "core/application.h" #include "boxes/confirm_box.h" -#include "ui/text/text.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/wrap/fade_wrap.h" -#include "ui/effects/slide_animation.h" #include "core/update_checker.h" #include "window/window_slide_animation.h" #include "window/window_connecting_widget.h" -#include "window/window_lock_widgets.h" #include "base/platform/base_platform_info.h" -#include "data/data_user.h" -#include "window/themes/window_theme.h" -#include "lang/lang_cloud_manager.h" -#include "main/main_session.h" #include "facades.h" #include "app.h" #include "styles/style_layers.h" #include "styles/style_intro.h" -#include "styles/style_window.h" namespace Intro { namespace { -void PrepareSupportMode() { - using Data::AutoDownload::Full; - - anim::SetDisabled(true); - Local::writeSettings(); - - Global::SetDesktopNotify(false); - Global::SetSoundNotify(false); - Auth().settings().autoDownload() = Full::FullDisabled(); - Local::writeUserSettings(); -} +using namespace ::Intro::details; } // namespace @@ -72,7 +51,9 @@ Widget::Widget(QWidget *parent, not_null account) , _next(this, nullptr, st::introNextButton) { getData()->country = Platform::SystemCountry(); - _back->entity()->setClickedCallback([this] { historyMove(Direction::Back); }); + _back->entity()->setClickedCallback([=] { + historyMove(Direction::Back); + }); _back->hide(anim::type::instant); _next->setClickedCallback([this] { getStep()->submit(); }); @@ -100,17 +81,15 @@ Widget::Widget(QWidget *parent, not_null account) if (!Core::UpdaterDisabled()) { Core::UpdateChecker checker; - checker.isLatest() | rpl::start_with_next([=] { - onCheckUpdateStatus(); - }, lifetime()); - checker.failed() | rpl::start_with_next([=] { - onCheckUpdateStatus(); - }, lifetime()); - checker.ready() | rpl::start_with_next([=] { - onCheckUpdateStatus(); - }, lifetime()); checker.start(); - onCheckUpdateStatus(); + rpl::merge( + rpl::single(rpl::empty_value()), + checker.isLatest(), + checker.failed(), + checker.ready() + ) | rpl::start_with_next([=] { + checkUpdateStatus(); + }, lifetime()); } } @@ -165,7 +144,7 @@ void Widget::createLanguageLink() { } } -void Widget::onCheckUpdateStatus() { +void Widget::checkUpdateStatus() { Expects(!Core::UpdaterDisabled()); if (Core::UpdateChecker().state() == Core::UpdateChecker::State::Ready) { @@ -201,16 +180,18 @@ void Widget::setInnerFocus() { } void Widget::historyMove(Direction direction) { - if (getStep()->animating()) return; + Expects(_stepHistory.size() > 1); - Assert(_stepHistory.size() > 1); + if (getStep()->animating()) { + return; + } auto wasStep = getStep((direction == Direction::Back) ? 0 : 1); if (direction == Direction::Back) { _stepHistory.pop_back(); wasStep->cancelled(); } else if (direction == Direction::Replace) { - _stepHistory.removeAt(_stepHistory.size() - 2); + _stepHistory.erase(_stepHistory.end() - 2); } if (_resetAccount) { @@ -223,7 +204,7 @@ void Widget::historyMove(Direction direction) { getStep()->finishInit(); getStep()->prepareShowAnimated(wasStep); if (wasStep->hasCover() != getStep()->hasCover()) { - _nextTopFrom = wasStep->contentTop() + st::introStepHeight; + _nextTopFrom = wasStep->contentTop() + st::introNextTop; _controlsTopFrom = wasStep->hasCover() ? st::introCoverHeight : 0; _coverShownAnimation.start([this] { updateControlsGeometry(); }, 0., 1., st::introCoverDuration, wasStep->hasCover() ? anim::linear : anim::easeOutCirc); } @@ -241,7 +222,7 @@ void Widget::historyMove(Direction direction) { if (_update) { _update->toggle(!stepHasCover, anim::type::normal); } - _next->setText(getStep()->nextButtonText()); + setupNextButton(); if (_resetAccount) _resetAccount->show(anim::type::normal); if (_terms) _terms->show(anim::type::normal); if (_changeLanguage) { @@ -286,7 +267,7 @@ void Widget::moveToStep(Step *step, Direction direction) { void Widget::appendStep(Step *step) { _stepHistory.push_back(step); - step->setGeometry(calculateStepRect()); + step->setGeometry(rect()); step->setGoCallback([=](Step *step, Direction direction) { if (direction == Direction::Back) { historyMove(direction); @@ -495,7 +476,8 @@ void Widget::showTerms(Fn callback) { void Widget::showControls() { getStep()->show(); _next->show(); - _next->setText(getStep()->nextButtonText()); + setupNextButton(); + _nextShownAnimation.stop(); _connecting->setForceHidden(false); auto hasCover = getStep()->hasCover(); _settings->toggle(!hasCover, anim::type::instant); @@ -513,6 +495,27 @@ void Widget::showControls() { _back->toggle(getStep()->hasBack(), anim::type::instant); } +void Widget::setupNextButton() { + _next->setText(getStep()->nextButtonText( + ) | rpl::filter([](const QString &text) { + return !text.isEmpty(); + })); + auto visible = getStep()->nextButtonText( + ) | rpl::map([](const QString &text) { + return !text.isEmpty(); + }) | rpl::distinct_until_changed(); + std::move( + visible + ) | rpl::start_with_next([=](bool visible) { + _nextShown = visible; + _nextShownAnimation.start( + [=] { updateControlsGeometry(); }, + _nextShown ? 0. : 1., + _nextShown ? 1. : 0., + st::slideDuration); + }, getStep()->lifetime()); +} + void Widget::hideControls() { getStep()->hide(); _next->hide(); @@ -534,7 +537,12 @@ void Widget::showAnimated(const QPixmap &bgAnimCache, bool back) { (_showBack ? _cacheUnder : _cacheOver) = Ui::GrabWidget(this); hideControls(); - _a_show.start([=] { animationCallback(); }, 0., 1., st::slideDuration, Window::SlideAnimation::transition()); + _a_show.start( + [=] { animationCallback(); }, + 0., + 1., + st::slideDuration, + Window::SlideAnimation::transition()); show(); } @@ -575,20 +583,9 @@ void Widget::paintEvent(QPaintEvent *e) { } } -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); -} - void Widget::resizeEvent(QResizeEvent *e) { - auto stepRect = calculateStepRect(); - for_const (auto step, _stepHistory) { - step->setGeometry(stepRect); + for (const auto step : _stepHistory) { + step->setGeometry(rect()); } updateControlsGeometry(); @@ -605,9 +602,11 @@ void Widget::updateControlsGeometry() { } _back->moveToLeft(0, controlsTop); - auto nextTopTo = getStep()->contentTop() + st::introStepHeight; + auto nextTopTo = getStep()->contentTop() + st::introNextTop; auto nextTop = anim::interpolate(_nextTopFrom, nextTopTo, shown); - _next->moveToLeft((width() - _next->width()) / 2, nextTop); + const auto shownAmount = _nextShownAnimation.value(_nextShown ? 1. : 0.); + const auto realNextTop = anim::interpolate(height(), nextTop, shownAmount); + _next->moveToLeft((width() - _next->width()) / 2, realNextTop); if (_changeLanguage) { _changeLanguage->moveToLeft((width() - _changeLanguage->width()) / 2, _next->y() + _next->height() + _changeLanguage->height()); } @@ -641,441 +640,4 @@ Widget::~Widget() { if (App::wnd()) App::wnd()->noIntro(this); } -rpl::producer Widget::Step::nextButtonText() const { - return tr::lng_intro_next(); -} - -void Widget::Step::finish(const MTPUser &user, QImage &&photo) { - if (user.type() != mtpc_user - || !user.c_user().is_self() - || !user.c_user().vid().v) { - // No idea what to do here. - // We could've reset intro and MTP, but this really should not happen. - Ui::show(Box("Internal error: bad user.is_self() after sign in.")); - return; - } - - // Save the default language if we've suggested some other and user ignored it. - const auto currentId = Lang::Current().id(); - const auto defaultId = Lang::DefaultLanguageId(); - const auto suggested = Lang::CurrentCloudManager().suggestedLanguage(); - if (currentId.isEmpty() && !suggested.isEmpty() && suggested != defaultId) { - Lang::Current().switchToId(Lang::DefaultLanguage()); - Local::writeLangPack(); - } - - const auto account = _account; - const auto weak = base::make_weak(account.get()); - account->createSession(user); - Local::writeMtpData(); - App::wnd()->setupMain(); - - // "this" is already deleted here by creating the main widget. - if (weak && account->sessionExists()) { - auto &session = account->session(); - if (!photo.isNull()) { - session.api().uploadPeerPhoto(session.user(), std::move(photo)); - } - if (session.supportMode()) { - PrepareSupportMode(); - } - } -} - -void Widget::Step::paintEvent(QPaintEvent *e) { - Painter p(this); - paintAnimated(p, e->rect()); -} - -void Widget::Step::resizeEvent(QResizeEvent *e) { - updateLabelsPosition(); -} - -void Widget::Step::updateLabelsPosition() { - Ui::SendPendingMoveResizeEvents(_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->resizeToWidth(st::introDescription.minWidth); - _description->moveToLeft(contentLeft() + st::buttonRadius, contentTop() + st::introDescriptionTop); - } - if (_error) { - if (_errorCentered) { - _error->entity()->resizeToWidth(width()); - } - Ui::SendPendingMoveResizeEvents(_error->entity()); - auto errorLeft = _errorCentered ? 0 : (contentLeft() + st::buttonRadius); - auto errorTop = contentTop() + (_errorBelowLink ? st::introErrorBelowLinkTop : st::introErrorTop); - _error->moveToLeft(errorLeft, errorTop); - } -} - -void Widget::Step::setTitleText(rpl::producer titleText) { - _titleText = std::move(titleText); -} - -void Widget::Step::setDescriptionText( - rpl::producer descriptionText) { - setDescriptionText( - std::move(descriptionText) | Ui::Text::ToWithEntities()); -} - -void Widget::Step::setDescriptionText( - rpl::producer richDescriptionText) { - _descriptionText = std::move(richDescriptionText); -} - -void Widget::Step::showFinished() { - _a_show.stop(); - _coverAnimation = CoverAnimation(); - _slideAnimation.reset(); - prepareCoverMask(); - activate(); -} - -bool Widget::Step::paintAnimated(Painter &p, QRect clip) { - if (_slideAnimation) { - _slideAnimation->paintFrame(p, (width() - st::introStepWidth) / 2, contentTop(), width()); - if (!_slideAnimation->animating()) { - showFinished(); - return false; - } - return true; - } - - auto dt = _a_show.value(1.); - if (!_a_show.animating()) { - if (hasCover()) { - paintCover(p, 0); - } - if (_coverAnimation.title) { - showFinished(); - } - if (!QRect(0, contentTop(), width(), st::introStepHeight).intersects(clip)) { - return true; - } - return false; - } - - auto progress = (hasCover() ? anim::easeOutCirc(1., dt) : anim::linear(1., dt)); - auto arrivingAlpha = progress; - auto departingAlpha = 1. - progress; - auto showCoverMethod = progress; - auto hideCoverMethod = progress; - auto coverTop = (hasCover() ? anim::interpolate(-st::introCoverHeight, 0, showCoverMethod) : anim::interpolate(0, -st::introCoverHeight, hideCoverMethod)); - - paintCover(p, coverTop); - - 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 MTPDauth_sentCode &data) { - const auto &type = data.vtype(); - 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; - } -} - -void Widget::Step::showDescription() { - _description->show(anim::type::normal); -} - -void Widget::Step::hideDescription() { - _description->hide(anim::type::normal); -} - -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()); - Assert(mask.depth() == (sizeof(uint32) << 3)); - auto maskIntsPerLineAdded = (mask.bytesPerLine() >> 2) - maskWidth; - 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(rpl::producer text) { - _errorText = std::move(text); -} - -void Widget::Step::refreshError(const QString &text) { - if (text.isEmpty()) { - if (_error) _error->hide(anim::type::normal); - } else { - if (!_error) { - _error.create( - this, - object_ptr( - this, - _errorCentered - ? st::introErrorCentered - : st::introError)); - _error->hide(anim::type::instant); - } - _error->entity()->setText(text); - updateLabelsPosition(); - _error->show(anim::type::normal); - } -} - -Widget::Step::Step( - QWidget *parent, - not_null account, - not_null data, - bool hasCover) -: RpWidget(parent) -, _account(account) -, _data(data) -, _hasCover(hasCover) -, _title(this, _hasCover ? st::introCoverTitle : st::introTitle) -, _description( - this, - object_ptr( - this, - _hasCover - ? st::introCoverDescription - : st::introDescription)) { - hide(); - subscribe(Window::Theme::Background(), [this]( - const Window::Theme::BackgroundUpdate &update) { - if (update.paletteChanged()) { - if (!_coverMask.isNull()) { - _coverMask = QPixmap(); - prepareCoverMask(); - } - } - }); - - _errorText.value( - ) | rpl::start_with_next([=](const QString &text) { - refreshError(text); - }, lifetime()); - - _titleText.value( - ) | rpl::start_with_next([=](const QString &text) { - _title->setText(text); - updateLabelsPosition(); - }, lifetime()); - - _descriptionText.value( - ) | rpl::start_with_next([=](const TextWithEntities &text) { - _description->entity()->setMarkedText(text); - updateLabelsPosition(); - }, lifetime()); -} - -void Widget::Step::prepareShowAnimated(Step *after) { - setInnerFocus(); - 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 result; -} - -QPixmap Widget::Step::prepareContentSnapshot() { - auto otherTop = _description->y() + _description->height(); - auto otherRect = myrtlrect(contentLeft(), otherTop, st::introStepWidth, height() - otherTop); - return Ui::GrabWidget(this, otherRect); -} - -QPixmap Widget::Step::prepareSlideAnimation() { - auto grabLeft = (width() - st::introStepWidth) / 2; - auto grabTop = contentTop(); - return Ui::GrabWidget( - this, - QRect(grabLeft, grabTop, st::introStepWidth, st::introStepHeight)); -} - -void Widget::Step::showAnimated(Direction direction) { - setFocus(); - show(); - 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(Fn callback) { - _goCallback = std::move(callback); -} - -void Widget::Step::setShowResetCallback(Fn callback) { - _showResetCallback = std::move(callback); -} - -void Widget::Step::setShowTermsCallback(Fn callback) { - _showTermsCallback = std::move(callback); -} - -void Widget::Step::setAcceptTermsCallback( - Fn callback)> callback) { - _acceptTermsCallback = 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(anim::type::instant); - if (!_errorText.current().isEmpty()) { - _error->show(anim::type::instant); - } -} - -void Widget::Step::cancelled() { -} - -void Widget::Step::finished() { - hide(); -} - -Widget::Step::CoverAnimation::~CoverAnimation() = default; - -Widget::Step::~Step() = default; - } // namespace Intro diff --git a/Telegram/SourceFiles/intro/introwidget.h b/Telegram/SourceFiles/intro/introwidget.h index 58d49d524..4d2f69411 100644 --- a/Telegram/SourceFiles/intro/introwidget.h +++ b/Telegram/SourceFiles/intro/introwidget.h @@ -21,8 +21,6 @@ namespace Ui { class IconButton; class RoundButton; class LinkButton; -class SlideAnimation; -class CrossFadeAnimation; class FlatLabel; template class FadeWrap; @@ -33,10 +31,51 @@ class ConnectionState; } // namespace Window namespace Intro { +namespace details { -class Widget : public Ui::RpWidget, private MTP::Sender, private base::Subscriber { - Q_OBJECT +enum class CallStatus { + Waiting, + Calling, + Called, + Disabled, +}; +struct Data { + QString country; + QString phone; + QByteArray phoneHash; + + CallStatus callStatus = CallStatus::Disabled; + int callTimeout = 0; + + int codeLength = 5; + bool codeByTelegram = false; + + Core::CloudPasswordCheckRequest pwdRequest; + bool hasRecovery = false; + QString pwdHint; + bool pwdNotEmptyPassport = false; + + Window::TermsLock termsLock; + + base::Observable updated; + +}; + +enum class Direction { + Back, + Forward, + Replace, +}; + +class Step; + +} // namespace details + +class Widget + : public Ui::RpWidget + , private MTP::Sender + , private base::Subscriber { public: Widget(QWidget *parent, not_null account); @@ -51,221 +90,22 @@ protected: void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; -signals: - void countryChanged(); - -private slots: - void onCheckUpdateStatus(); - - // Internal interface. -public: - struct Data { - QString country; - QString phone; - QByteArray phoneHash; - - enum class CallStatus { - Waiting, - Calling, - Called, - Disabled, - }; - CallStatus callStatus = CallStatus::Disabled; - int callTimeout = 0; - - int codeLength = 5; - bool codeByTelegram = false; - - Core::CloudPasswordCheckRequest pwdRequest; - bool hasRecovery = false; - QString pwdHint; - bool pwdNotEmptyPassport = false; - - Window::TermsLock termsLock; - - base::Observable updated; - - }; - - enum class Direction { - Back, - Forward, - Replace, - }; - class Step : public Ui::RpWidget, public RPCSender, protected base::Subscriber { - public: - Step( - QWidget *parent, - not_null account, - not_null data, - bool hasCover = false); - - Main::Account &account() const { - return *_account; - } - - virtual void finishInit() { - } - virtual void setInnerFocus() { - setFocus(); - } - - void setGoCallback( - Fn callback); - void setShowResetCallback(Fn callback); - void setShowTermsCallback( - Fn callback); - void setAcceptTermsCallback( - Fn callback)> 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 rpl::producer nextButtonText() const; - - int contentLeft() const; - int contentTop() const; - - void setErrorCentered(bool centered); - void setErrorBelowLink(bool below); - void showError(rpl::producer text); - void hideError() { - showError(rpl::single(QString())); - } - - ~Step(); - - protected: - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - - void setTitleText(rpl::producer titleText); - void setDescriptionText(rpl::producer descriptionText); - void setDescriptionText( - rpl::producer richDescriptionText); - bool paintAnimated(Painter &p, QRect clip); - - void fillSentCodeData(const MTPDauth_sentCode &type); - - void showDescription(); - void hideDescription(); - - not_null getData() const { - return _data; - } - void finish(const MTPUser &user, QImage &&photo = QImage()); - - void goBack() { - if (_goCallback) { - _goCallback(nullptr, Direction::Back); - } - } - - template - void goNext() { - if (_goCallback) { - _goCallback( - new StepType(parentWidget(), _account, _data), - Direction::Forward); - } - } - - template - void goReplace() { - if (_goCallback) { - _goCallback( - new StepType(parentWidget(), _account, _data), - Direction::Replace); - } - } - void showResetButton() { - if (_showResetCallback) _showResetCallback(); - } - void showTerms() { - if (_showTermsCallback) _showTermsCallback(); - } - void acceptTerms(Fn callback) { - if (_acceptTermsCallback) { - _acceptTermsCallback(callback); - } - } - - private: - struct CoverAnimation { - CoverAnimation() = default; - CoverAnimation(CoverAnimation &&other) = default; - CoverAnimation &operator=(CoverAnimation &&other) = default; - ~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); - void refreshError(const QString &text); - - CoverAnimation prepareCoverAnimation(Step *step); - QPixmap prepareContentSnapshot(); - QPixmap prepareSlideAnimation(); - void showFinished(); - - void prepareCoverMask(); - void paintCover(Painter &p, int top); - - const not_null _account; - const not_null _data; - - bool _hasCover = false; - Fn _goCallback; - Fn _showResetCallback; - Fn _showTermsCallback; - Fn callback)> _acceptTermsCallback; - - rpl::variable _titleText; - object_ptr _title; - rpl::variable _descriptionText; - object_ptr> _description; - - bool _errorCentered = false; - bool _errorBelowLink = false; - rpl::variable _errorText; - object_ptr> _error = { nullptr }; - - Ui::Animations::Simple _a_show; - CoverAnimation _coverAnimation; - std::unique_ptr _slideAnimation; - QPixmap _coverMask; - - }; - private: void setupConnectingWidget(); void refreshLang(); void animationCallback(); void createLanguageLink(); + void checkUpdateStatus(); + void setupNextButton(); void updateControlsGeometry(); - not_null getData() { + [[nodiscard]] not_null getData() { return &_data; } void fixOrder(); void showControls(); void hideControls(); - QRect calculateStepRect() const; void showResetButton(); void resetAccount(); @@ -274,13 +114,15 @@ private: void acceptTerms(Fn callback); void hideAndDestroy(object_ptr> widget); - Step *getStep(int skip = 0) { - Assert(_stepHistory.size() + skip > 0); - return _stepHistory.at(_stepHistory.size() - skip - 1); + [[nodiscard]] details::Step *getStep(int skip = 0) const { + Expects(skip >= 0); + Expects(skip < _stepHistory.size()); + + return _stepHistory[_stepHistory.size() - skip - 1]; } - void historyMove(Direction direction); - void moveToStep(Step *step, Direction direction); - void appendStep(Step *step); + void historyMove(details::Direction direction); + void moveToStep(details::Step *step, details::Direction direction); + void appendStep(details::Step *step); void getNearestDC(); void showTerms(Fn callback); @@ -291,9 +133,9 @@ private: bool _showBack = false; QPixmap _cacheUnder, _cacheOver; - QVector _stepHistory; + std::vector _stepHistory; - Data _data; + details::Data _data; Ui::Animations::Simple _coverShownAnimation; int _nextTopFrom = 0; @@ -310,6 +152,9 @@ private: std::unique_ptr _connecting; + bool _nextShown = false; + Ui::Animations::Simple _nextShownAnimation; + mtpRequestId _resetRequest = 0; }; diff --git a/Telegram/gyp/telegram/sources.txt b/Telegram/gyp/telegram/sources.txt index ca6bea78e..37b643429 100644 --- a/Telegram/gyp/telegram/sources.txt +++ b/Telegram/gyp/telegram/sources.txt @@ -446,6 +446,8 @@ <(src_loc)/intro/introstart.h <(src_loc)/intro/intro_qr.cpp <(src_loc)/intro/intro_qr.h +<(src_loc)/intro/intro_step.cpp +<(src_loc)/intro/intro_step.h <(src_loc)/lang/lang_cloud_manager.cpp <(src_loc)/lang/lang_cloud_manager.h <(src_loc)/lang/lang_file_parser.cpp diff --git a/Telegram/lib_qr b/Telegram/lib_qr index 6111aa3ce..4b0a1d5fb 160000 --- a/Telegram/lib_qr +++ b/Telegram/lib_qr @@ -1 +1 @@ -Subproject commit 6111aa3cef49d481d6450f463cfc4fe482755db7 +Subproject commit 4b0a1d5fb546af4671048aeeec9c355a67a12a01