diff --git a/.gitmodules b/.gitmodules index c0880c9cd2..56e46263bf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -52,3 +52,9 @@ [submodule "Telegram/ThirdParty/expected"] path = Telegram/ThirdParty/expected url = https://github.com/TartanLlama/expected +[submodule "Telegram/ThirdParty/QR"] + path = Telegram/ThirdParty/QR + url = https://github.com/nayuki/QR-Code-generator +[submodule "Telegram/lib_qr"] + path = Telegram/lib_qr + url = https://github.com/desktop-app/lib_qr.git diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f72fad17b8..78983bab6d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -178,6 +178,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_photo_caption" = "Caption"; "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_phone_title" = "Your Phone Number"; "lng_phone_desc" = "Please confirm your country code and\nenter your mobile phone number."; "lng_country_code" = "Country Code"; diff --git a/Telegram/SourceFiles/intro/intro.style b/Telegram/SourceFiles/intro/intro.style index fdae237d76..01852c4975 100644 --- a/Telegram/SourceFiles/intro/intro.style +++ b/Telegram/SourceFiles/intro/intro.style @@ -160,3 +160,6 @@ introBackButton: IconButton(defaultIconButton) { } } +introQrTop: 90px; +introQrPixel: 50px; // large enough +introQrMaxSize: 170px; diff --git a/Telegram/SourceFiles/intro/intro_qr.cpp b/Telegram/SourceFiles/intro/intro_qr.cpp new file mode 100644 index 0000000000..07770fcd4d --- /dev/null +++ b/Telegram/SourceFiles/intro/intro_qr.cpp @@ -0,0 +1,240 @@ +/* +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 "lang/lang_keys.h" +#include "intro/introphone.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/fade_wrap.h" +#include "main/main_account.h" +#include "boxes/confirm_box.h" +#include "core/application.h" +#include "base/unixtime.h" +#include "qr/qr_generate.h" +#include "styles/style_intro.h" + +namespace Intro { +namespace { + +[[nodiscard]] QImage TelegramLogoImage(int size) { + return Core::App().logo().scaled( + size, + size, + Qt::KeepAspectRatio, + Qt::SmoothTransformation); +} + +[[nodiscard]] QImage TelegramQrExact(const Qr::Data &data, int pixel) { + return Qr::ReplaceCenter( + Qr::Generate(data, pixel), + TelegramLogoImage(Qr::ReplaceSize(data, pixel))); +} + +[[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); + } + return TelegramQrExact(data, pixel * style::DevicePixelRatio()); +} + +[[nodiscard]] QImage TelegramQr(const QString &text, int pixel, int max) { + return TelegramQr(Qr::Encode(text), pixel, max); +} + +[[nodiscard]] not_null PrepareQrWidget( + not_null parent, + rpl::producer images) { + auto result = Ui::CreateChild(parent.get()); + auto current = result->lifetime().make_state(); + std::move( + images + ) | rpl::start_with_next([=](QImage &&image) { + result->resize(image.size() / cIntRetinaFactor()); + *current = std::move(image); + result->update(); + }, result->lifetime()); + result->paintRequest( + ) | rpl::filter([=] { + return !current->isNull(); + }) | rpl::start_with_next([=](QRect clip) { + QPainter(result).drawImage( + QRect(QPoint(), current->size() / cIntRetinaFactor()), + *current); + }, result->lifetime()); + return result; +} + +} // 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()); + setErrorCentered(true); + + account->destroyStaleAuthorizationKeys(); + account->mtpUpdates( + ) | rpl::start_with_next([=](const MTPUpdates &updates) { + checkForTokenUpdate(updates); + }, lifetime()); + + _code->widthValue( + ) | rpl::start_with_next([=] { + updateCodeGeometry(); + }, _code->lifetime()); + _code->show(); + + refreshCode(); +} + +void QrWidget::resizeEvent(QResizeEvent *e) { + Step::resizeEvent(e); + updateCodeGeometry(); +} + +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::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(); +} + +void QrWidget::refreshCode() { + if (_requestId) { + return; + } + _requestId = _api.request(MTPauth_ExportLoginToken( + MTP_int(ApiId), + MTP_string(ApiHash), + MTP_vector(0) + )).done([=](const MTPauth_LoginToken &result) { + handleTokenResult(result); + }).fail([=](const RPCError &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 RPCError &error) { + _requestId = 0; + if (base::take(_forceRefresh)) { + refreshCode(); + } else { + showError(rpl::single(error.type())); + } +} + +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)); +} + +void QrWidget::importTo(MTP::DcId dcId, const QByteArray &token) { + Expects(_requestId != 0); + + _requestId = _api.request(MTPauth_ImportLoginToken( + MTP_bytes(token) + )).done([=](const MTPauth_LoginToken &result) { + handleTokenResult(result); + }).fail([=](const RPCError &error) { + showTokenError(error); + }).toDC(dcId).send(); +} + +void QrWidget::done(const MTPauth_Authorization &authorization) { + authorization.match([&](const MTPDauth_authorization &data) { + if (data.vuser().type() != mtpc_user + || !data.vuser().c_user().is_self()) { + showError(rpl::single(Lang::Hard::ServerError())); + return; + } + const auto phone = data.vuser().c_user().vphone().value_or_empty(); + cSetLoggedPhoneNumber(phone); + finish(data.vuser()); + }, [&](const MTPDauth_authorizationSignUpRequired &data) { + _requestId = 0; + LOG(("API Error: Unexpected auth.authorizationSignUpRequired.")); + showError(rpl::single(Lang::Hard::ServerError())); + }); +} + +void QrWidget::activate() { + Step::activate(); + _code->show(); +} + +void QrWidget::finished() { + Step::finished(); + _refreshTimer.cancel(); + rpcInvalidate(); + cancelled(); +} + +void QrWidget::cancelled() { + _api.request(base::take(_requestId)).cancel(); +} + +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/intro_qr.h b/Telegram/SourceFiles/intro/intro_qr.h new file mode 100644 index 0000000000..af2d3f7fcd --- /dev/null +++ b/Telegram/SourceFiles/intro/intro_qr.h @@ -0,0 +1,64 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/countryinput.h" +#include "intro/introwidget.h" +#include "mtproto/sender.h" +#include "base/timer.h" + +namespace Ui { +class PhonePartInput; +class CountryCodeInput; +class RoundButton; +class FlatLabel; +} // namespace Ui + +namespace Intro { + +class QrWidget : public Widget::Step { +public: + QrWidget( + QWidget *parent, + not_null account, + not_null data); + + void activate() override; + void finished() override; + void cancelled() override; + void submit() override; + rpl::producer nextButtonText() const override; + + bool hasBack() const override { + return true; + } + +protected: + void resizeEvent(QResizeEvent *e) override; + +private: + void refreshCode(); + void updateCodeGeometry(); + void checkForTokenUpdate(const MTPUpdates &updates); + void checkForTokenUpdate(const MTPUpdate &update); + void handleTokenResult(const MTPauth_LoginToken &result); + void showTokenError(const RPCError &error); + void importTo(MTP::DcId dcId, const QByteArray &token); + void showToken(const QByteArray &token); + void done(const MTPauth_Authorization &authorization); + + rpl::event_stream _qrImages; + not_null _code; + base::Timer _refreshTimer; + MTP::Sender _api; + mtpRequestId _requestId = 0; + bool _forceRefresh = false; + +}; + +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/introcode.cpp b/Telegram/SourceFiles/intro/introcode.cpp index 26736e1a80..32eee158b7 100644 --- a/Telegram/SourceFiles/intro/introcode.cpp +++ b/Telegram/SourceFiles/intro/introcode.cpp @@ -236,7 +236,7 @@ void CodeWidget::codeSubmitDone(const MTPauth_Authorization &result) { result.match([&](const MTPDauth_authorization &data) { if (data.vuser().type() != mtpc_user || !data.vuser().c_user().is_self()) { - showCodeError(rpl::single(Lang::Hard::ServerError())); + showError(rpl::single(Lang::Hard::ServerError())); return; } cSetLoggedPhoneNumber(getData()->phone); diff --git a/Telegram/SourceFiles/intro/introphone.cpp b/Telegram/SourceFiles/intro/introphone.cpp index d9a3e2d5f9..ea838a170f 100644 --- a/Telegram/SourceFiles/intro/introphone.cpp +++ b/Telegram/SourceFiles/intro/introphone.cpp @@ -69,13 +69,6 @@ void PhoneWidget::resizeEvent(QResizeEvent *e) { auto phoneTop = _country->y() + _country->height() + st::introPhoneTop; _code->moveToLeft(contentLeft(), phoneTop); _phone->moveToLeft(contentLeft() + _country->width() - st::introPhone.width, phoneTop); - updateSignupGeometry(); -} - -void PhoneWidget::updateSignupGeometry() { - if (_signup) { - _signup->moveToLeft(contentLeft() + st::buttonRadius, contentTop() + st::introDescriptionTop); - } } void PhoneWidget::showPhoneError(rpl::producer text) { @@ -85,10 +78,6 @@ void PhoneWidget::showPhoneError(rpl::producer text) { void PhoneWidget::hidePhoneError() { hideError(); - if (_signup) { - _signup->hide(anim::type::instant); - showDescription(); - } } void PhoneWidget::countryChanged() { diff --git a/Telegram/SourceFiles/intro/introphone.h b/Telegram/SourceFiles/intro/introphone.h index 60e706345e..71316cdfe5 100644 --- a/Telegram/SourceFiles/intro/introphone.h +++ b/Telegram/SourceFiles/intro/introphone.h @@ -48,7 +48,6 @@ private slots: void onCheckRequest(); private: - void updateSignupGeometry(); void countryChanged(); void phoneSubmitDone(const MTPauth_SentCode &result); @@ -66,8 +65,6 @@ private: object_ptr _code; object_ptr _phone; - object_ptr> _signup = { nullptr }; - QString _sentPhone; mtpRequestId _sentRequest = 0; diff --git a/Telegram/SourceFiles/intro/introstart.cpp b/Telegram/SourceFiles/intro/introstart.cpp index 8ee0b1e5ce..cd70a996e5 100644 --- a/Telegram/SourceFiles/intro/introstart.cpp +++ b/Telegram/SourceFiles/intro/introstart.cpp @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "intro/introstart.h" #include "lang/lang_keys.h" -#include "intro/introphone.h" +#include "intro/intro_qr.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" @@ -26,7 +26,7 @@ StartWidget::StartWidget( } void StartWidget::submit() { - goNext(); + goNext(); } rpl::producer StartWidget::nextButtonText() const { diff --git a/Telegram/SourceFiles/main/main_account.cpp b/Telegram/SourceFiles/main/main_account.cpp index 5fbbd0b646..faa11e7cc9 100644 --- a/Telegram/SourceFiles/main/main_account.cpp +++ b/Telegram/SourceFiles/main/main_account.cpp @@ -120,21 +120,6 @@ void Account::createSession( Expects(_session == nullptr); Expects(_sessionValue.current() == nullptr); - _mtp->setUpdatesHandler(::rpcDone([]( - const mtpPrime *from, - const mtpPrime *end) { - if (const auto main = App::main()) { - return main->updateReceived(from, end); - } - return true; - })); - _mtp->setGlobalFailHandler(::rpcFail([=](const RPCError &error) { - if (sessionExists()) { - crl::on_main(&session(), [=] { logOut(); }); - } - return true; - })); - _session = std::make_unique(this, user, std::move(settings)); _sessionValue = _session.get(); @@ -152,7 +137,6 @@ void Account::destroySession() { return; } session().data().clear(); - _mtp->clearGlobalHandlers(); _sessionValue = nullptr; _session = nullptr; @@ -192,6 +176,14 @@ rpl::producer Account::mtpChanges() const { return _mtpValue.changes(); } +rpl::producer Account::mtpUpdates() const { + return _mtpUpdates.events(); +} + +rpl::producer<> Account::mtpNewSessionCreated() const { + return _mtpNewSessionCreated.events(); +} + void Account::setMtpMainDcId(MTP::DcId mainDcId) { Expects(!_mtp); @@ -344,6 +336,26 @@ void Account::startMtp() { _mtp->setUserPhone(cLoggedPhoneNumber()); _mtpConfig.mainDcId = _mtp->mainDcId(); + _mtp->setUpdatesHandler(::rpcDone([=]( + const mtpPrime *from, + const mtpPrime *end) { + auto newSession = MTPNewSession(); + auto updates = MTPUpdates(); + if (updates.read(from, end)) { + _mtpUpdates.fire(std::move(updates)); + } else if (newSession.read(from, end)) { + _mtpNewSessionCreated.fire({}); + } else { + return false; + } + return true; + })); + _mtp->setGlobalFailHandler(::rpcFail([=](const RPCError &error) { + if (sessionExists()) { + crl::on_main(&session(), [=] { logOut(); }); + } + return true; + })); _mtp->setStateChangedHandler([](MTP::ShiftedDcId dc, int32 state) { if (dc == MTP::maindc()) { Global::RefConnectionTypeChanged().notify(); diff --git a/Telegram/SourceFiles/main/main_account.h b/Telegram/SourceFiles/main/main_account.h index 8783e9a081..5e4fc12388 100644 --- a/Telegram/SourceFiles/main/main_account.h +++ b/Telegram/SourceFiles/main/main_account.h @@ -56,6 +56,8 @@ public: QByteArray &&selfSerialized, int32 selfStreamVersion); [[nodiscard]] Settings *getSessionSettings(); + [[nodiscard]] rpl::producer<> mtpNewSessionCreated() const; + [[nodiscard]] rpl::producer mtpUpdates() const; // Serialization. [[nodiscard]] QByteArray serializeMtpAuthorization() const; @@ -86,6 +88,8 @@ private: std::unique_ptr _mtp; rpl::variable _mtpValue; std::unique_ptr _mtpForKeysDestroy; + rpl::event_stream _mtpUpdates; + rpl::event_stream<> _mtpNewSessionCreated; rpl::event_stream<> _configUpdates; std::unique_ptr _session; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index ee09ab726e..58e007dbbb 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -93,6 +93,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "export/view/export_view_top_bar.h" #include "export/view/export_view_panel_controller.h" #include "main/main_session.h" +#include "main/main_account.h" #include "support/support_helper.h" #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" @@ -430,6 +431,16 @@ MainWidget::MainWidget( [this] { updateControlsGeometry(); }, lifetime()); + session().account().mtpUpdates( + ) | rpl::start_with_next([=](const MTPUpdates &updates) { + mtpUpdateReceived(updates); + }, lifetime()); + + session().account().mtpNewSessionCreated( + ) | rpl::start_with_next([=] { + mtpNewSessionCreated(); + }, lifetime()); + // MSVC BUG + REGRESSION rpl::mappers::tuple :( using namespace rpl::mappers; _controller->activeChatValue( @@ -3673,35 +3684,22 @@ void MainWidget::checkIdleFinish() { } } -bool MainWidget::updateReceived(const mtpPrime *from, const mtpPrime *end) { - if (end <= from) { - return false; - } - +void MainWidget::mtpNewSessionCreated() { session().checkAutoLock(); + updSeq = 0; + MTP_LOG(0, ("getDifference { after new_session_created }%1" + ).arg(cTestMode() ? " TESTMODE" : "")); + getDifference(); +} - if (mtpTypeId(*from) == mtpc_new_session_created) { - MTPNewSession newSession; - if (!newSession.read(from, end)) { - return false; - } - updSeq = 0; - MTP_LOG(0, ("getDifference { after new_session_created }%1").arg(cTestMode() ? " TESTMODE" : "")); - getDifference(); - return true; - } - MTPUpdates updates; - if (!updates.read(from, end)) { - return false; - } - +void MainWidget::mtpUpdateReceived(const MTPUpdates &updates) { + session().checkAutoLock(); _lastUpdateTime = crl::now(); _noUpdatesTimer.callOnce(kNoUpdatesTimeout); if (!requestingDifference() || HasForceLogoutNotification(updates)) { feedUpdates(updates); } - return true; } void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index d07cd1ddc3..6a41d156e4 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -133,7 +133,6 @@ public: void incrementSticker(DocumentData *sticker); void activate(); - [[nodiscard]] bool updateReceived(const mtpPrime *from, const mtpPrime *end); void refreshDialog(Dialogs::Key key); void removeDialog(Dialogs::Key key); @@ -394,6 +393,8 @@ private: bool failChannelDifference(ChannelData *channel, const RPCError &err); void failDifferenceStartTimerFor(ChannelData *channel); + void mtpUpdateReceived(const MTPUpdates &updates); + void mtpNewSessionCreated(); void feedUpdateVector( const MTPVector &updates, bool skipMessageIds = false); diff --git a/Telegram/SourceFiles/storage/file_download.cpp b/Telegram/SourceFiles/storage/file_download.cpp index ffac1874ef..ae046b0d48 100644 --- a/Telegram/SourceFiles/storage/file_download.cpp +++ b/Telegram/SourceFiles/storage/file_download.cpp @@ -60,7 +60,7 @@ void Downloader::requestedAmountIncrement(MTP::DcId dcId, int index, int amount) using namespace rpl::mappers; auto it = _requestedBytesAmount.find(dcId); - if (it == _requestedBytesAmount.cend()) { + if (it == _requestedBytesAmount.end()) { it = _requestedBytesAmount.emplace(dcId, RequestedInDc { { 0 } }).first; } it->second[index] += amount; diff --git a/Telegram/SourceFiles/storage/file_download.h b/Telegram/SourceFiles/storage/file_download.h index c15d8745d8..32f20677b4 100644 --- a/Telegram/SourceFiles/storage/file_download.h +++ b/Telegram/SourceFiles/storage/file_download.h @@ -79,7 +79,7 @@ private: int _priority = 1; using RequestedInDc = std::array; - std::map _requestedBytesAmount; + base::flat_map _requestedBytesAmount; base::flat_map _killDownloadSessionTimes; base::Timer _killDownloadSessionsTimer; diff --git a/Telegram/ThirdParty/QR b/Telegram/ThirdParty/QR new file mode 160000 index 0000000000..67c62461d3 --- /dev/null +++ b/Telegram/ThirdParty/QR @@ -0,0 +1 @@ +Subproject commit 67c62461d380352500fc39557fd9f046b7fe1d18 diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 7f080fba81..ac05463a9d 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -75,6 +75,7 @@ '<(submodules_loc)/codegen/codegen.gyp:codegen_style', '<(submodules_loc)/lib_base/lib_base.gyp:lib_base', '<(submodules_loc)/lib_ui/lib_ui.gyp:lib_ui', + '<(submodules_loc)/lib_qr/lib_qr.gyp:lib_qr', '<(third_party_loc)/libtgvoip/libtgvoip.gyp:libtgvoip', '<(submodules_loc)/lib_lottie/lib_lottie.gyp:lib_lottie', 'tests/tests.gyp:tests', diff --git a/Telegram/gyp/telegram/sources.txt b/Telegram/gyp/telegram/sources.txt index 0bd9449702..ca6bea78ee 100644 --- a/Telegram/gyp/telegram/sources.txt +++ b/Telegram/gyp/telegram/sources.txt @@ -444,6 +444,8 @@ <(src_loc)/intro/introsignup.h <(src_loc)/intro/introstart.cpp <(src_loc)/intro/introstart.h +<(src_loc)/intro/intro_qr.cpp +<(src_loc)/intro/intro_qr.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 new file mode 160000 index 0000000000..6111aa3cef --- /dev/null +++ b/Telegram/lib_qr @@ -0,0 +1 @@ +Subproject commit 6111aa3cef49d481d6450f463cfc4fe482755db7