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