| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- /*
- 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_qr.h"
- #include "boxes/abstract_box.h"
- #include "intro/intro_phone.h"
- #include "intro/intro_widget.h"
- #include "intro/intro_password_check.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/effects/radial_animation.h"
- #include "ui/text/text_utilities.h"
- #include "ui/image/image_prepare.h"
- #include "ui/painter.h"
- #include "main/main_account.h"
- #include "ui/boxes/confirm_box.h"
- #include "core/application.h"
- #include "core/core_cloud_password.h"
- #include "core/update_checker.h"
- #include "base/unixtime.h"
- #include "qr/qr_generate.h"
- #include "styles/style_intro.h"
- namespace Intro {
- namespace details {
- namespace {
- [[nodiscard]] QImage TelegramQrExact(const Qr::Data &data, int pixel) {
- return Qr::Generate(data, pixel, Qt::black);
- }
- [[nodiscard]] QImage TelegramQr(const Qr::Data &data, int pixel, int max = 0) {
- Expects(data.size > 0);
- if (max > 0 && data.size * pixel > max) {
- pixel = std::max(max / data.size, 1);
- }
- const auto qr = TelegramQrExact(data, pixel * style::DevicePixelRatio());
- auto result = QImage(qr.size(), QImage::Format_ARGB32_Premultiplied);
- result.fill(Qt::white);
- {
- auto p = QPainter(&result);
- p.drawImage(QRect(QPoint(), qr.size()), qr);
- }
- return result;
- }
- [[nodiscard]] QColor QrActiveColor() {
- return QColor(0x40, 0xA7, 0xE3); // Default windowBgActive.
- }
- [[nodiscard]] not_null<Ui::RpWidget*> PrepareQrWidget(
- not_null<QWidget*> parent,
- rpl::producer<QByteArray> codes) {
- struct State {
- explicit State(Fn<void()> callback)
- : waiting(callback, st::defaultInfiniteRadialAnimation) {
- }
- QImage previous;
- QImage qr;
- QImage center;
- Ui::Animations::Simple shown;
- Ui::InfiniteRadialAnimation waiting;
- };
- auto qrs = std::move(
- codes
- ) | rpl::map([](const QByteArray &code) {
- return Qr::Encode(code, Qr::Redundancy::Quartile);
- });
- auto palettes = rpl::single(rpl::empty) | rpl::then(
- style::PaletteChanged()
- );
- auto result = Ui::CreateChild<Ui::RpWidget>(parent.get());
- const auto state = result->lifetime().make_state<State>(
- [=] { result->update(); });
- state->waiting.start();
- const auto size = st::introQrMaxSize + 2 * st::introQrBackgroundSkip;
- result->resize(size, size);
- rpl::combine(
- std::move(qrs),
- rpl::duplicate(palettes)
- ) | rpl::map([](const Qr::Data &code, const auto &) {
- return TelegramQr(code, st::introQrPixel, st::introQrMaxSize);
- }) | rpl::start_with_next([=](QImage &&image) {
- state->previous = std::move(state->qr);
- state->qr = std::move(image);
- state->waiting.stop();
- state->shown.stop();
- state->shown.start(
- [=] { result->update(); },
- 0.,
- 1.,
- st::fadeWrapDuration);
- }, result->lifetime());
- std::move(
- palettes
- ) | rpl::map([] {
- return TelegramLogoImage();
- }) | rpl::start_with_next([=](QImage &&image) {
- state->center = std::move(image);
- }, result->lifetime());
- result->paintRequest(
- ) | rpl::start_with_next([=](QRect clip) {
- auto p = QPainter(result);
- const auto has = !state->qr.isNull();
- const auto shown = has ? state->shown.value(1.) : 0.;
- const auto usualSize = 41;
- const auto pixel = std::clamp(
- st::introQrMaxSize / usualSize,
- 1,
- st::introQrPixel);
- const auto size = has
- ? (state->qr.size() / style::DevicePixelRatio())
- : QSize(usualSize * pixel, usualSize * pixel);
- const auto qr = QRect(
- (result->width() - size.width()) / 2,
- (result->height() - size.height()) / 2,
- size.width(),
- size.height());
- const auto radius = st::introQrBackgroundRadius;
- const auto skip = st::introQrBackgroundSkip;
- auto hq = PainterHighQualityEnabler(p);
- p.setPen(Qt::NoPen);
- p.setBrush(Qt::white);
- p.drawRoundedRect(
- qr.marginsAdded({ skip, skip, skip, skip }),
- radius,
- radius);
- if (!state->qr.isNull()) {
- if (shown == 1.) {
- state->previous = QImage();
- } else if (!state->previous.isNull()) {
- p.drawImage(qr, state->previous);
- }
- p.setOpacity(shown);
- p.drawImage(qr, state->qr);
- p.setOpacity(1.);
- }
- const auto rect = QRect(
- (result->width() - st::introQrCenterSize) / 2,
- (result->height() - st::introQrCenterSize) / 2,
- st::introQrCenterSize,
- st::introQrCenterSize);
- p.drawImage(rect, state->center);
- if (!anim::Disabled() && state->waiting.animating()) {
- auto hq = PainterHighQualityEnabler(p);
- const auto line = st::radialLine;
- const auto radial = state->waiting.computeState();
- auto pen = QPen(QrActiveColor());
- pen.setWidth(line);
- pen.setCapStyle(Qt::RoundCap);
- p.setOpacity(radial.shown * (1. - shown));
- p.setPen(pen);
- p.drawArc(
- rect.marginsAdded({ line, line, line, line }),
- radial.arcFrom,
- radial.arcLength);
- p.setOpacity(1.);
- }
- }, result->lifetime());
- return result;
- }
- } // namespace
- QrWidget::QrWidget(
- QWidget *parent,
- not_null<Main::Account*> account,
- not_null<Data*> data)
- : Step(parent, account, data)
- , _refreshTimer([=] { refreshCode(); }) {
- setTitleText(rpl::single(QString()));
- setDescriptionText(rpl::single(QString()));
- setErrorCentered(true);
- cancelNearestDcRequest();
- account->mtpUpdates(
- ) | rpl::start_with_next([=](const MTPUpdates &updates) {
- checkForTokenUpdate(updates);
- }, lifetime());
- setupControls();
- account->mtp().mainDcIdValue(
- ) | rpl::start_with_next([=] {
- api().request(base::take(_requestId)).cancel();
- refreshCode();
- }, lifetime());
- }
- int QrWidget::errorTop() const {
- return contentTop() + st::introQrErrorTop;
- }
- void QrWidget::checkForTokenUpdate(const MTPUpdates &updates) {
- updates.match([&](const MTPDupdateShort &data) {
- checkForTokenUpdate(data.vupdate());
- }, [&](const MTPDupdates &data) {
- for (const auto &update : data.vupdates().v) {
- checkForTokenUpdate(update);
- }
- }, [&](const MTPDupdatesCombined &data) {
- for (const auto &update : data.vupdates().v) {
- checkForTokenUpdate(update);
- }
- }, [](const auto &) {});
- }
- void QrWidget::checkForTokenUpdate(const MTPUpdate &update) {
- update.match([&](const MTPDupdateLoginToken &data) {
- if (_requestId) {
- _forceRefresh = true;
- } else {
- _refreshTimer.cancel();
- refreshCode();
- }
- }, [](const auto &) {});
- }
- void QrWidget::submit() {
- goReplace<PhoneWidget>(Animate::Forward);
- }
- rpl::producer<QString> QrWidget::nextButtonText() const {
- 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<Ui::FlatLabel>(
- this,
- tr::lng_intro_qr_title(),
- st::introQrTitle);
- rpl::combine(
- sizeValue(),
- title->widthValue()
- ) | rpl::start_with_next([=](QSize size, int titleWidth) {
- title->resizeToWidth(st::introQrTitleWidth);
- const auto oneLine = st::introQrTitle.style.font->height;
- const auto topDelta = (title->height() - oneLine);
- title->moveToLeft(
- (size.width() - title->width()) / 2,
- contentTop() + st::introQrTitleTop - topDelta);
- }, title->lifetime());
- const auto steps = Ui::CreateChild<Ui::VerticalLayout>(this);
- const auto texts = {
- tr::lng_intro_qr_step1,
- tr::lng_intro_qr_step2,
- tr::lng_intro_qr_step3,
- };
- auto index = 0;
- for (const auto &text : texts) {
- const auto label = steps->add(
- object_ptr<Ui::FlatLabel>(
- steps,
- text(Ui::Text::RichLangValue),
- st::introQrStep),
- st::introQrStepMargins);
- const auto number = Ui::CreateChild<Ui::FlatLabel>(
- steps,
- rpl::single(Ui::Text::Semibold(QString::number(++index) + ".")),
- st::defaultFlatLabel);
- rpl::combine(
- number->widthValue(),
- label->positionValue()
- ) | rpl::start_with_next([=](int width, QPoint position) {
- number->moveToLeft(
- position.x() - width - st::normalFont->spacew,
- position.y());
- }, number->lifetime());
- }
- 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<Ui::LinkButton>(
- 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([=] { submit(); });
- }
- void QrWidget::refreshCode() {
- if (_requestId) {
- return;
- }
- _requestId = api().request(MTPauth_ExportLoginToken(
- MTP_int(ApiId),
- MTP_string(ApiHash),
- MTP_vector<MTPlong>(0)
- )).done([=](const MTPauth_LoginToken &result) {
- handleTokenResult(result);
- }).fail([=](const MTP::Error &error) {
- showTokenError(error);
- }).send();
- }
- void QrWidget::handleTokenResult(const MTPauth_LoginToken &result) {
- result.match([&](const MTPDauth_loginToken &data) {
- _requestId = 0;
- showToken(data.vtoken().v);
- if (base::take(_forceRefresh)) {
- refreshCode();
- } else {
- const auto left = data.vexpires().v - base::unixtime::now();
- _refreshTimer.callOnce(std::max(left, 1) * crl::time(1000));
- }
- }, [&](const MTPDauth_loginTokenMigrateTo &data) {
- importTo(data.vdc_id().v, data.vtoken().v);
- }, [&](const MTPDauth_loginTokenSuccess &data) {
- done(data.vauthorization());
- });
- }
- void QrWidget::showTokenError(const MTP::Error &error) {
- _requestId = 0;
- if (error.type() == u"SESSION_PASSWORD_NEEDED"_q) {
- sendCheckPasswordRequest();
- } else if (base::take(_forceRefresh)) {
- refreshCode();
- } else {
- showError(rpl::single(error.type()));
- }
- }
- void QrWidget::showToken(const QByteArray &token) {
- const auto encoded = token.toBase64(QByteArray::Base64UrlEncoding);
- _qrCodes.fire_copy("tg://login?token=" + encoded);
- }
- void QrWidget::importTo(MTP::DcId dcId, const QByteArray &token) {
- Expects(_requestId != 0);
- api().instance().setMainDcId(dcId);
- _requestId = api().request(MTPauth_ImportLoginToken(
- MTP_bytes(token)
- )).done([=](const MTPauth_LoginToken &result) {
- handleTokenResult(result);
- }).fail([=](const MTP::Error &error) {
- showTokenError(error);
- }).toDC(dcId).send();
- }
- void QrWidget::done(const MTPauth_Authorization &authorization) {
- finish(authorization);
- }
- void QrWidget::sendCheckPasswordRequest() {
- _requestId = api().request(MTPaccount_GetPassword(
- )).done([=](const MTPaccount_Password &result) {
- result.match([&](const MTPDaccount_password &data) {
- getData()->pwdState = Core::ParseCloudPasswordState(data);
- if (!data.vcurrent_algo() || !data.vsrp_id() || !data.vsrp_B()) {
- LOG(("API Error: No current password received on login."));
- goReplace<QrWidget>(Animate::Forward);
- return;
- } else if (!getData()->pwdState.hasPassword) {
- const auto callback = [=](Fn<void()> &&close) {
- Core::UpdateApplication();
- close();
- };
- Ui::show(Ui::MakeConfirmBox({
- .text = tr::lng_passport_app_out_of_date(),
- .confirmed = callback,
- .confirmText = tr::lng_menu_update(),
- }));
- return;
- }
- goReplace<PasswordCheckWidget>(Animate::Forward);
- });
- }).fail([=](const MTP::Error &error) {
- showTokenError(error);
- }).send();
- }
- void QrWidget::activate() {
- Step::activate();
- showChildren();
- }
- void QrWidget::finished() {
- Step::finished();
- _refreshTimer.cancel();
- apiClear();
- cancelled();
- }
- void QrWidget::cancelled() {
- api().request(base::take(_requestId)).cancel();
- }
- QImage TelegramLogoImage() {
- const auto size = QSize(st::introQrCenterSize, st::introQrCenterSize);
- auto result = QImage(
- size * style::DevicePixelRatio(),
- QImage::Format_ARGB32_Premultiplied);
- result.fill(Qt::transparent);
- result.setDevicePixelRatio(style::DevicePixelRatio());
- {
- auto p = QPainter(&result);
- auto hq = PainterHighQualityEnabler(p);
- p.setBrush(QrActiveColor());
- p.setPen(Qt::NoPen);
- p.drawEllipse(QRect(QPoint(), size));
- st::introQrPlane.paintInCenter(p, QRect(QPoint(), size));
- }
- return result;
- }
- } // namespace details
- } // namespace Intro
|