From d5718f96b8c7011dc63910832eaecc838d35b1d0 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 22 Nov 2019 12:40:52 +0300
Subject: [PATCH] First working auth by QR code.

---
 .gitmodules                                   |   6 +
 Telegram/Resources/langs/lang.strings         |   4 +
 Telegram/SourceFiles/intro/intro.style        |   3 +
 Telegram/SourceFiles/intro/intro_qr.cpp       | 240 ++++++++++++++++++
 Telegram/SourceFiles/intro/intro_qr.h         |  64 +++++
 Telegram/SourceFiles/intro/introcode.cpp      |   2 +-
 Telegram/SourceFiles/intro/introphone.cpp     |  11 -
 Telegram/SourceFiles/intro/introphone.h       |   3 -
 Telegram/SourceFiles/intro/introstart.cpp     |   4 +-
 Telegram/SourceFiles/main/main_account.cpp    |  44 ++--
 Telegram/SourceFiles/main/main_account.h      |   4 +
 Telegram/SourceFiles/mainwidget.cpp           |  40 ++-
 Telegram/SourceFiles/mainwidget.h             |   3 +-
 .../SourceFiles/storage/file_download.cpp     |   2 +-
 Telegram/SourceFiles/storage/file_download.h  |   2 +-
 Telegram/ThirdParty/QR                        |   1 +
 Telegram/gyp/Telegram.gyp                     |   1 +
 Telegram/gyp/telegram/sources.txt             |   2 +
 Telegram/lib_qr                               |   1 +
 19 files changed, 380 insertions(+), 57 deletions(-)
 create mode 100644 Telegram/SourceFiles/intro/intro_qr.cpp
 create mode 100644 Telegram/SourceFiles/intro/intro_qr.h
 create mode 160000 Telegram/ThirdParty/QR
 create mode 160000 Telegram/lib_qr

diff --git a/.gitmodules b/.gitmodules
index c0880c9cd..56e46263b 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 f72fad17b..78983bab6 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 fdae237d7..01852c497 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 000000000..07770fcd4
--- /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<Ui::RpWidget*> PrepareQrWidget(
+		not_null<QWidget*> parent,
+		rpl::producer<QImage> images) {
+	auto result = Ui::CreateChild<Ui::RpWidget>(parent.get());
+	auto current = result->lifetime().make_state<QImage>();
+	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<Main::Account*> account,
+	not_null<Widget::Data*> 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<PhoneWidget>();
+}
+
+rpl::producer<QString> 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<MTPint>(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 000000000..af2d3f7fc
--- /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<Main::Account*> account,
+		not_null<Widget::Data*> data);
+
+	void activate() override;
+	void finished() override;
+	void cancelled() override;
+	void submit() override;
+	rpl::producer<QString> 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<QImage> _qrImages;
+	not_null<Ui::RpWidget*> _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 26736e1a8..32eee158b 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 d9a3e2d5f..ea838a170 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<QString> text) {
@@ -85,10 +78,6 @@ void PhoneWidget::showPhoneError(rpl::producer<QString> 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 60e706345..71316cdfe 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<Ui::CountryCodeInput> _code;
 	object_ptr<Ui::PhonePartInput> _phone;
 
-	object_ptr<Ui::FadeWrap<Ui::FlatLabel>> _signup = { nullptr };
-
 	QString _sentPhone;
 	mtpRequestId _sentRequest = 0;
 
diff --git a/Telegram/SourceFiles/intro/introstart.cpp b/Telegram/SourceFiles/intro/introstart.cpp
index 8ee0b1e5c..cd70a996e 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<PhoneWidget>();
+	goNext<QrWidget>();
 }
 
 rpl::producer<QString> StartWidget::nextButtonText() const {
diff --git a/Telegram/SourceFiles/main/main_account.cpp b/Telegram/SourceFiles/main/main_account.cpp
index 5fbbd0b64..faa11e7cc 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<Session>(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<MTP::Instance*> Account::mtpChanges() const {
 	return _mtpValue.changes();
 }
 
+rpl::producer<MTPUpdates> 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 8783e9a08..5e4fc1238 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> mtpUpdates() const;
 
 	// Serialization.
 	[[nodiscard]] QByteArray serializeMtpAuthorization() const;
@@ -86,6 +88,8 @@ private:
 	std::unique_ptr<MTP::Instance> _mtp;
 	rpl::variable<MTP::Instance*> _mtpValue;
 	std::unique_ptr<MTP::Instance> _mtpForKeysDestroy;
+	rpl::event_stream<MTPUpdates> _mtpUpdates;
+	rpl::event_stream<> _mtpNewSessionCreated;
 	rpl::event_stream<> _configUpdates;
 
 	std::unique_ptr<Session> _session;
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index ee09ab726..58e007dbb 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 d07cd1ddc..6a41d156e 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<MTPUpdate> &updates,
 		bool skipMessageIds = false);
diff --git a/Telegram/SourceFiles/storage/file_download.cpp b/Telegram/SourceFiles/storage/file_download.cpp
index ffac1874e..ae046b0d4 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 c15d8745d..32f20677b 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<int64, MTP::kDownloadSessionsCount>;
-	std::map<MTP::DcId, RequestedInDc> _requestedBytesAmount;
+	base::flat_map<MTP::DcId, RequestedInDc> _requestedBytesAmount;
 
 	base::flat_map<MTP::DcId, crl::time> _killDownloadSessionTimes;
 	base::Timer _killDownloadSessionsTimer;
diff --git a/Telegram/ThirdParty/QR b/Telegram/ThirdParty/QR
new file mode 160000
index 000000000..67c62461d
--- /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 7f080fba8..ac05463a9 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 0bd944970..ca6bea78e 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 000000000..6111aa3ce
--- /dev/null
+++ b/Telegram/lib_qr
@@ -0,0 +1 @@
+Subproject commit 6111aa3cef49d481d6450f463cfc4fe482755db7