diff --git a/Telegram/Resources/colors.palette b/Telegram/Resources/colors.palette index 03141b33d..6d34b8563 100644 --- a/Telegram/Resources/colors.palette +++ b/Telegram/Resources/colors.palette @@ -456,6 +456,9 @@ notificationSampleCloseFg: #d7d7d7 | windowSubTextFg; // custom notifications se notificationSampleTextFg: #d7d7d7 | windowSubTextFg; // custom notifications settings box small sample text placeholder notificationSampleNameFg: #939393 | windowSubTextFg; // custom notifications settings box small sample name placeholder +changePhoneSimcardFrom: notificationSampleTextFg; // change phone number box left simcard icon +changePhoneSimcardTo: notificationSampleNameFg; // change phone number box right simcard and plane icons + mainMenuBg: windowBg; // main menu background mainMenuCoverBg: dialogsBgActive; // main menu top cover background mainMenuCoverFg: windowFgActive; // main menu top cover text diff --git a/Telegram/Resources/icons/phone_simcard_from.png b/Telegram/Resources/icons/phone_simcard_from.png new file mode 100644 index 000000000..e9a8e089a Binary files /dev/null and b/Telegram/Resources/icons/phone_simcard_from.png differ diff --git a/Telegram/Resources/icons/phone_simcard_from@2x.png b/Telegram/Resources/icons/phone_simcard_from@2x.png new file mode 100644 index 000000000..5eb9e027b Binary files /dev/null and b/Telegram/Resources/icons/phone_simcard_from@2x.png differ diff --git a/Telegram/Resources/icons/phone_simcard_migrate.png b/Telegram/Resources/icons/phone_simcard_migrate.png new file mode 100644 index 000000000..4f260997c Binary files /dev/null and b/Telegram/Resources/icons/phone_simcard_migrate.png differ diff --git a/Telegram/Resources/icons/phone_simcard_migrate@2x.png b/Telegram/Resources/icons/phone_simcard_migrate@2x.png new file mode 100644 index 000000000..4a27a4aaf Binary files /dev/null and b/Telegram/Resources/icons/phone_simcard_migrate@2x.png differ diff --git a/Telegram/Resources/icons/phone_simcard_to.png b/Telegram/Resources/icons/phone_simcard_to.png new file mode 100644 index 000000000..ace803c02 Binary files /dev/null and b/Telegram/Resources/icons/phone_simcard_to.png differ diff --git a/Telegram/Resources/icons/phone_simcard_to@2x.png b/Telegram/Resources/icons/phone_simcard_to@2x.png new file mode 100644 index 000000000..710808ec2 Binary files /dev/null and b/Telegram/Resources/icons/phone_simcard_to@2x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0ae0fc3a3..acb484561 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -470,6 +470,18 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_self_destruct_months" = "{count:_not_used_|# month|# months}"; "lng_self_destruct_years" = "{count:_not_used_|# year|# years}"; +"lng_change_phone_title" = "Change phone number"; +"lng_change_phone_description" = "You can change your Telegram number\nhere. Your account and all your cloud data\n— messages, media, contacts, etc. will be\nmoved to the new number.\n\n[b]Important[/b]: all your Telegram contacts will\nget your [b]new number[/b] added to their address\nbook, provided they had your old number and\nyou haven't blocked them in Telegram."; +"lng_change_phone_warning" = "All your Telegram contacts will get your new number added to their address book, provided they had your old number and you haven't blocked them in Telegram."; +"lng_change_phone_occupied" = "The number {phone} is already connected to a Telegram account. Please delete that account before migrating to the new number."; +"lng_change_phone_button" = "Change number"; +"lng_change_phone_new_title" = "Enter new number"; +"lng_change_phone_new_description" = "We will send an SMS with a confirmation code to your new number."; +"lng_change_phone_new_submit" = "Submit"; +"lng_change_phone_code_title" = "Enter code"; +"lng_change_phone_code_description" = "We've sent an SMS with a confirmation code to your phone {phone}."; +"lng_change_phone_success" = "Your phone number was changed."; + "lng_preview_loading" = "Getting Link Info..."; "lng_profile_chat_unaccessible" = "Group is inaccessible"; diff --git a/Telegram/SourceFiles/boxes/abstractbox.h b/Telegram/SourceFiles/boxes/abstractbox.h index 0ef58121d..867b50fc6 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.h +++ b/Telegram/SourceFiles/boxes/abstractbox.h @@ -61,6 +61,8 @@ public: BoxContent() { setAttribute(Qt::WA_OpaquePaintEvent); } + BoxContent(QWidget*) : BoxContent() { + } bool isBoxShown() const { return getDelegate()->isBoxShown(); diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 34938a883..089e7e506 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -605,3 +605,22 @@ editPrivacyLabel: FlatLabel(defaultFlatLabel) { style: defaultTextStyle; } editPrivacyLinkMargin: margins(0px, 0px, 0px, 8px); + +changePhoneIcon: icon { + { "phone_simcard_from", changePhoneSimcardFrom }, + { "phone_simcard_migrate", changePhoneSimcardTo, point(30px, 11px) }, + { "phone_simcard_to", changePhoneSimcardTo, point(78px, 0px) } +}; +changePhoneDescription: FlatLabel(boxLabel) { + width: 332px; + align: align(top); +} +changePhoneIconTop: 20px; +changePhoneDescriptionTop: 96px; +changePhoneLabel: FlatLabel(defaultFlatLabel) { + width: 275px; + textFg: windowSubTextFg; +} +changePhoneError: FlatLabel(changePhoneLabel) { + textFg: boxTextFgError; +} diff --git a/Telegram/SourceFiles/boxes/change_phone_box.cpp b/Telegram/SourceFiles/boxes/change_phone_box.cpp new file mode 100644 index 000000000..725b348e8 --- /dev/null +++ b/Telegram/SourceFiles/boxes/change_phone_box.cpp @@ -0,0 +1,324 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#include "boxes/change_phone_box.h" + +#include "lang.h" +#include "styles/style_boxes.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/input_fields.h" +#include "ui/effects/widget_fade_wrap.h" +#include "boxes/confirmphonebox.h" +#include "ui/toast/toast.h" +#include "boxes/confirmbox.h" + +namespace { + +void createErrorLabel(QWidget *parent, object_ptr> &label, const QString &text, int x, int y) { + if (label) { + auto errorFadeOut = std::move(label); + errorFadeOut->setUpdateCallback([label = errorFadeOut.data()] { + if (label->isHidden() || !label->animating()) { + label->deleteLater(); + } + }); + errorFadeOut->hideAnimated(); + } + if (!text.isEmpty()) { + label.create(parent, object_ptr(parent, text, Ui::FlatLabel::InitType::Simple, st::changePhoneError)); + label->hideFast(); + label->moveToLeft(x, y); + label->showAnimated(); + } +} + +} // namespace + +class ChangePhoneBox::EnterPhone : public BoxContent { +public: + using BoxContent::BoxContent; + + void setInnerFocus() override { + _phone->setFocusFast(); + } + +protected: + void prepare() override; + +private: + void submit(); + void sendPhoneDone(const QString &phoneNumber, const MTPauth_SentCode &result); + bool sendPhoneFail(const QString &phoneNumber, const RPCError &error); + void showError(const QString &text); + void hideError() { + showError(QString()); + } + + object_ptr _phone = { nullptr }; + object_ptr> _error = { nullptr }; + mtpRequestId _requestId = 0; + +}; + +class ChangePhoneBox::EnterCode : public BoxContent { +public: + EnterCode(QWidget*, const QString &phone, const QString &hash, int codeLength, int callTimeout); + + void setInnerFocus() override { + _code->setFocusFast(); + } + +protected: + void prepare() override; + +private: + void submit(); + void sendCall(); + void updateCall(); + bool sendCodeFail(const RPCError &error); + void showError(const QString &text); + void hideError() { + showError(QString()); + } + int countHeight(); + + QString _phone; + QString _hash; + int _codeLength = 0; + int _callTimeout = 0; + object_ptr _code = { nullptr }; + object_ptr> _error = { nullptr }; + object_ptr _callLabel = { nullptr }; + mtpRequestId _requestId = 0; + SentCodeCall _call; + +}; + +void ChangePhoneBox::EnterPhone::prepare() { + setTitle(lang(lng_change_phone_title)); + + auto phoneValue = QString(); + _phone.create(this, st::defaultInputField, lang(lng_change_phone_new_title), phoneValue); + + _phone->resize(st::boxWidth - 2 * st::boxPadding.left(), _phone->height()); + _phone->moveToLeft(st::boxPadding.left(), st::boxLittleSkip); + connect(_phone, &Ui::PhoneInput::submitted, this, [this] { submit(); }); + + auto description = object_ptr(this, lang(lng_change_phone_new_description), Ui::FlatLabel::InitType::Simple, st::changePhoneLabel); + auto errorSkip = st::boxLittleSkip + st::changePhoneError.style.font->height; + description->moveToLeft(st::boxPadding.left(), _phone->y() + _phone->height() + errorSkip + st::boxLittleSkip); + + setDimensions(st::boxWidth, description->bottomNoMargins() + st::boxLittleSkip); + + addButton(lang(lng_change_phone_new_submit), [this] { submit(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); +} + +void ChangePhoneBox::EnterPhone::submit() { + if (_requestId) { + return; + } + hideError(); + + auto phoneNumber = _phone->getLastText().trimmed(); + auto flags = MTPaccount_SendChangePhoneCode::Flags(0); + _requestId = MTP::send(MTPaccount_SendChangePhoneCode(MTP_flags(flags), MTP_string(phoneNumber), MTP_bool(false)), rpcDone(base::lambda_guarded(this, [this, phoneNumber](const MTPauth_SentCode &result) { + return sendPhoneDone(phoneNumber, result); + })), rpcFail(base::lambda_guarded(this, [this, phoneNumber](const RPCError &error) { + return sendPhoneFail(phoneNumber, error); + }))); +} + +void ChangePhoneBox::EnterPhone::sendPhoneDone(const QString &phoneNumber, const MTPauth_SentCode &result) { + Expects(result.type() == mtpc_auth_sentCode); + _requestId = 0; + + auto codeLength = 0; + auto &data = result.c_auth_sentCode(); + switch (data.vtype.type()) { + case mtpc_auth_sentCodeTypeApp: + LOG(("Error: should not be in-app code!")); + showError(lang(lng_server_error)); + return; + case mtpc_auth_sentCodeTypeSms: codeLength = data.vtype.c_auth_sentCodeTypeSms().vlength.v; break; + case mtpc_auth_sentCodeTypeCall: codeLength = data.vtype.c_auth_sentCodeTypeCall().vlength.v; break; + case mtpc_auth_sentCodeTypeFlashCall: + LOG(("Error: should not be flashcall!")); + showError(lang(lng_server_error)); + return; + } + auto phoneCodeHash = qs(data.vphone_code_hash); + auto callTimeout = 0; + if (data.has_next_type() && data.vnext_type.type() == mtpc_auth_codeTypeCall) { + callTimeout = data.has_timeout() ? data.vtimeout.v : 60; + } + Ui::show(Box(phoneNumber, phoneCodeHash, codeLength, callTimeout), KeepOtherLayers); +} + +bool ChangePhoneBox::EnterPhone::sendPhoneFail(const QString &phoneNumber, const RPCError &error) { + auto errorText = lang(lng_server_error); + if (MTP::isFloodError(error)) { + errorText = lang(lng_flood_error); + } else if (MTP::isDefaultHandledError(error)) { + return false; + } else if (error.type() == qstr("PHONE_NUMBER_INVALID")) { + errorText = lang(lng_bad_phone); + } else if (error.type() == qstr("PHONE_NUMBER_OCCUPIED")) { + Ui::show(Box(lng_change_phone_occupied(lt_phone, App::formatPhone(phoneNumber)), lang(lng_box_ok))); + _requestId = 0; + return true; + } + showError(errorText); + _requestId = 0; + return true; +} + +void ChangePhoneBox::EnterPhone::showError(const QString &text) { + createErrorLabel(this, _error, text, st::boxPadding.left(), _phone->y() + _phone->height() + st::boxLittleSkip); + if (!text.isEmpty()) { + _phone->showError(); + } +} + +ChangePhoneBox::EnterCode::EnterCode(QWidget*, const QString &phone, const QString &hash, int codeLength, int callTimeout) +: _phone(phone) +, _hash(hash) +, _codeLength(codeLength) +, _callTimeout(callTimeout) +, _call(this, [this] { sendCall(); }, [this] { updateCall(); }) { +} + +void ChangePhoneBox::EnterCode::prepare() { + setTitle(lang(lng_change_phone_title)); + + auto descriptionText = lng_change_phone_code_description(lt_phone, textcmdStartSemibold() + App::formatPhone(_phone) + textcmdStopSemibold()); + auto description = object_ptr(this, descriptionText, Ui::FlatLabel::InitType::Rich, st::changePhoneLabel); + description->moveToLeft(st::boxPadding.left(), 0); + + auto phoneValue = QString(); + _code.create(this, st::defaultInputField, lang(lng_change_phone_code_title), phoneValue); + _code->setAutoSubmit(_codeLength, [this] { submit(); }); + _code->setChangedCallback([this] { hideError(); }); + + _code->resize(st::boxWidth - 2 * st::boxPadding.left(), _code->height()); + _code->moveToLeft(st::boxPadding.left(), description->bottomNoMargins()); + connect(_code, &Ui::InputField::submitted, this, [this] { submit(); }); + + setDimensions(st::boxWidth, countHeight()); + + if (_callTimeout > 0) { + _call.setStatus({ SentCodeCall::State::Waiting, _callTimeout }); + updateCall(); + } + + addButton(lang(lng_change_phone_new_submit), [this] { submit(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); +} + +int ChangePhoneBox::EnterCode::countHeight() { + auto errorSkip = st::boxLittleSkip + st::changePhoneError.style.font->height; + return _code->bottomNoMargins() + errorSkip + 3 * st::boxLittleSkip; +} + +void ChangePhoneBox::EnterCode::submit() { + if (_requestId) { + return; + } + hideError(); + + auto code = _code->getLastText().trimmed(); + _requestId = MTP::send(MTPaccount_ChangePhone(MTP_string(_phone), MTP_string(_hash), MTP_string(code)), rpcDone([weak = weak(this)](const MTPUser &result) { + App::feedUser(result); + if (weak) { + Ui::hideLayer(); + } + Ui::Toast::Show(lang(lng_change_phone_success)); + }), rpcFail(base::lambda_guarded(this, [this](const RPCError &error) { + return sendCodeFail(error); + }))); +} + +void ChangePhoneBox::EnterCode::sendCall() { + MTP::send(MTPauth_ResendCode(MTP_string(_phone), MTP_string(_hash)), rpcDone(base::lambda_guarded(this, [this] { + _call.callDone(); + }))); +} + +void ChangePhoneBox::EnterCode::updateCall() { + auto text = _call.getText(); + if (text.isEmpty()) { + _callLabel.destroy(); + } else if (!_callLabel) { + _callLabel.create(this, text, Ui::FlatLabel::InitType::Simple, st::changePhoneLabel); + _callLabel->moveToLeft(st::boxPadding.left(), countHeight() - _callLabel->height()); + _callLabel->show(); + } else { + _callLabel->setText(text); + } +} + +void ChangePhoneBox::EnterCode::showError(const QString &text) { + createErrorLabel(this, _error, text, st::boxPadding.left(), _code->y() + _code->height() + st::boxLittleSkip); + if (!text.isEmpty()) { + _code->showError(); + } +} + +bool ChangePhoneBox::EnterCode::sendCodeFail(const RPCError &error) { + auto errorText = lang(lng_server_error); + if (MTP::isFloodError(error)) { + errorText = lang(lng_flood_error); + } else if (MTP::isDefaultHandledError(error)) { + return false; + } else if (error.type() == qstr("PHONE_CODE_EMPTY") || error.type() == qstr("PHONE_CODE_INVALID")) { + errorText = lang(lng_bad_code); + } else if (error.type() == qstr("PHONE_CODE_EXPIRED")) { + closeBox(); // Go back to phone input. + _requestId = 0; + return true; + } else if (error.type() == qstr("PHONE_NUMBER_INVALID")) { + errorText = lang(lng_bad_phone); + } + _requestId = 0; + showError(errorText); + return true; +} + +void ChangePhoneBox::prepare() { + setTitle(lang(lng_change_phone_title)); + addButton(lang(lng_change_phone_button), [this] { + Ui::show(Box()); + }); + addButton(lang(lng_cancel), [this] { + closeBox(); + }); + + auto label = object_ptr(this, lang(lng_change_phone_description), Ui::FlatLabel::InitType::Rich, st::changePhoneDescription); + label->moveToLeft((st::boxWideWidth - label->width()) / 2, st::changePhoneDescriptionTop); + + setDimensions(st::boxWideWidth, label->bottomNoMargins() + st::boxLittleSkip); +} + +void ChangePhoneBox::paintEvent(QPaintEvent *e) { + BoxContent::paintEvent(e); + + Painter p(this); + st::changePhoneIcon.paint(p, (width() - st::changePhoneIcon.width()) / 2, st::changePhoneIconTop, width()); +} diff --git a/Telegram/SourceFiles/boxes/change_phone_box.h b/Telegram/SourceFiles/boxes/change_phone_box.h new file mode 100644 index 000000000..ca696d54e --- /dev/null +++ b/Telegram/SourceFiles/boxes/change_phone_box.h @@ -0,0 +1,39 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "boxes/abstractbox.h" + +class ChangePhoneBox : public BoxContent { +public: + using BoxContent::BoxContent; + +protected: + void prepare() override; + + void paintEvent(QPaintEvent *e) override; + +private: + class EnterPhone; + class EnterCode; + +}; + diff --git a/Telegram/SourceFiles/boxes/confirmphonebox.cpp b/Telegram/SourceFiles/boxes/confirmphonebox.cpp index aadb7d0fe..492b7186f 100644 --- a/Telegram/SourceFiles/boxes/confirmphonebox.cpp +++ b/Telegram/SourceFiles/boxes/confirmphonebox.cpp @@ -34,6 +34,102 @@ object_ptr CurrentConfirmPhoneBox = { nullptr }; } // namespace +void SentCodeField::fix() { + if (_fixing) return; + + _fixing = true; + auto newText = QString(); + auto now = getLastText(); + auto oldPos = textCursor().position(); + auto newPos = -1; + auto oldLen = now.size(); + auto digitCount = 0; + for_const (auto ch, now) { + if (ch.isDigit()) { + ++digitCount; + } + } + + if (_autoSubmitLength > 0 && digitCount > _autoSubmitLength) { + digitCount = _autoSubmitLength; + } + auto strict = (_autoSubmitLength > 0 && digitCount == _autoSubmitLength); + + newText.reserve(oldLen); + int i = 0; + for_const (auto ch, now) { + if (i++ == oldPos) { + newPos = newText.length(); + } + if (ch.isDigit()) { + if (!digitCount--) { + break; + } + newText += ch; + if (strict && !digitCount) { + break; + } + } + } + if (newPos < 0) { + newPos = newText.length(); + } + if (newText != now) { + now = newText; + setText(now); + setCursorPosition(newPos); + } + _fixing = false; + + if (_changedCallback) { + _changedCallback(); + } + if (strict && _submitCallback) { + _submitCallback(); + } +} + +SentCodeCall::SentCodeCall(QObject *parent, base::lambda_once callCallback, base::lambda updateCallback) +: _timer(parent) +, _call(std::move(callCallback)) +, _update(std::move(updateCallback)) { + _timer->connect(_timer, &QTimer::timeout, [this] { + if (_status.state == State::Waiting) { + if (--_status.timeout <= 0) { + _status.state = State::Calling; + _timer->stop(); + if (_call) { + _call(); + } + } + } + if (_update) { + _update(); + } + }); +} + +void SentCodeCall::setStatus(const Status &status) { + _status = status; + if (_status.state == State::Waiting) { + _timer->start(1000); + } +} + +QString SentCodeCall::getText() const { + switch (_status.state) { + case State::Waiting: { + if (_status.timeout >= 3600) { + return lng_code_call(lt_minutes, qsl("%1:%2").arg(_status.timeout / 3600).arg((_status.timeout / 60) % 60, 2, 10, QChar('0')), lt_seconds, qsl("%1").arg(_status.timeout % 60, 2, 10, QChar('0'))); + } + return lng_code_call(lt_minutes, QString::number(_status.timeout / 60), lt_seconds, qsl("%1").arg(_status.timeout % 60, 2, 10, QChar('0'))); + } break; + case State::Calling: return lang(lng_code_calling); + case State::Called: return lang(lng_code_called); + } + return QString(); +} + void ConfirmPhoneBox::start(const QString &phone, const QString &hash) { if (CurrentConfirmPhoneBox && CurrentConfirmPhoneBox->getPhone() != phone) { CurrentConfirmPhoneBox.destroyDelayed(); @@ -47,7 +143,11 @@ void ConfirmPhoneBox::start(const QString &phone, const QString &hash) { ConfirmPhoneBox::ConfirmPhoneBox(QWidget*, const QString &phone, const QString &hash) : _phone(phone) , _hash(hash) -, _callTimer(this) { +, _call(this, [this] { sendCall(); }, [this] { update(); }) { +} + +void ConfirmPhoneBox::sendCall() { + MTP::send(MTPauth_ResendCode(MTP_string(_phone), MTP_string(_phoneHash)), rpcDone(&ConfirmPhoneBox::callDone)); } void ConfirmPhoneBox::checkPhoneAndHash() { @@ -59,6 +159,7 @@ void ConfirmPhoneBox::checkPhoneAndHash() { } void ConfirmPhoneBox::sendCodeDone(const MTPauth_SentCode &result) { + Expects(result.type() == mtpc_auth_sentCode); _sendCodeRequestId = 0; auto &resultInner = result.c_auth_sentCode(); @@ -70,9 +171,7 @@ void ConfirmPhoneBox::sendCodeDone(const MTPauth_SentCode &result) { } _phoneHash = qs(resultInner.vphone_code_hash); if (resultInner.has_next_type() && resultInner.vnext_type.type() == mtpc_auth_codeTypeCall) { - setCallStatus({ CallState::Waiting, resultInner.has_timeout() ? resultInner.vtimeout.v : 60 }); - } else { - setCallStatus({ CallState::Disabled, 0 }); + _call.setStatus({ SentCodeCall::State::Waiting, resultInner.has_timeout() ? resultInner.vtimeout.v : 60 }); } launch(); } @@ -96,20 +195,12 @@ bool ConfirmPhoneBox::sendCodeFail(const RPCError &error) { return true; } -void ConfirmPhoneBox::setCallStatus(const CallStatus &status) { - _callStatus = status; - if (_callStatus.state == CallState::Waiting) { - _callTimer->start(1000); - } -} - void ConfirmPhoneBox::launch() { if (!CurrentConfirmPhoneBox) return; Ui::show(std::move(CurrentConfirmPhoneBox)); } void ConfirmPhoneBox::prepare() { - _about.create(this, st::confirmPhoneAboutLabel); TextWithEntities aboutText; auto formattedPhone = App::formatPhone(_phone); @@ -121,6 +212,8 @@ void ConfirmPhoneBox::prepare() { _about->setMarkedText(aboutText); _code.create(this, st::confirmPhoneCodeField, lang(lng_code_ph)); + _code->setAutoSubmit(_sentCodeLength, [this] { onSendCode(); }); + _code->setChangedCallback([this] { showError(QString()); }); setTitle(lang(lng_confirm_phone_title)); @@ -129,30 +222,13 @@ void ConfirmPhoneBox::prepare() { setDimensions(st::boxWidth, st::usernamePadding.top() + _code->height() + st::usernameSkip + _about->height() + st::usernameSkip); - connect(_code, SIGNAL(changed()), this, SLOT(onCodeChanged())); connect(_code, SIGNAL(submitted(bool)), this, SLOT(onSendCode())); - connect(_callTimer, SIGNAL(timeout()), this, SLOT(onCallStatusTimer())); - showChildren(); } -void ConfirmPhoneBox::onCallStatusTimer() { - if (_callStatus.state == CallState::Waiting) { - if (--_callStatus.timeout <= 0) { - _callStatus.state = CallState::Calling; - _callTimer->stop(); - MTP::send(MTPauth_ResendCode(MTP_string(_phone), MTP_string(_phoneHash)), rpcDone(&ConfirmPhoneBox::callDone)); - } - } - update(); -} - void ConfirmPhoneBox::callDone(const MTPauth_SentCode &result) { - if (_callStatus.state == CallState::Calling) { - _callStatus.state = CallState::Called; - update(); - } + _call.callDone(); } void ConfirmPhoneBox::onSendCode() { @@ -197,56 +273,6 @@ bool ConfirmPhoneBox::confirmFail(const RPCError &error) { return true; } -void ConfirmPhoneBox::onCodeChanged() { - if (_fixing) return; - - _fixing = true; - QString newText, now = _code->getLastText(); - int oldPos = _code->textCursor().position(), newPos = -1; - int oldLen = now.size(), digitCount = 0; - for_const (auto ch, now) { - if (ch.isDigit()) { - ++digitCount; - } - } - - if (_sentCodeLength > 0 && digitCount > _sentCodeLength) { - digitCount = _sentCodeLength; - } - bool strict = (_sentCodeLength > 0 && digitCount == _sentCodeLength); - - newText.reserve(oldLen); - int i = 0; - for_const (auto ch, now) { - if (i++ == oldPos) { - newPos = newText.length(); - } - if (ch.isDigit()) { - if (!digitCount--) { - break; - } - newText += ch; - if (strict && !digitCount) { - break; - } - } - } - if (newPos < 0) { - newPos = newText.length(); - } - if (newText != now) { - now = newText; - _code->setText(now); - _code->setCursorPosition(newPos); - } - _fixing = false; - - showError(QString()); - if (strict) { - onSendCode(); - } -} - void ConfirmPhoneBox::showError(const QString &error) { _error = error; if (!_error.isEmpty()) { @@ -261,7 +287,7 @@ void ConfirmPhoneBox::paintEvent(QPaintEvent *e) { Painter p(this); p.setFont(st::boxTextFont); - auto callText = getCallText(); + auto callText = _call.getText(); if (!callText.isEmpty()) { p.setPen(st::usernameDefaultFg); auto callTextRectLeft = st::usernamePadding.left(); @@ -284,20 +310,6 @@ void ConfirmPhoneBox::paintEvent(QPaintEvent *e) { p.drawText(errorTextRect, errorText, style::al_left); } -QString ConfirmPhoneBox::getCallText() const { - switch (_callStatus.state) { - case CallState::Waiting: { - if (_callStatus.timeout >= 3600) { - return lng_code_call(lt_minutes, qsl("%1:%2").arg(_callStatus.timeout / 3600).arg((_callStatus.timeout / 60) % 60, 2, 10, QChar('0')), lt_seconds, qsl("%1").arg(_callStatus.timeout % 60, 2, 10, QChar('0'))); - } - return lng_code_call(lt_minutes, QString::number(_callStatus.timeout / 60), lt_seconds, qsl("%1").arg(_callStatus.timeout % 60, 2, 10, QChar('0'))); - } break; - case CallState::Calling: return lang(lng_code_calling); - case CallState::Called: return lang(lng_code_called); - } - return QString(); -} - void ConfirmPhoneBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); diff --git a/Telegram/SourceFiles/boxes/confirmphonebox.h b/Telegram/SourceFiles/boxes/confirmphonebox.h index a48f5ffff..5f0b8a7bd 100644 --- a/Telegram/SourceFiles/boxes/confirmphonebox.h +++ b/Telegram/SourceFiles/boxes/confirmphonebox.h @@ -21,12 +21,79 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "boxes/abstractbox.h" +#include "ui/widgets/input_fields.h" namespace Ui { class InputField; class FlatLabel; } // namespace Ui +class SentCodeField : public Ui::InputField { +public: + SentCodeField(QWidget *parent, const style::InputField &st, const QString &ph = QString(), const QString &val = QString()) : Ui::InputField(parent, st, ph, val) { + connect(this, &Ui::InputField::changed, [this] { fix(); }); + } + + void setAutoSubmit(int length, base::lambda submitCallback) { + _autoSubmitLength = length; + _submitCallback = std::move(submitCallback); + } + void setChangedCallback(base::lambda changedCallback) { + _changedCallback = std::move(changedCallback); + } + +private: + void fix(); + + // Flag for not calling onTextChanged() recursively. + bool _fixing = false; + + int _autoSubmitLength = 0; + base::lambda _submitCallback; + base::lambda _changedCallback; + +}; + +class SentCodeCall { +public: + SentCodeCall(QObject *parent, base::lambda_once callCallback, base::lambda updateCallback); + + enum class State { + Waiting, + Calling, + Called, + Disabled, + }; + struct Status { + Status() { + } + Status(State state, int timeout) : state(state), timeout(timeout) { + } + + State state = State::Disabled; + int timeout = 0; + }; + void setStatus(const Status &status); + + void callDone() { + if (_status.state == State::Calling) { + _status.state = State::Called; + if (_update) { + _update(); + } + } + } + + QString getText() const; + +private: + Status _status; + object_ptr _timer; + base::lambda_once _call; + base::lambda _update; + +}; + class ConfirmPhoneBox : public BoxContent, public RPCSender { Q_OBJECT @@ -36,9 +103,7 @@ public: ~ConfirmPhoneBox(); private slots: - void onCallStatusTimer(); void onSendCode(); - void onCodeChanged(); protected: void prepare() override; @@ -51,6 +116,7 @@ private: ConfirmPhoneBox(QWidget*, const QString &phone, const QString &hash); friend class object_ptr; + void sendCall(); void checkPhoneAndHash(); void sendCodeDone(const MTPauth_SentCode &result); @@ -66,19 +132,6 @@ private: } void launch(); - enum CallState { - Waiting, - Calling, - Called, - Disabled, - }; - struct CallStatus { - CallState state; - int timeout; - }; - void setCallStatus(const CallStatus &status); - QString getCallText() const; - void showError(const QString &error); mtpRequestId _sendCodeRequestId = 0; @@ -94,13 +147,9 @@ private: mtpRequestId _checkCodeRequestId = 0; object_ptr _about = { nullptr }; - object_ptr _code = { nullptr }; + object_ptr _code = { nullptr }; - // Flag for not calling onTextChanged() recursively. - bool _fixing = false; QString _error; - - CallStatus _callStatus; - object_ptr _callTimer; + SentCodeCall _call; }; diff --git a/Telegram/SourceFiles/settings/settings_info_widget.cpp b/Telegram/SourceFiles/settings/settings_info_widget.cpp index 40fe4a185..b0881eed6 100644 --- a/Telegram/SourceFiles/settings/settings_info_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_info_widget.cpp @@ -25,6 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "ui/widgets/labels.h" #include "ui/effects/widget_slide_wrap.h" #include "boxes/usernamebox.h" +#include "boxes/change_phone_box.h" #include "observer_peer.h" namespace Settings { @@ -68,6 +69,12 @@ void InfoWidget::refreshMobileNumber() { } } setLabeledText(_mobileNumber, lang(lng_profile_mobile_number), phoneText, TextWithEntities(), lang(lng_profile_copy_phone)); + if (auto text = _mobileNumber->entity()->textLabel()) { + text->setRichText(textcmdLink(1, phoneText.text)); + text->setLink(1, MakeShared([] { + Ui::show(Box()); + })); + } } void InfoWidget::refreshUsername() { diff --git a/Telegram/SourceFiles/ui/effects/widget_fade_wrap.cpp b/Telegram/SourceFiles/ui/effects/widget_fade_wrap.cpp index f9d0f8665..fb333dddc 100644 --- a/Telegram/SourceFiles/ui/effects/widget_fade_wrap.cpp +++ b/Telegram/SourceFiles/ui/effects/widget_fade_wrap.cpp @@ -163,16 +163,23 @@ WidgetFadeWrap::WidgetFadeWrap(QWidget *parent , _updateCallback(std::move(updateCallback)) , _animation(this, scaled) { _animation.show(); - if (_updateCallback) { - _animation.setFinishedCallback([this] { _updateCallback(); }); - _animation.setUpdatedCallback([this](float64 opacity) { _updateCallback(); }); - } + installCallbacks(); _entity->setParent(this); _entity->moveToLeft(0, 0); _entity->installEventFilter(this); resize(_entity->size()); } +void WidgetFadeWrap::installCallbacks() { + if (_updateCallback) { + _animation.setFinishedCallback([this] { _updateCallback(); }); + _animation.setUpdatedCallback([this](float64 opacity) { _updateCallback(); }); + } else { + _animation.setFinishedCallback(base::lambda()); + _animation.setUpdatedCallback(base::lambda()); + } +} + bool WidgetFadeWrap::eventFilter(QObject *object, QEvent *event) { if (object == _entity && event->type() == QEvent::Resize) { resize(_entity->rect().size()); diff --git a/Telegram/SourceFiles/ui/effects/widget_fade_wrap.h b/Telegram/SourceFiles/ui/effects/widget_fade_wrap.h index c370fc913..275a0f706 100644 --- a/Telegram/SourceFiles/ui/effects/widget_fade_wrap.h +++ b/Telegram/SourceFiles/ui/effects/widget_fade_wrap.h @@ -136,12 +136,18 @@ public: bool animating() const { return _animation.animating(); } + void setUpdateCallback(base::lambda callback) { + _updateCallback = std::move(callback); + installCallbacks(); + } protected: bool eventFilter(QObject *object, QEvent *event) override; void paintEvent(QPaintEvent *e) override; private: + void installCallbacks(); + object_ptr _entity; int _duration; base::lambda _updateCallback; diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index df2b9a87e..8aeb5b9df 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -10,6 +10,8 @@ <(src_loc)/boxes/backgroundbox.h <(src_loc)/boxes/calendarbox.cpp <(src_loc)/boxes/calendarbox.h +<(src_loc)/boxes/change_phone_box.cpp +<(src_loc)/boxes/change_phone_box.h <(src_loc)/boxes/confirmbox.cpp <(src_loc)/boxes/confirmbox.h <(src_loc)/boxes/confirmphonebox.cpp