1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4 
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "intro/intro_phone.h"
9 
10 #include "lang/lang_keys.h"
11 #include "intro/intro_code.h"
12 #include "intro/intro_qr.h"
13 #include "styles/style_intro.h"
14 #include "ui/widgets/buttons.h"
15 #include "ui/widgets/labels.h"
16 #include "ui/wrap/fade_wrap.h"
17 #include "ui/special_fields.h"
18 #include "main/main_account.h"
19 #include "main/main_domain.h"
20 #include "main/main_app_config.h"
21 #include "main/main_session.h"
22 #include "data/data_user.h"
23 #include "ui/boxes/confirm_box.h"
24 #include "boxes/phone_banned_box.h"
25 #include "core/application.h"
26 
27 namespace Intro {
28 namespace details {
29 namespace {
30 
AllowPhoneAttempt(const QString & phone)31 bool AllowPhoneAttempt(const QString &phone) {
32 	const auto digits = ranges::count_if(
33 		phone,
34 		[](QChar ch) { return ch.isNumber(); });
35 	return (digits > 1);
36 }
37 
38 } // namespace
39 
PhoneWidget(QWidget * parent,not_null<Main::Account * > account,not_null<Data * > data)40 PhoneWidget::PhoneWidget(
41 	QWidget *parent,
42 	not_null<Main::Account*> account,
43 	not_null<Data*> data)
44 : Step(parent, account, data)
45 , _country(this, st::introCountry)
46 , _code(this, st::introCountryCode)
47 , _phone(this, st::introPhone)
48 , _checkRequestTimer([=] { checkRequest(); }) {
49 	_phone->frontBackspaceEvent(
__anone90cf9ae0402(not_null<QKeyEvent*> e) 50 	) | rpl::start_with_next([=](not_null<QKeyEvent*> e) {
51 		_code->startErasing(e);
52 	}, _code->lifetime());
53 
54 	_country->codeChanged(
__anone90cf9ae0502(const QString &code) 55 	) | rpl::start_with_next([=](const QString &code) {
56 		_code->codeSelected(code);
57 		_phone->chooseCode(code);
58 	}, _country->lifetime());
59 	_code->codeChanged(
__anone90cf9ae0602(const QString &code) 60 	) | rpl::start_with_next([=](const QString &code) {
61 		_country->onChooseCode(code);
62 		_phone->chooseCode(code);
63 	}, _code->lifetime());
64 	_code->addedToNumber(
__anone90cf9ae0702(const QString &added) 65 	) | rpl::start_with_next([=](const QString &added) {
66 		_phone->addedToNumber(added);
67 	}, _phone->lifetime());
__anone90cf9ae0802null68 	connect(_phone, &Ui::PhonePartInput::changed, [=] { phoneChanged(); });
__anone90cf9ae0902null69 	connect(_code, &Ui::CountryCodeInput::changed, [=] { phoneChanged(); });
70 
71 	setTitleText(tr::lng_phone_title());
72 	setDescriptionText(tr::lng_phone_desc());
73 	getData()->updated.events(
__anone90cf9ae0a02null74 	) | rpl::start_with_next([=] {
75 		countryChanged();
76 	}, lifetime());
77 	setErrorCentered(true);
78 	setupQrLogin();
79 
80 	if (!_country->chooseCountry(getData()->country)) {
81 		_country->chooseCountry(qsl("US"));
82 	}
83 	_changed = false;
84 }
85 
setupQrLogin()86 void PhoneWidget::setupQrLogin() {
87 	const auto qrLogin = Ui::CreateChild<Ui::LinkButton>(
88 		this,
89 		tr::lng_phone_to_qr(tr::now));
90 	qrLogin->show();
91 
92 	DEBUG_LOG(("PhoneWidget.qrLogin link created and shown."));
93 
94 	rpl::combine(
95 		sizeValue(),
96 		qrLogin->widthValue()
97 	) | rpl::start_with_next([=](QSize size, int qrLoginWidth) {
98 		qrLogin->moveToLeft(
99 			(size.width() - qrLoginWidth) / 2,
100 			contentTop() + st::introQrLoginLinkTop);
101 	}, qrLogin->lifetime());
102 
103 	qrLogin->setClickedCallback([=] {
104 		goReplace<QrWidget>(Animate::Forward);
105 	});
106 }
107 
resizeEvent(QResizeEvent * e)108 void PhoneWidget::resizeEvent(QResizeEvent *e) {
109 	Step::resizeEvent(e);
110 	_country->moveToLeft(contentLeft(), contentTop() + st::introStepFieldTop);
111 	auto phoneTop = _country->y() + _country->height() + st::introPhoneTop;
112 	_code->moveToLeft(contentLeft(), phoneTop);
113 	_phone->moveToLeft(contentLeft() + _country->width() - st::introPhone.width, phoneTop);
114 }
115 
showPhoneError(rpl::producer<QString> text)116 void PhoneWidget::showPhoneError(rpl::producer<QString> text) {
117 	_phone->showError();
118 	showError(std::move(text));
119 }
120 
hidePhoneError()121 void PhoneWidget::hidePhoneError() {
122 	hideError();
123 }
124 
countryChanged()125 void PhoneWidget::countryChanged() {
126 	if (!_changed) {
127 		selectCountry(getData()->country);
128 	}
129 }
130 
phoneChanged()131 void PhoneWidget::phoneChanged() {
132 	_changed = true;
133 	hidePhoneError();
134 }
135 
submit()136 void PhoneWidget::submit() {
137 	if (_sentRequest || isHidden()) {
138 		return;
139 	}
140 
141 	const auto phone = fullNumber();
142 	if (!AllowPhoneAttempt(phone)) {
143 		showPhoneError(tr::lng_bad_phone());
144 		_phone->setFocus();
145 		return;
146 	}
147 
148 	cancelNearestDcRequest();
149 
150 	// Check if such account is authorized already.
151 	const auto digitsOnly = [](QString value) {
152 		return value.replace(QRegularExpression("[^0-9]"), QString());
153 	};
154 	const auto phoneDigits = digitsOnly(phone);
155 	for (const auto &[index, existing] : Core::App().domain().accounts()) {
156 		const auto raw = existing.get();
157 		if (const auto session = raw->maybeSession()) {
158 			if (raw->mtp().environment() == account().mtp().environment()
159 				&& digitsOnly(session->user()->phone()) == phoneDigits) {
160 				crl::on_main(raw, [=] {
161 					Core::App().domain().activate(raw);
162 				});
163 				return;
164 			}
165 		}
166 	}
167 
168 	hidePhoneError();
169 
170 	_checkRequestTimer.callEach(1000);
171 
172 	_sentPhone = phone;
173 	api().instance().setUserPhone(_sentPhone);
174 	_sentRequest = api().request(MTPauth_SendCode(
175 		MTP_string(_sentPhone),
176 		MTP_int(ApiId),
177 		MTP_string(ApiHash),
178 		MTP_codeSettings(MTP_flags(0))
179 	)).done([=](const MTPauth_SentCode &result) {
180 		phoneSubmitDone(result);
181 	}).fail([=](const MTP::Error &error) {
182 		phoneSubmitFail(error);
183 	}).handleFloodErrors().send();
184 }
185 
stopCheck()186 void PhoneWidget::stopCheck() {
187 	_checkRequestTimer.cancel();
188 }
189 
checkRequest()190 void PhoneWidget::checkRequest() {
191 	auto status = api().instance().state(_sentRequest);
192 	if (status < 0) {
193 		auto leftms = -status;
194 		if (leftms >= 1000) {
195 			api().request(base::take(_sentRequest)).cancel();
196 		}
197 	}
198 	if (!_sentRequest && status == MTP::RequestSent) {
199 		stopCheck();
200 	}
201 }
202 
phoneSubmitDone(const MTPauth_SentCode & result)203 void PhoneWidget::phoneSubmitDone(const MTPauth_SentCode &result) {
204 	stopCheck();
205 	_sentRequest = 0;
206 
207 	if (result.type() != mtpc_auth_sentCode) {
208 		showPhoneError(rpl::single(Lang::Hard::ServerError()));
209 		return;
210 	}
211 
212 	const auto &d = result.c_auth_sentCode();
213 	fillSentCodeData(d);
214 	getData()->phone = _sentPhone;
215 	getData()->phoneHash = qba(d.vphone_code_hash());
216 	const auto next = d.vnext_type();
217 	if (next && next->type() == mtpc_auth_codeTypeCall) {
218 		getData()->callStatus = CallStatus::Waiting;
219 		getData()->callTimeout = d.vtimeout().value_or(60);
220 	} else {
221 		getData()->callStatus = CallStatus::Disabled;
222 		getData()->callTimeout = 0;
223 	}
224 	goNext<CodeWidget>();
225 }
226 
phoneSubmitFail(const MTP::Error & error)227 void PhoneWidget::phoneSubmitFail(const MTP::Error &error) {
228 	if (MTP::IsFloodError(error)) {
229 		stopCheck();
230 		_sentRequest = 0;
231 		showPhoneError(tr::lng_flood_error());
232 		return;
233 	}
234 
235 	stopCheck();
236 	_sentRequest = 0;
237 	auto &err = error.type();
238 	if (err == qstr("PHONE_NUMBER_FLOOD")) {
239 		Ui::show(Box<Ui::InformBox>(tr::lng_error_phone_flood(tr::now)));
240 	} else if (err == qstr("PHONE_NUMBER_INVALID")) { // show error
241 		showPhoneError(tr::lng_bad_phone());
242 	} else if (err == qstr("PHONE_NUMBER_BANNED")) {
243 		Ui::ShowPhoneBannedError(getData()->controller, _sentPhone);
244 	} else if (Logs::DebugEnabled()) { // internal server error
245 		showPhoneError(rpl::single(err + ": " + error.description()));
246 	} else {
247 		showPhoneError(rpl::single(Lang::Hard::ServerError()));
248 	}
249 }
250 
fullNumber() const251 QString PhoneWidget::fullNumber() const {
252 	return _code->getLastText() + _phone->getLastText();
253 }
254 
selectCountry(const QString & country)255 void PhoneWidget::selectCountry(const QString &country) {
256 	_country->chooseCountry(country);
257 }
258 
setInnerFocus()259 void PhoneWidget::setInnerFocus() {
260 	_phone->setFocusFast();
261 }
262 
activate()263 void PhoneWidget::activate() {
264 	Step::activate();
265 	showChildren();
266 	setInnerFocus();
267 }
268 
finished()269 void PhoneWidget::finished() {
270 	Step::finished();
271 	_checkRequestTimer.cancel();
272 	apiClear();
273 
274 	cancelled();
275 }
276 
cancelled()277 void PhoneWidget::cancelled() {
278 	api().request(base::take(_sentRequest)).cancel();
279 }
280 
281 } // namespace details
282 } // namespace Intro
283