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