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 "boxes/change_phone_box.h"
9 
10 #include "lang/lang_keys.h"
11 #include "ui/widgets/labels.h"
12 #include "ui/widgets/sent_code_field.h"
13 #include "ui/wrap/fade_wrap.h"
14 #include "ui/toast/toast.h"
15 #include "ui/text/format_values.h" // Ui::FormatPhone
16 #include "ui/text/text_utilities.h"
17 #include "ui/special_fields.h"
18 #include "ui/boxes/confirm_box.h"
19 #include "boxes/phone_banned_box.h"
20 #include "countries/countries_instance.h" // Countries::ExtractPhoneCode.
21 #include "main/main_session.h"
22 #include "data/data_session.h"
23 #include "data/data_user.h"
24 #include "mtproto/sender.h"
25 #include "apiwrap.h"
26 #include "window/window_session_controller.h"
27 #include "styles/style_layers.h"
28 #include "styles/style_boxes.h"
29 
30 namespace {
31 
CreateErrorLabel(QWidget * parent,object_ptr<Ui::FadeWrap<Ui::FlatLabel>> & label,const QString & text,int x,int y)32 void CreateErrorLabel(
33 		QWidget *parent,
34 		object_ptr<Ui::FadeWrap<Ui::FlatLabel>> &label,
35 		const QString &text,
36 		int x,
37 		int y) {
38 	if (label) {
39 		label->hide(anim::type::normal);
40 
41 		auto saved = label.data();
42 		auto destroy = [old = std::move(label)]() mutable {
43 			old.destroyDelayed();
44 		};
45 
46 		using namespace rpl::mappers;
47 		saved->shownValue()
48 			| rpl::filter(_1 == false)
49 			| rpl::take(1)
50 			| rpl::start_with_done(
51 				std::move(destroy),
52 				saved->lifetime());
53 	}
54 	if (!text.isEmpty()) {
55 		label.create(
56 			parent,
57 			object_ptr<Ui::FlatLabel>(
58 				parent,
59 				text,
60 				st::changePhoneError));
61 		label->hide(anim::type::instant);
62 		label->moveToLeft(x, y);
63 		label->show(anim::type::normal);
64 	}
65 }
66 
67 } // namespace
68 
69 class ChangePhoneBox::EnterPhone : public Ui::BoxContent {
70 public:
71 	EnterPhone(QWidget*, not_null<Window::SessionController*> controller);
72 
setInnerFocus()73 	void setInnerFocus() override {
74 		_phone->setFocusFast();
75 	}
76 
77 protected:
78 	void prepare() override;
79 
80 private:
81 	void submit();
82 	void sendPhoneDone(
83 		const MTPauth_SentCode &result,
84 		const QString &phoneNumber);
85 	void sendPhoneFail(const MTP::Error &error, const QString &phoneNumber);
86 	void showError(const QString &text);
hideError()87 	void hideError() {
88 		showError(QString());
89 	}
90 
91 	const not_null<Window::SessionController*> _controller;
92 	MTP::Sender _api;
93 
94 	object_ptr<Ui::PhoneInput> _phone = { nullptr };
95 	object_ptr<Ui::FadeWrap<Ui::FlatLabel>> _error = { nullptr };
96 	mtpRequestId _requestId = 0;
97 
98 };
99 
100 class ChangePhoneBox::EnterCode : public Ui::BoxContent {
101 public:
102 	EnterCode(
103 		QWidget*,
104 		not_null<Main::Session*> session,
105 		const QString &phone,
106 		const QString &hash,
107 		int codeLength,
108 		int callTimeout);
109 
setInnerFocus()110 	void setInnerFocus() override {
111 		_code->setFocusFast();
112 	}
113 
114 protected:
115 	void prepare() override;
116 
117 private:
118 	void submit();
119 	void sendCall();
120 	void updateCall();
121 	void sendCodeFail(const MTP::Error &error);
122 	void showError(const QString &text);
hideError()123 	void hideError() {
124 		showError(QString());
125 	}
126 	int countHeight();
127 
128 	const not_null<Main::Session*> _session;
129 	MTP::Sender _api;
130 
131 	QString _phone;
132 	QString _hash;
133 	int _codeLength = 0;
134 	int _callTimeout = 0;
135 	object_ptr<Ui::SentCodeField> _code = { nullptr };
136 	object_ptr<Ui::FadeWrap<Ui::FlatLabel>> _error = { nullptr };
137 	object_ptr<Ui::FlatLabel> _callLabel = { nullptr };
138 	mtpRequestId _requestId = 0;
139 	Ui::SentCodeCall _call;
140 
141 };
142 
EnterPhone(QWidget *,not_null<Window::SessionController * > controller)143 ChangePhoneBox::EnterPhone::EnterPhone(
144 	QWidget*,
145 	not_null<Window::SessionController*> controller)
146 : _controller(controller)
147 , _api(&controller->session().mtp()) {
148 }
149 
prepare()150 void ChangePhoneBox::EnterPhone::prepare() {
151 	setTitle(tr::lng_change_phone_title());
152 
153 	const auto phoneValue = QString();
154 	_phone.create(
155 		this,
156 		st::defaultInputField,
157 		tr::lng_change_phone_new_title(),
158 		Countries::ExtractPhoneCode(_controller->session().user()->phone()),
159 		phoneValue);
160 
161 	_phone->resize(
162 		st::boxWidth - 2 * st::boxPadding.left(),
163 		_phone->height());
164 	_phone->moveToLeft(st::boxPadding.left(), st::boxLittleSkip);
165 	connect(_phone, &Ui::PhoneInput::submitted, [=] { submit(); });
166 
167 	const auto description = object_ptr<Ui::FlatLabel>(
168 		this,
169 		tr::lng_change_phone_new_description(tr::now),
170 		st::changePhoneLabel);
171 	const auto errorSkip = st::boxLittleSkip
172 		+ st::changePhoneError.style.font->height;
173 	description->moveToLeft(
174 		st::boxPadding.left(),
175 		_phone->y() + _phone->height() + errorSkip + st::boxLittleSkip);
176 
177 	setDimensions(
178 		st::boxWidth,
179 		description->bottomNoMargins() + st::boxLittleSkip);
180 
181 	addButton(tr::lng_change_phone_new_submit(), [this] { submit(); });
182 	addButton(tr::lng_cancel(), [this] { closeBox(); });
183 }
184 
submit()185 void ChangePhoneBox::EnterPhone::submit() {
186 	if (_requestId) {
187 		return;
188 	}
189 	hideError();
190 
191 	const auto phoneNumber = _phone->getLastText().trimmed();
192 	_requestId = _api.request(MTPaccount_SendChangePhoneCode(
193 		MTP_string(phoneNumber),
194 		MTP_codeSettings(MTP_flags(0))
195 	)).done([=](const MTPauth_SentCode &result) {
196 		_requestId = 0;
197 		sendPhoneDone(result, phoneNumber);
198 	}).fail([=](const MTP::Error &error) {
199 		_requestId = 0;
200 		sendPhoneFail(error, phoneNumber);
201 	}).handleFloodErrors().send();
202 }
203 
sendPhoneDone(const MTPauth_SentCode & result,const QString & phoneNumber)204 void ChangePhoneBox::EnterPhone::sendPhoneDone(
205 		const MTPauth_SentCode &result,
206 		const QString &phoneNumber) {
207 	using CodeData = const MTPDauth_sentCode&;
208 	const auto &data = result.match([](const auto &data) -> CodeData {
209 		return data;
210 	});
211 
212 	auto codeLength = 0;
213 	const auto hasLength = data.vtype().match([&](
214 			const MTPDauth_sentCodeTypeApp &typeData) {
215 		LOG(("Error: should not be in-app code!"));
216 		showError(Lang::Hard::ServerError());
217 		return false;
218 	}, [&](const MTPDauth_sentCodeTypeSms &typeData) {
219 		codeLength = typeData.vlength().v;
220 		return true;
221 	}, [&](const MTPDauth_sentCodeTypeCall &typeData) {
222 		codeLength = typeData.vlength().v;
223 		return true;
224 	}, [&](const MTPDauth_sentCodeTypeFlashCall &typeData) {
225 		LOG(("Error: should not be flashcall!"));
226 		showError(Lang::Hard::ServerError());
227 		return false;
228 	});
229 	if (!hasLength) {
230 		return;
231 	}
232 	const auto phoneCodeHash = qs(data.vphone_code_hash());
233 	const auto callTimeout = [&] {
234 		if (const auto nextType = data.vnext_type()) {
235 			return nextType->match([&](const MTPDauth_sentCodeTypeCall &) {
236 				return data.vtimeout().value_or(60);
237 			}, [](const auto &) {
238 				return 0;
239 			});
240 		}
241 		return 0;
242 	}();
243 	_controller->show(
244 		Box<EnterCode>(
245 			&_controller->session(),
246 			phoneNumber,
247 			phoneCodeHash,
248 			codeLength,
249 			callTimeout),
250 		Ui::LayerOption::KeepOther);
251 }
252 
sendPhoneFail(const MTP::Error & error,const QString & phoneNumber)253 void ChangePhoneBox::EnterPhone::sendPhoneFail(
254 		const MTP::Error &error,
255 		const QString &phoneNumber) {
256 	if (MTP::IsFloodError(error)) {
257 		showError(tr::lng_flood_error(tr::now));
258 	} else if (error.type() == qstr("PHONE_NUMBER_INVALID")) {
259 		showError(tr::lng_bad_phone(tr::now));
260 	} else if (error.type() == qstr("PHONE_NUMBER_BANNED")) {
261 		Ui::ShowPhoneBannedError(&_controller->window(), phoneNumber);
262 	} else if (error.type() == qstr("PHONE_NUMBER_OCCUPIED")) {
263 		_controller->show(
264 			Box<Ui::InformBox>(
265 				tr::lng_change_phone_occupied(
266 					tr::now,
267 					lt_phone,
268 					Ui::FormatPhone(phoneNumber)),
269 				tr::lng_box_ok(tr::now)),
270 			Ui::LayerOption::CloseOther);
271 	} else {
272 		showError(Lang::Hard::ServerError());
273 	}
274 }
275 
showError(const QString & text)276 void ChangePhoneBox::EnterPhone::showError(const QString &text) {
277 	CreateErrorLabel(
278 		this,
279 		_error,
280 		text,
281 		st::boxPadding.left(),
282 		_phone->y() + _phone->height() + st::boxLittleSkip);
283 	if (!text.isEmpty()) {
284 		_phone->showError();
285 	}
286 }
287 
EnterCode(QWidget *,not_null<Main::Session * > session,const QString & phone,const QString & hash,int codeLength,int callTimeout)288 ChangePhoneBox::EnterCode::EnterCode(
289 	QWidget*,
290 	not_null<Main::Session*> session,
291 	const QString &phone,
292 	const QString &hash,
293 	int codeLength,
294 	int callTimeout)
295 : _session(session)
296 , _api(&session->mtp())
297 , _phone(phone)
298 , _hash(hash)
299 , _codeLength(codeLength)
300 , _callTimeout(callTimeout)
301 , _call([this] { sendCall(); }, [this] { updateCall(); }) {
302 }
303 
prepare()304 void ChangePhoneBox::EnterCode::prepare() {
305 	setTitle(tr::lng_change_phone_title());
306 
307 	const auto descriptionText = tr::lng_change_phone_code_description(
308 		tr::now,
309 		lt_phone,
310 		Ui::Text::Bold(Ui::FormatPhone(_phone)),
311 		Ui::Text::WithEntities);
312 	const auto description = object_ptr<Ui::FlatLabel>(
313 		this,
314 		rpl::single(descriptionText),
315 		st::changePhoneLabel);
316 	description->moveToLeft(st::boxPadding.left(), 0);
317 
318 	const auto phoneValue = QString();
319 	_code.create(
320 		this,
321 		st::defaultInputField,
322 		tr::lng_change_phone_code_title(),
323 		phoneValue);
324 	_code->setAutoSubmit(_codeLength, [=] { submit(); });
325 	_code->setChangedCallback([=] { hideError(); });
326 
327 	_code->resize(st::boxWidth - 2 * st::boxPadding.left(), _code->height());
328 	_code->moveToLeft(st::boxPadding.left(), description->bottomNoMargins());
329 	connect(_code, &Ui::InputField::submitted, [=] { submit(); });
330 
331 	setDimensions(st::boxWidth, countHeight());
332 
333 	if (_callTimeout > 0) {
334 		_call.setStatus({ Ui::SentCodeCall::State::Waiting, _callTimeout });
335 		updateCall();
336 	}
337 
338 	addButton(tr::lng_change_phone_new_submit(), [=] { submit(); });
339 	addButton(tr::lng_cancel(), [=] { closeBox(); });
340 }
341 
countHeight()342 int ChangePhoneBox::EnterCode::countHeight() {
343 	const auto errorSkip = st::boxLittleSkip
344 		+ st::changePhoneError.style.font->height;
345 	return _code->bottomNoMargins() + errorSkip + 3 * st::boxLittleSkip;
346 }
347 
submit()348 void ChangePhoneBox::EnterCode::submit() {
349 	if (_requestId) {
350 		return;
351 	}
352 	hideError();
353 
354 	const auto session = _session;
355 	const auto code = _code->getDigitsOnly();
356 	const auto weak = Ui::MakeWeak(this);
357 	_requestId = session->api().request(MTPaccount_ChangePhone(
358 		MTP_string(_phone),
359 		MTP_string(_hash),
360 		MTP_string(code)
361 	)).done([=](const MTPUser &result) {
362 		_requestId = 0;
363 		session->data().processUser(result);
364 		if (weak) {
365 			Ui::hideLayer();
366 		}
367 		Ui::Toast::Show(tr::lng_change_phone_success(tr::now));
368 	}).fail(crl::guard(this, [=](const MTP::Error &error) {
369 		_requestId = 0;
370 		sendCodeFail(error);
371 	})).handleFloodErrors().send();
372 }
373 
sendCall()374 void ChangePhoneBox::EnterCode::sendCall() {
375 	_api.request(MTPauth_ResendCode(
376 		MTP_string(_phone),
377 		MTP_string(_hash)
378 	)).done([=](const MTPauth_SentCode &result) {
379 		_call.callDone();
380 	}).send();
381 }
382 
updateCall()383 void ChangePhoneBox::EnterCode::updateCall() {
384 	const auto text = _call.getText();
385 	if (text.isEmpty()) {
386 		_callLabel.destroy();
387 	} else if (!_callLabel) {
388 		_callLabel.create(this, text, st::changePhoneLabel);
389 		_callLabel->moveToLeft(
390 			st::boxPadding.left(),
391 			countHeight() - _callLabel->height());
392 		_callLabel->show();
393 	} else {
394 		_callLabel->setText(text);
395 	}
396 }
397 
showError(const QString & text)398 void ChangePhoneBox::EnterCode::showError(const QString &text) {
399 	CreateErrorLabel(
400 		this,
401 		_error,
402 		text,
403 		st::boxPadding.left(),
404 		_code->y() + _code->height() + st::boxLittleSkip);
405 	if (!text.isEmpty()) {
406 		_code->showError();
407 	}
408 }
409 
sendCodeFail(const MTP::Error & error)410 void ChangePhoneBox::EnterCode::sendCodeFail(const MTP::Error &error) {
411 	if (MTP::IsFloodError(error)) {
412 		showError(tr::lng_flood_error(tr::now));
413 	} else if (error.type() == qstr("PHONE_CODE_EMPTY")
414 		|| error.type() == qstr("PHONE_CODE_INVALID")) {
415 		showError(tr::lng_bad_code(tr::now));
416 	} else if (error.type() == qstr("PHONE_CODE_EXPIRED")
417 		|| error.type() == qstr("PHONE_NUMBER_BANNED")) {
418 		closeBox(); // Go back to phone input.
419 	} else if (error.type() == qstr("PHONE_NUMBER_INVALID")) {
420 		showError(tr::lng_bad_phone(tr::now));
421 	} else {
422 		showError(Lang::Hard::ServerError());
423 	}
424 }
425 
ChangePhoneBox(QWidget *,not_null<Window::SessionController * > controller)426 ChangePhoneBox::ChangePhoneBox(
427 	QWidget*,
428 	not_null<Window::SessionController*> controller)
429 : _controller(controller) {
430 }
431 
prepare()432 void ChangePhoneBox::prepare() {
433 	setTitle(tr::lng_change_phone_title());
434 	addButton(tr::lng_change_phone_button(), [=, controller = _controller] {
435 		auto callback = [=] {
436 			controller->show(
437 				Box<EnterPhone>(controller),
438 				Ui::LayerOption::CloseOther);
439 		};
440 		controller->show(
441 			Box<Ui::ConfirmBox>(
442 				tr::lng_change_phone_warning(tr::now),
443 				std::move(callback)),
444 			Ui::LayerOption::CloseOther);
445 	});
446 	addButton(tr::lng_cancel(), [this] {
447 		closeBox();
448 	});
449 
450 	const auto label = Ui::CreateChild<Ui::FlatLabel>(
451 		this,
452 		tr::lng_change_phone_about(Ui::Text::RichLangValue),
453 		st::changePhoneDescription);
454 	label->moveToLeft(
455 		(st::boxWideWidth - label->width()) / 2,
456 		st::changePhoneDescriptionTop);
457 
458 	setDimensions(
459 		st::boxWideWidth,
460 		label->bottomNoMargins() + st::boxLittleSkip);
461 }
462 
paintEvent(QPaintEvent * e)463 void ChangePhoneBox::paintEvent(QPaintEvent *e) {
464 	BoxContent::paintEvent(e);
465 
466 	Painter p(this);
467 	st::changePhoneIcon.paint(
468 		p,
469 		(width() - st::changePhoneIcon.width()) / 2,
470 		st::changePhoneIconTop,
471 		width());
472 }
473