From a2dabfde569a345a31462cd8b3a01187fe93dff8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 29 Mar 2018 23:49:31 +0400 Subject: [PATCH] Separate form controller from view controller. --- Telegram/SourceFiles/boxes/abstract_box.h | 11 +- .../SourceFiles/boxes/add_contact_box.cpp | 6 +- Telegram/SourceFiles/boxes/confirm_box.cpp | 6 +- Telegram/SourceFiles/boxes/edit_color_box.cpp | 6 +- .../boxes/peer_list_controllers.cpp | 6 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 4 +- Telegram/SourceFiles/boxes/stickers_box.cpp | 4 +- .../dialogs_search_from_controllers.cpp | 6 +- .../passport/passport_edit_identity_box.cpp | 4 +- .../passport/passport_edit_identity_box.h | 6 +- .../passport/passport_form_box.cpp | 16 +- .../SourceFiles/passport/passport_form_box.h | 6 +- .../passport/passport_form_controller.cpp | 305 ++++++++---------- .../passport/passport_form_controller.h | 219 +++++++------ .../passport/passport_form_view_controller.h | 24 ++ .../passport/passport_form_view_separate.cpp | 248 ++++++++++++++ .../passport/passport_form_view_separate.h | 86 +++++ .../SourceFiles/settings/settings_cover.cpp | 4 +- Telegram/gyp/telegram_sources.txt | 3 + 19 files changed, 668 insertions(+), 302 deletions(-) create mode 100644 Telegram/SourceFiles/passport/passport_form_view_controller.h create mode 100644 Telegram/SourceFiles/passport/passport_form_view_separate.cpp create mode 100644 Telegram/SourceFiles/passport/passport_form_view_separate.h diff --git a/Telegram/SourceFiles/boxes/abstract_box.h b/Telegram/SourceFiles/boxes/abstract_box.h index 92475fdbe..7c2223286 100644 --- a/Telegram/SourceFiles/boxes/abstract_box.h +++ b/Telegram/SourceFiles/boxes/abstract_box.h @@ -94,7 +94,12 @@ public: setFocus(); } - base::Observable boxClosing; + rpl::producer<> boxClosing() const { + return _boxClosingStream.events(); + } + void notifyBoxClosing() { + _boxClosingStream.fire({}); + } void setDelegate(BoxContentDelegate *newDelegate) { _delegate = newDelegate; @@ -201,6 +206,8 @@ private: object_ptr _draggingScrollTimer = { nullptr }; int _draggingScrollDelta = 0; + rpl::event_stream<> _boxClosingStream; + }; class AbstractBox @@ -249,7 +256,7 @@ protected: _content->setInnerFocus(); } void closeHook() override { - _content->boxClosing.notify(true); + _content->notifyBoxClosing(); } private: diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index cce2b56a3..a295b613c 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -617,11 +617,13 @@ void SetupChannelBox::prepare() { rtlupdate(_invitationLink); } })); - subscribe(boxClosing, [this] { + + boxClosing() | rpl::start_with_next([=] { if (!_existing) { AddParticipantsBoxController::Start(_channel); } - }); + }, lifetime()); + updateMaxHeight(); } diff --git a/Telegram/SourceFiles/boxes/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp index adce3a044..dedbf6f1c 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/boxes/confirm_box.cpp @@ -111,11 +111,13 @@ void ConfirmBox::prepare() { if (!_informative) { addButton([this] { return _cancelText; }, [this] { _cancelled = true; closeBox(); }); } - subscribe(boxClosing, [this] { + + boxClosing() | rpl::start_with_next([=] { if (!_confirmed && (!_strictCancel || _cancelled) && _cancelledCallback) { _cancelledCallback(); } - }); + }, lifetime()); + textUpdated(); } diff --git a/Telegram/SourceFiles/boxes/edit_color_box.cpp b/Telegram/SourceFiles/boxes/edit_color_box.cpp index bc8975122..4cc85aba6 100644 --- a/Telegram/SourceFiles/boxes/edit_color_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_color_box.cpp @@ -659,11 +659,13 @@ void EditColorBox::prepare() { subscribe(_picker->changed(), [=] { updateFromControls(); }); subscribe(_hueSlider->changed(), [=] { updateFromControls(); }); subscribe(_opacitySlider->changed(), [=] { updateFromControls(); }); - subscribe(boxClosing, [=] { + + boxClosing() | rpl::start_with_next([=] { if (_cancelCallback) { _cancelCallback(); } - }); + }, lifetime()); + updateFromControls(); } diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index 43f494d28..80bfbd3fc 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -507,7 +507,7 @@ void AddParticipantsBoxController::Start( base::flat_set> &&alreadyIn, bool justCreated) { auto initBox = [channel, justCreated](not_null box) { - auto subscription = std::make_shared(); + auto subscription = std::make_shared(); box->addButton(langFactory(lng_participant_invite), [box, channel, subscription] { auto rows = box->peerListCollectSelectedRows(); if (!rows.empty()) { @@ -528,9 +528,9 @@ void AddParticipantsBoxController::Start( }); box->addButton(langFactory(justCreated ? lng_create_group_skip : lng_cancel), [box] { box->closeBox(); }); if (justCreated) { - *subscription = box->boxClosing.add_subscription([channel] { + box->boxClosing() | rpl::start_with_next([=] { Ui::showPeerHistory(channel, ShowAtTheEndMsgId); - }); + }, *subscription); } }; Ui::show(Box(std::make_unique(channel, std::move(alreadyIn)), std::move(initBox))); diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 9386c3cc6..02cd2382a 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -1431,11 +1431,11 @@ void SendFilesBox::prepare() { setupCaption(); initSendWay(); preparePreview(); - subscribe(boxClosing, [this] { + boxClosing() | rpl::start_with_next([=] { if (!_confirmed && _cancelledCallback) { _cancelledCallback(); } - }); + }, lifetime()); } void SendFilesBox::initSendWay() { diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index 0ac822869..2965763a0 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -274,7 +274,9 @@ void StickersBox::prepare() { if (_installed.widget()) { connect(_installed.widget(), SIGNAL(draggingScrollDelta(int)), this, SLOT(onDraggingScrollDelta(int))); if (!_megagroupSet) { - subscribe(boxClosing, [this] { saveChanges(); }); + boxClosing() | rpl::start_with_next([=] { + saveChanges(); + }, lifetime()); } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp index 2d54a9497..b4f9267c1 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp @@ -41,11 +41,13 @@ void ShowSearchFromBox( return nullptr; }; if (auto controller = createController()) { - auto subscription = std::make_shared(); + auto subscription = std::make_shared(); auto box = Ui::show(Box(std::move(controller), [subscription](not_null box) { box->addButton(langFactory(lng_cancel), [box, subscription] { box->closeBox(); }); }), LayerOption::KeepOther); - *subscription = box->boxClosing.add_subscription(std::move(closedCallback)); + box->boxClosing() | rpl::start_with_next( + std::move(closedCallback), + *subscription); } } diff --git a/Telegram/SourceFiles/passport/passport_edit_identity_box.cpp b/Telegram/SourceFiles/passport/passport_edit_identity_box.cpp index 9ae9df40d..e23913ad2 100644 --- a/Telegram/SourceFiles/passport/passport_edit_identity_box.cpp +++ b/Telegram/SourceFiles/passport/passport_edit_identity_box.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "passport/passport_edit_identity_box.h" -#include "passport/passport_form_controller.h" +#include "passport/passport_form_view_separate.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/buttons.h" #include "ui/text_options.h" @@ -142,7 +142,7 @@ void ScanButton::paintEvent(QPaintEvent *e) { IdentityBox::IdentityBox( QWidget*, - not_null controller, + not_null controller, int valueIndex, const IdentityData &data, std::vector &&files) diff --git a/Telegram/SourceFiles/passport/passport_edit_identity_box.h b/Telegram/SourceFiles/passport/passport_edit_identity_box.h index 74a215883..343f4677a 100644 --- a/Telegram/SourceFiles/passport/passport_edit_identity_box.h +++ b/Telegram/SourceFiles/passport/passport_edit_identity_box.h @@ -16,7 +16,7 @@ class InputField; namespace Passport { -class FormController; +class ViewSeparate; struct ScanInfo; class ScanButton; @@ -29,7 +29,7 @@ class IdentityBox : public BoxContent { public: IdentityBox( QWidget*, - not_null controller, + not_null controller, int valueIndex, const IdentityData &data, std::vector &&files); @@ -49,7 +49,7 @@ private: void updateControlsPosition(); void save(); - not_null _controller; + not_null _controller; int _valueIndex = -1; std::vector _files; diff --git a/Telegram/SourceFiles/passport/passport_form_box.cpp b/Telegram/SourceFiles/passport/passport_form_box.cpp index 77226bc75..96f11157d 100644 --- a/Telegram/SourceFiles/passport/passport_form_box.cpp +++ b/Telegram/SourceFiles/passport/passport_form_box.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "passport/passport_form_box.h" -#include "passport/passport_form_controller.h" +#include "passport/passport_form_view_separate.h" #include "passport/passport_form_row.h" #include "lang/lang_keys.h" #include "ui/widgets/input_fields.h" @@ -22,7 +22,7 @@ namespace Passport { class FormBox::CheckWidget : public Ui::RpWidget { public: - CheckWidget(QWidget *parent, not_null controller); + CheckWidget(QWidget *parent, not_null controller); void setInnerFocus(); void submit(); @@ -34,7 +34,7 @@ private: void showError(const QString &error); void hideError(); - not_null _controller; + not_null _controller; object_ptr _password; object_ptr _hint = { nullptr }; @@ -46,7 +46,7 @@ private: class FormBox::Inner : public Ui::RpWidget { public: - Inner(QWidget *parent, not_null controller); + Inner(QWidget *parent, not_null controller); void refresh(); @@ -56,14 +56,14 @@ protected: void paintEvent(QPaintEvent *e) override; private: - not_null _controller; + not_null _controller; std::vector> _rows; }; FormBox::CheckWidget::CheckWidget( QWidget *parent, - not_null controller) + not_null controller) : RpWidget(parent) , _controller(controller) , _password( @@ -157,7 +157,7 @@ int FormBox::CheckWidget::resizeGetHeight(int newWidth) { FormBox::Inner::Inner( QWidget *parent, - not_null controller) + not_null controller) : RpWidget(parent) , _controller(controller) { refresh(); @@ -198,7 +198,7 @@ void FormBox::Inner::paintEvent(QPaintEvent *e) { } -FormBox::FormBox(QWidget*, not_null controller) +FormBox::FormBox(QWidget*, not_null controller) : _controller(controller) { } diff --git a/Telegram/SourceFiles/passport/passport_form_box.h b/Telegram/SourceFiles/passport/passport_form_box.h index 67ceb36d1..5f4e2ab84 100644 --- a/Telegram/SourceFiles/passport/passport_form_box.h +++ b/Telegram/SourceFiles/passport/passport_form_box.h @@ -11,11 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Passport { -class FormController; +class ViewSeparate; class FormBox : public BoxContent { public: - FormBox(QWidget*, not_null controller); + FormBox(QWidget*, not_null controller); protected: void prepare() override; @@ -29,7 +29,7 @@ private: void showForm(); void submitForm(); - not_null _controller; + not_null _controller; object_ptr _innerCached = { nullptr }; QPointer _passwordCheck; QPointer _inner; diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index 2f7b96525..e6062cb3e 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "passport/passport_form_box.h" #include "passport/passport_edit_identity_box.h" #include "passport/passport_encryption.h" +#include "passport/passport_form_view_separate.h" #include "boxes/confirm_box.h" #include "lang/lang_keys.h" #include "base/openssl_help.h" @@ -42,26 +43,57 @@ FormRequest::FormRequest( , publicKey(publicKey) { } -FormController::UploadScanData::~UploadScanData() { - if (fullId) { - Auth().uploader().cancel(fullId); - } -} - -FormController::EditFile::EditFile( +EditFile::EditFile( const File &fields, std::unique_ptr &&uploadData) : fields(std::move(fields)) -, uploadData(std::move(uploadData)) { +, uploadData(std::move(uploadData)) +, guard(std::make_shared(true)) { } -FormController::Value::Value(Type type) : type(type) { +UploadScanDataPointer::UploadScanDataPointer( + std::unique_ptr &&value) +: _value(std::move(value)) { +} + +UploadScanDataPointer::UploadScanDataPointer( + UploadScanDataPointer &&other) = default; + +UploadScanDataPointer &UploadScanDataPointer::operator=( + UploadScanDataPointer &&other) = default; + +UploadScanDataPointer::~UploadScanDataPointer() { + if (const auto value = _value.get()) { + if (const auto fullId = value->fullId) { + Auth().uploader().cancel(fullId); + } + } +} + +UploadScanData *UploadScanDataPointer::get() const { + return _value.get(); +} + +UploadScanDataPointer::operator UploadScanData*() const { + return _value.get(); +} + +UploadScanDataPointer::operator bool() const { + return _value.get(); +} + +UploadScanData *UploadScanDataPointer::operator->() const { + return _value.get(); +} + +Value::Value(Type type) : type(type) { } FormController::FormController( not_null controller, const FormRequest &request) : _controller(controller) +, _view(std::make_unique(this)) , _request(request) { } @@ -70,6 +102,10 @@ void FormController::show() { requestPassword(); } +UserData *FormController::bot() const { + return _bot; +} + bytes::vector FormController::passwordHashForAuth( bytes::const_span password) const { return openssl::Sha256(bytes::concatenate( @@ -214,7 +250,6 @@ QString FormController::passwordHint() const { } void FormController::uploadScan(int valueIndex, QByteArray &&content) { - Expects(_editBox != nullptr); Expects(valueIndex >= 0 && valueIndex < _form.rows.size()); auto &value = _form.rows[valueIndex]; @@ -232,7 +267,7 @@ void FormController::uploadScan(int valueIndex, QByteArray &&content) { file.fields.image = ReadImage(bytes::make_span(content)); file.fields.downloadOffset = file.fields.size; - _scanUpdated.fire(collectScanInfo(file)); + _scanUpdated.fire(&file); encryptScan(valueIndex, fileIndex, std::move(content)); } @@ -241,19 +276,18 @@ void FormController::encryptScan( int valueIndex, int fileIndex, QByteArray &&content) { - Expects(_editBox != nullptr); Expects(valueIndex >= 0 && valueIndex < _form.rows.size()); Expects(fileIndex >= 0 && fileIndex < _form.rows[valueIndex].filesInEdit.size()); const auto &value = _form.rows[valueIndex]; - const auto &file = value.filesInEdit[fileIndex].fields; - const auto weak = _editBox; + const auto &file = value.filesInEdit[fileIndex]; + const auto weak = std::weak_ptr(file.guard); crl::async([ =, - fileId = file.id, + fileId = file.fields.id, bytes = std::move(content), - fileSecret = file.secret + fileSecret = file.fields.secret ] { auto data = EncryptData( bytes::make_span(bytes), @@ -268,7 +302,7 @@ void FormController::encryptScan( result.bytes.size(), result.md5checksum.data()); crl::on_main([=, encrypted = std::move(result)]() mutable { - if (weak) { + if (weak.lock()) { uploadEncryptedScan( valueIndex, fileIndex, @@ -287,7 +321,7 @@ void FormController::deleteScan( auto &file = _form.rows[valueIndex].filesInEdit[fileIndex]; file.deleted = !file.deleted; - _scanUpdated.fire(collectScanInfo(file)); + _scanUpdated.fire(&file); } void FormController::subscribeToUploader() { @@ -317,7 +351,6 @@ void FormController::uploadEncryptedScan( int valueIndex, int fileIndex, UploadScanData &&data) { - Expects(_editBox != nullptr); Expects(valueIndex >= 0 && valueIndex < _form.rows.size()); Expects(fileIndex >= 0 && fileIndex < _form.rows[valueIndex].filesInEdit.size()); @@ -357,7 +390,7 @@ void FormController::scanUploadDone(const Storage::UploadSecureDone &data) { file->fields.hash); file->uploadData->fullId = FullMsgId(); - _scanUpdated.fire(collectScanInfo(*file)); + _scanUpdated.fire(file); } } @@ -368,7 +401,7 @@ void FormController::scanUploadProgress( file->uploadData->offset = data.offset; - _scanUpdated.fire(collectScanInfo(*file)); + _scanUpdated.fire(file); } } @@ -378,7 +411,7 @@ void FormController::scanUploadFail(const FullMsgId &fullId) { file->uploadData->offset = -1; - _scanUpdated.fire(collectScanInfo(*file)); + _scanUpdated.fire(file); } } @@ -397,46 +430,16 @@ QString FormController::defaultPhoneNumber() const { return QString(); } -rpl::producer FormController::scanUpdated() const { +rpl::producer> FormController::scanUpdated() const { return _scanUpdated.events(); } -void FormController::fillRows( - base::lambda callback) { - for (const auto &value : _form.rows) { - switch (value.type) { - case Value::Type::Identity: - callback( - lang(lng_passport_identity_title), - lang(lng_passport_identity_description), - false); - break; - case Value::Type::Address: - callback( - lang(lng_passport_address_title), - lang(lng_passport_address_description), - false); - break; - case Value::Type::Phone: - callback( - lang(lng_passport_phone_title), - App::self()->phone(), - true); - break; - case Value::Type::Email: - callback( - lang(lng_passport_email_title), - lang(lng_passport_email_description), - false); - break; - } - } +void FormController::enumerateRows( + base::lambda callback) { + ranges::for_each(_form.rows, callback); } -void FormController::editValue(int index) { +not_null FormController::startValueEdit(int index) { Expects(index >= 0 && index < _form.rows.size()); auto &value = _form.rows[index]; @@ -447,20 +450,7 @@ void FormController::editValue(int index) { return EditFile(file, nullptr); }) | ranges::to_vector; - auto box = [&]() -> object_ptr { - switch (value.type) { - case Value::Type::Identity: - return Box( - this, - index, - valueDataIdentity(value), - valueFilesIdentity(value)); - } - return { nullptr }; - }(); - if (box) { - _editBox = Ui::show(std::move(box), LayerOption::KeepOther); - } + return &value; } void FormController::loadFiles(std::vector &files) { @@ -520,7 +510,7 @@ void FormController::fileLoadDone(FileKey key, const QByteArray &bytes) { if (const auto fileInEdit = findEditFile(key)) { fileInEdit->fields.image = file->image; fileInEdit->fields.downloadOffset = file->downloadOffset; - _scanUpdated.fire(collectScanInfo(*fileInEdit)); + _scanUpdated.fire(fileInEdit); } } } @@ -530,7 +520,7 @@ void FormController::fileLoadProgress(FileKey key, int offset) { file->downloadOffset = offset; if (const auto fileInEdit = findEditFile(key)) { fileInEdit->fields.downloadOffset = file->downloadOffset; - _scanUpdated.fire(collectScanInfo(*fileInEdit)); + _scanUpdated.fire(fileInEdit); } } } @@ -540,95 +530,43 @@ void FormController::fileLoadFail(FileKey key) { file->downloadOffset = -1; if (const auto fileInEdit = findEditFile(key)) { fileInEdit->fields.downloadOffset = file->downloadOffset; - _scanUpdated.fire(collectScanInfo(*fileInEdit)); + _scanUpdated.fire(fileInEdit); } } } -ScanInfo FormController::collectScanInfo(const EditFile &file) const { - const auto status = [&] { - if (file.deleted) { - return QString("deleted"); - } else if (file.fields.accessHash) { - if (file.fields.downloadOffset < 0) { - return QString("download failed"); - } else if (file.fields.downloadOffset < file.fields.size) { - return QString("downloading %1 / %2" - ).arg(file.fields.downloadOffset - ).arg(file.fields.size); - } else { - return QString("uploaded ") - + langDateTimeFull(ParseDateTime(file.fields.date)); - } - } else if (file.uploadData) { - if (file.uploadData->offset < 0) { - return QString("upload failed"); - } else if (file.uploadData->fullId) { - return QString("uploading %1 / %2" - ).arg(file.uploadData->offset - ).arg(file.uploadData->bytes.size()); - } else { - return QString("upload ready"); - } - } else { - return QString("preparing"); - } - }(); - return { - FileKey{ file.fields.id, file.fields.dcId }, - status, - file.fields.image }; -} - -IdentityData FormController::valueDataIdentity(const Value &value) const { - const auto &map = value.data.parsed; - auto result = IdentityData(); - if (const auto i = map.find(qsl("first_name")); i != map.cend()) { - result.name = i->second; - } - if (const auto i = map.find(qsl("last_name")); i != map.cend()) { - result.surname = i->second; - } - return result; -} - -std::vector FormController::valueFilesIdentity( - const Value &value) const { - auto result = std::vector(); - for (const auto &file : value.filesInEdit) { - result.push_back(collectScanInfo(file)); - } - return result; -} - -void FormController::saveValueIdentity( - int index, - const IdentityData &data) { - Expects(_editBox != nullptr); +void FormController::cancelValueEdit(int index) { Expects(index >= 0 && index < _form.rows.size()); - Expects(_form.rows[index].type == Value::Type::Identity); - _form.rows[index].data.parsed[qsl("first_name")] = data.name; - _form.rows[index].data.parsed[qsl("last_name")] = data.surname; - - saveIdentity(index); + _form.rows[index].filesInEdit.clear(); } -void FormController::saveIdentity(int index) { - Expects(index >= 0 && index < _form.rows.size()); - Expects(_form.rows[index].type == Value::Type::Identity); +bool FormController::isEncryptedValue(Value::Type type) const { + return (type == Value::Type::Identity || type == Value::Type::Address); +} +void FormController::saveValueEdit(int index) { + Expects(index >= 0 && index < _form.rows.size()); + + if (isEncryptedValue(_form.rows[index].type)) { + saveEncryptedValue(index); + } else { + savePlainTextValue(index); + } +} + +void FormController::saveEncryptedValue(int index) { + Expects(index >= 0 && index < _form.rows.size()); + Expects(isEncryptedValue(_form.rows[index].type)); + + auto &value = _form.rows[index]; if (_secret.empty()) { _secretCallbacks.push_back([=] { - saveIdentity(index); + saveEncryptedValue(index); }); return; } - _editBox->closeBox(); - - auto &value = _form.rows[index]; - auto inputFiles = QVector(); inputFiles.reserve(value.filesInEdit.size()); for (const auto &file : value.filesInEdit) { @@ -674,15 +612,52 @@ void FormController::saveIdentity(int index) { value.data.encryptedSecret, bytes::concatenate(fileHashesSecrets))); + const auto wrap = [&] { + switch (value.type) { + case Value::Type::Identity: return MTP_inputSecureValueIdentity; + case Value::Type::Address: return MTP_inputSecureValueAddress; + } + Unexpected("Value type in saveEncryptedValue()."); + }(); + sendSaveRequest(index, wrap( + MTP_secureData( + MTP_bytes(encryptedData.bytes), + MTP_bytes(value.data.hash), + MTP_bytes(value.data.encryptedSecret)), + MTP_vector(inputFiles), + MTP_bytes(value.consistencyHash))); +} + +void FormController::savePlainTextValue(int index) { + Expects(index >= 0 && index < _form.rows.size()); + Expects(!isEncryptedValue(_form.rows[index].type)); + + auto &value = _form.rows[index]; + const auto text = value.data.parsed[QString("value")]; + QVector(); + value.consistencyHash = openssl::Sha256( + bytes::make_span(text.toUtf8())); + + const auto wrap = [&] { + switch (value.type) { + case Value::Type::Phone: return MTP_inputSecureValuePhone; + case Value::Type::Email: return MTP_inputSecureValueEmail; + } + Unexpected("Value type in savePlainTextValue()."); + }(); + sendSaveRequest(index, wrap( + MTP_string(text), + MTP_bytes(value.consistencyHash))); +} + +void FormController::sendSaveRequest( + int index, + const MTPInputSecureValue &value) { + Expects(index >= 0 && index < _form.rows.size()); + request(MTPaccount_SaveSecureValue( - MTP_inputSecureValueIdentity( - MTP_secureData( - MTP_bytes(encryptedData.bytes), - MTP_bytes(value.data.hash), - MTP_bytes(value.data.encryptedSecret)), - MTP_vector(inputFiles), - MTP_bytes(value.consistencyHash)), - MTP_long(CountSecureSecretHash(_secret)) + value, + MTP_long(_secretId) )).done([=](const MTPSecureValueSaved &result) { Expects(result.type() == mtpc_secureValueSaved); @@ -844,21 +819,21 @@ void FormController::fillDownloadedFile( template auto FormController::parsePlainTextValue( Value::Type type, - const QByteArray &value, + const QByteArray &text, const DataType &data) const -> Value { auto result = Value(type); const auto check = bytes::compare( bytes::make_span(data.vhash.v), - openssl::Sha256(bytes::make_span(value))); + openssl::Sha256(bytes::make_span(text))); if (check != 0) { LOG(("API Error: Bad hash for plain text value. " "Value '%1', hash '%2'" - ).arg(QString::fromUtf8(value) + ).arg(QString::fromUtf8(text) ).arg(Logs::mb(data.vhash.v.data(), data.vhash.v.size()).str() )); return result; } - result.data.parsed[QString("value")] = QString::fromUtf8(value); + result.data.parsed[QString("value")] = QString::fromUtf8(text); if (data.has_verified()) { result.verification = parseVerified(data.vverified); } @@ -942,7 +917,7 @@ auto FormController::findFile(const FileKey &key) void FormController::formDone(const MTPaccount_AuthorizationForm &result) { parseForm(result); if (!_passwordRequestId) { - showForm(); + _view->showForm(); } } @@ -987,14 +962,6 @@ void FormController::parseForm(const MTPaccount_AuthorizationForm &result) { _bot = App::userLoaded(_request.botId); } -void FormController::showForm() { - if (!_bot) { - Ui::show(Box("Could not get authorization bot.")); - return; - } - Ui::show(Box(this)); -} - void FormController::formFail(const RPCError &error) { Ui::show(Box(lang(lng_passport_form_error))); } @@ -1020,7 +987,7 @@ void FormController::passwordDone(const MTPaccount_Password &result) { break; } if (!_formRequestId) { - showForm(); + _view->showForm(); } } diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h index c1db98a40..9cf09cc92 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_controller.h @@ -23,6 +23,8 @@ class Controller; namespace Passport { +class ViewController; + struct FormRequest { FormRequest( UserId botId, @@ -37,7 +39,104 @@ struct FormRequest { }; -struct IdentityData; +struct UploadScanData { + FullMsgId fullId; + uint64 fileId = 0; + int partsCount = 0; + QByteArray md5checksum; + bytes::vector hash; + bytes::vector bytes; + + int offset = 0; +}; + +class UploadScanDataPointer { +public: + UploadScanDataPointer(std::unique_ptr &&value); + UploadScanDataPointer(UploadScanDataPointer &&other); + UploadScanDataPointer &operator=(UploadScanDataPointer &&other); + ~UploadScanDataPointer(); + + UploadScanData *get() const; + operator UploadScanData*() const; + explicit operator bool() const; + UploadScanData *operator->() const; + +private: + std::unique_ptr _value; + +}; + +struct File { + uint64 id = 0; + uint64 accessHash = 0; + int32 size = 0; + int32 dcId = 0; + TimeId date = 0; + bytes::vector hash; + bytes::vector secret; + bytes::vector encryptedSecret; + + int downloadOffset = 0; + QImage image; +}; + +struct EditFile { + EditFile( + const File &fields, + std::unique_ptr &&uploadData); + + File fields; + UploadScanDataPointer uploadData; + std::shared_ptr guard; + bool deleted = false; +}; + +struct Verification { + TimeId date; +}; + +struct ValueData { + QByteArray original; + std::map parsed; + bytes::vector hash; + bytes::vector secret; + bytes::vector encryptedSecret; +}; + +struct Value { + enum class Type { + Identity, + Address, + Phone, + Email, + }; + + explicit Value(Type type); + Value(Value &&other) = default; + Value &operator=(Value &&other) = default; + + Type type; + ValueData data; + std::vector files; + std::vector filesInEdit; + bytes::vector consistencyHash; + base::optional verification; +}; + +struct Form { + std::vector rows; +}; + +struct PasswordSettings { + bytes::vector salt; + bytes::vector newSalt; + bytes::vector newSecureSalt; + QString hint; + QString unconfirmedPattern; + QString confirmedEmail; + bool hasRecovery = false; +}; struct FileKey { uint64 id = 0; @@ -64,13 +163,6 @@ struct FileKey { }; -struct ScanInfo { - FileKey key; - QString status; - QImage thumb; - -}; - class FormController : private MTP::Sender, public base::has_weak_ptr { public: FormController( @@ -78,6 +170,7 @@ public: const FormRequest &request); void show(); + UserData *bot() const; void submitPassword(const QString &password); rpl::producer passwordError() const; @@ -90,96 +183,21 @@ public: QString defaultEmail() const; QString defaultPhoneNumber() const; - rpl::producer scanUpdated() const; - void fillRows( - base::lambda callback); - void editValue(int index); + rpl::producer> scanUpdated() const; - void saveValueIdentity(int index, const IdentityData &data); + void enumerateRows(base::lambda callback); + not_null startValueEdit(int index); + void cancelValueEdit(int index); + void saveValueEdit(int index); + + rpl::lifetime &lifetime() { + return _lifetime; + } ~FormController(); private: - struct UploadScanData { - ~UploadScanData(); - - FullMsgId fullId; - uint64 fileId = 0; - int partsCount = 0; - QByteArray md5checksum; - bytes::vector hash; - bytes::vector bytes; - - int offset = 0; - }; - struct File { - uint64 id = 0; - uint64 accessHash = 0; - int32 size = 0; - int32 dcId = 0; - TimeId date = 0; - bytes::vector hash; - bytes::vector secret; - bytes::vector encryptedSecret; - - int downloadOffset = 0; - QImage image; - }; - struct EditFile { - EditFile( - const File &fields, - std::unique_ptr &&uploadData); - - File fields; - std::unique_ptr uploadData; - bool deleted = false; - }; - struct Verification { - TimeId date; - }; - struct ValueData { - QByteArray original; - std::map parsed; - bytes::vector hash; - bytes::vector secret; - bytes::vector encryptedSecret; - }; - struct Value { - enum class Type { - Identity, - Address, - Phone, - Email, - }; - - explicit Value(Type type); - Value(Value &&other) = default; - Value &operator=(Value &&other) = default; - - Type type; - ValueData data; - std::vector files; - std::vector filesInEdit; - bytes::vector consistencyHash; - base::optional verification; - }; - struct Form { - std::vector rows; - }; - struct PasswordSettings { - bytes::vector salt; - bytes::vector newSalt; - bytes::vector newSecureSalt; - QString hint; - QString unconfirmedPattern; - QString confirmedEmail; - bool hasRecovery = false; - }; - EditFile *findEditFile(const FullMsgId &fullId); EditFile *findEditFile(const FileKey &key); std::pair findFile(const FileKey &key); @@ -223,10 +241,6 @@ private: bool validateValueSecrets(Value &value); void resetValue(Value &value); - IdentityData valueDataIdentity(const Value &value) const; - std::vector valueFilesIdentity(const Value &value) const; - void saveIdentity(int index); - void loadFiles(std::vector &files); void fileLoadDone(FileKey key, const QByteArray &bytes); void fileLoadProgress(FileKey key, int offset); @@ -245,9 +259,14 @@ private: void scanUploadDone(const Storage::UploadSecureDone &data); void scanUploadProgress(const Storage::UploadSecureProgress &data); void scanUploadFail(const FullMsgId &fullId); - ScanInfo collectScanInfo(const EditFile &file) const; + + bool isEncryptedValue(Value::Type type) const; + void saveEncryptedValue(int index); + void savePlainTextValue(int index); + void sendSaveRequest(int index, const MTPInputSecureValue &value); not_null _controller; + std::unique_ptr _view; FormRequest _request; UserData *_bot = nullptr; @@ -258,7 +277,8 @@ private: PasswordSettings _password; Form _form; std::map> _fileLoaders; - rpl::event_stream _scanUpdated; + + rpl::event_stream> _scanUpdated; bytes::vector _secret; uint64 _secretId = 0; @@ -267,9 +287,8 @@ private: rpl::event_stream<> _secretReady; rpl::event_stream _passwordError; - QPointer _editBox; - rpl::lifetime _uploaderSubscriptions; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/passport/passport_form_view_controller.h b/Telegram/SourceFiles/passport/passport_form_view_controller.h new file mode 100644 index 000000000..317df769f --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_form_view_controller.h @@ -0,0 +1,24 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Passport { + +struct Value; + +class ViewController { +public: + virtual void showForm() = 0; + virtual void editValue(int index) = 0; + + virtual ~ViewController() { + } + +}; + +} // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_form_view_separate.cpp b/Telegram/SourceFiles/passport/passport_form_view_separate.cpp new file mode 100644 index 000000000..6e6dd4f58 --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_form_view_separate.cpp @@ -0,0 +1,248 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "passport/passport_form_view_separate.h" + +#include "lang/lang_keys.h" +#include "passport/passport_edit_identity_box.h" +#include "passport/passport_form_box.h" +#include "boxes/confirm_box.h" + +namespace Passport { + +BoxPointer::BoxPointer(QPointer value) +: _value(value) { +} + +BoxPointer::BoxPointer(BoxPointer &&other) +: _value(base::take(other._value)) { +} + +BoxPointer &BoxPointer::operator=(BoxPointer &&other) { + std::swap(_value, other._value); + return *this; +} + +BoxPointer::~BoxPointer() { + if (const auto strong = get()) { + strong->closeBox(); + } +} + +BoxContent *BoxPointer::get() const { + return _value.data(); +} + +BoxPointer::operator BoxContent*() const { + return get(); +} + +BoxPointer::operator bool() const { + return get(); +} + +BoxContent *BoxPointer::operator->() const { + return get(); +} + +ViewSeparate::ViewSeparate(not_null form) +: _form(form) { +} + +not_null ViewSeparate::bot() const { + return _form->bot(); +} + +void ViewSeparate::fillRows( + base::lambda callback) { + _form->enumerateRows([&](const Value &value) { + switch (value.type) { + case Value::Type::Identity: + callback( + lang(lng_passport_identity_title), + lang(lng_passport_identity_description), + false); + break; + case Value::Type::Address: + callback( + lang(lng_passport_address_title), + lang(lng_passport_address_description), + false); + break; + case Value::Type::Phone: + callback( + lang(lng_passport_phone_title), + App::self()->phone(), + true); + break; + case Value::Type::Email: + callback( + lang(lng_passport_email_title), + lang(lng_passport_email_description), + false); + break; + } + }); +} + +void ViewSeparate::submitPassword(const QString &password) { + _form->submitPassword(password); +} + +rpl::producer ViewSeparate::passwordError() const { + return _form->passwordError(); +} + +QString ViewSeparate::passwordHint() const { + return _form->passwordHint(); +} + +rpl::producer<> ViewSeparate::secretReadyEvents() const { + return _form->secretReadyEvents(); +} + +QString ViewSeparate::defaultEmail() const { + return _form->defaultEmail(); +} + +QString ViewSeparate::defaultPhoneNumber() const { + return _form->defaultPhoneNumber(); +} + +void ViewSeparate::uploadScan(int valueIndex, QByteArray &&content) { + Expects(_editBox != nullptr); + + _form->uploadScan(valueIndex, std::move(content)); +} + +void ViewSeparate::deleteScan(int valueIndex, int fileIndex) { + Expects(_editBox != nullptr); + + _form->deleteScan(valueIndex, fileIndex); +} + +rpl::producer ViewSeparate::scanUpdated() const { + return _form->scanUpdated( + ) | rpl::map([=](not_null file) { + return collectScanInfo(*file); + }); +} + +ScanInfo ViewSeparate::collectScanInfo(const EditFile &file) const { + const auto status = [&] { + if (file.deleted) { + return QString("deleted"); + } else if (file.fields.accessHash) { + if (file.fields.downloadOffset < 0) { + return QString("download failed"); + } else if (file.fields.downloadOffset < file.fields.size) { + return QString("downloading %1 / %2" + ).arg(file.fields.downloadOffset + ).arg(file.fields.size); + } else { + return QString("uploaded ") + + langDateTimeFull(ParseDateTime(file.fields.date)); + } + } else if (file.uploadData) { + if (file.uploadData->offset < 0) { + return QString("upload failed"); + } else if (file.uploadData->fullId) { + return QString("uploading %1 / %2" + ).arg(file.uploadData->offset + ).arg(file.uploadData->bytes.size()); + } else { + return QString("upload ready"); + } + } else { + return QString("preparing"); + } + }(); + return { + FileKey{ file.fields.id, file.fields.dcId }, + status, + file.fields.image }; +} + +void ViewSeparate::showForm() { + if (!_form->bot()) { + Ui::show(Box("Could not get authorization bot.")); + return; + } + Ui::show(Box(this)); +} + +void ViewSeparate::editValue(int index) { + _editValue = _form->startValueEdit(index); + Assert(_editValue != nullptr); + + auto box = [&]() -> object_ptr { + switch (_editValue->type) { + case Value::Type::Identity: + return Box( + this, + index, + valueDataIdentity(*_editValue), + valueFiles(*_editValue)); + } + return { nullptr }; + }(); + if (box) { + _editBox = Ui::show(std::move(box), LayerOption::KeepOther); + _editBox->boxClosing() | rpl::start_with_next([=] { + cancelValueEdit(index); + }, _form->lifetime()); + } else { + cancelValueEdit(index); + } +} + +IdentityData ViewSeparate::valueDataIdentity(const Value &value) const { + const auto &map = value.data.parsed; + auto result = IdentityData(); + if (const auto i = map.find(qsl("first_name")); i != map.cend()) { + result.name = i->second; + } + if (const auto i = map.find(qsl("last_name")); i != map.cend()) { + result.surname = i->second; + } + return result; +} + +std::vector ViewSeparate::valueFiles(const Value &value) const { + auto result = std::vector(); + for (const auto &file : value.filesInEdit) { + result.push_back(collectScanInfo(file)); + } + return result; +} + +void ViewSeparate::cancelValueEdit(int index) { + if (base::take(_editValue)) { + _form->cancelValueEdit(index); + } +} + +void ViewSeparate::saveValueIdentity( + int index, + const IdentityData &data) { + Expects(_editBox != nullptr); + Expects(_editValue != nullptr); + Expects(_editValue->type == Value::Type::Identity); + + _editValue->data.parsed[qsl("first_name")] = data.name; + _editValue->data.parsed[qsl("last_name")] = data.surname; + _editValue = nullptr; + + _editBox->closeBox(); + + _form->saveValueEdit(index); +} + +} // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_form_view_separate.h b/Telegram/SourceFiles/passport/passport_form_view_separate.h new file mode 100644 index 000000000..eefca7e93 --- /dev/null +++ b/Telegram/SourceFiles/passport/passport_form_view_separate.h @@ -0,0 +1,86 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "passport/passport_form_view_controller.h" +#include "passport/passport_form_controller.h" + +namespace Passport { + +class FormController; + +struct IdentityData; + +struct ScanInfo { + FileKey key; + QString status; + QImage thumb; + +}; + +class BoxPointer { +public: + BoxPointer(QPointer value = nullptr); + BoxPointer(BoxPointer &&other); + BoxPointer &operator=(BoxPointer &&other); + ~BoxPointer(); + + BoxContent *get() const; + operator BoxContent*() const; + explicit operator bool() const; + BoxContent *operator->() const; + +private: + QPointer _value; + +}; + +class ViewSeparate : public ViewController { +public: + ViewSeparate(not_null form); + + not_null bot() const; + + void submitPassword(const QString &password); + rpl::producer passwordError() const; + QString passwordHint() const; + + void uploadScan(int valueIndex, QByteArray &&content); + void deleteScan(int valueIndex, int fileIndex); + rpl::producer scanUpdated() const; + + rpl::producer<> secretReadyEvents() const; + + QString defaultEmail() const; + QString defaultPhoneNumber() const; + + void showForm() override; + void fillRows( + base::lambda callback); + + void editValue(int index) override; + void saveValueIdentity(int index, const IdentityData &data); + +private: + void cancelValueEdit(int index); + IdentityData valueDataIdentity(const Value &value) const; + std::vector valueFiles(const Value &value) const; + + ScanInfo collectScanInfo(const EditFile &file) const; + + not_null _form; + + Value *_editValue = nullptr; + BoxPointer _editBox; + +}; + +} // namespace Passport diff --git a/Telegram/SourceFiles/settings/settings_cover.cpp b/Telegram/SourceFiles/settings/settings_cover.cpp index 5d988324a..2c34ce173 100644 --- a/Telegram/SourceFiles/settings/settings_cover.cpp +++ b/Telegram/SourceFiles/settings/settings_cover.cpp @@ -377,7 +377,9 @@ void CoverWidget::showSetPhotoBox(const QImage &img) { std::move(image), peer->id); }, box->lifetime()); - subscribe(box->boxClosing, [this] { onPhotoUploadStatusChanged(); }); + box->boxClosing() | rpl::start_with_next([=] { + onPhotoUploadStatusChanged(); + }, lifetime()); } void CoverWidget::onPhotoUploadStatusChanged(PeerId peerId) { diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index fdca343d9..24b43e4d8 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -464,6 +464,9 @@ <(src_loc)/passport/passport_form_controller.h <(src_loc)/passport/passport_form_row.cpp <(src_loc)/passport/passport_form_row.h +<(src_loc)/passport/passport_form_view_controller.h +<(src_loc)/passport/passport_form_view_separate.cpp +<(src_loc)/passport/passport_form_view_separate.h <(src_loc)/platform/linux/linux_desktop_environment.cpp <(src_loc)/platform/linux/linux_desktop_environment.h <(src_loc)/platform/linux/linux_gdk_helper.cpp