Save value without closing the passport panel.

This commit is contained in:
John Preston 2018-04-06 22:47:29 +04:00
parent d0e854e9d8
commit 35dcbe0aa0
10 changed files with 196 additions and 104 deletions

View File

@ -1520,8 +1520,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passport_phone_description" = "Enter your phone number";
"lng_passport_email_title" = "Email";
"lng_passport_email_description" = "Enter your email address";
"lng_passport_accept_allow" = "You accept {policy} and allow their {bot} to send messages to you.";
"lng_passport_allow" = "You allow {bot} to send messages to you.";
"lng_passport_accept_allow" = "You accept {policy} and allow their {bot} to send you messages.";
"lng_passport_allow" = "You allow {bot} to send you messages.";
"lng_passport_policy" = "{bot} privacy policy";
"lng_passport_authorize" = "Authorize";
"lng_passport_form_error" = "Could not get authorization form.";

View File

@ -836,6 +836,25 @@ bool Messenger::openLocalUrl(const QString &url) {
}
auto command = urlTrimmed.midRef(qstr("tg://").size());
const auto showPassportForm = [](const QMap<QString, QString> &params) {
if (const auto botId = params.value("bot_id", QString()).toInt()) {
const auto scope = params.value("scope", QString());
const auto callback = params.value("callback_url", QString());
const auto publicKey = params.value("public_key", QString());
if (const auto window = App::wnd()) {
if (const auto controller = window->controller()) {
controller->showPassportForm(Passport::FormRequest(
botId,
scope,
callback,
publicKey));
return true;
}
}
}
return false;
};
using namespace qthelp;
auto matchOptions = RegExOption::CaseInsensitive;
if (auto joinChatMatch = regex_match(qsl("^join/?\\?invite=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"), command, matchOptions)) {
@ -871,7 +890,9 @@ bool Messenger::openLocalUrl(const QString &url) {
if (auto main = App::main()) {
auto params = url_parse_params(usernameMatch->captured(1), UrlParamNameTransform::ToLower);
auto domain = params.value(qsl("domain"));
if (regex_match(qsl("^[a-zA-Z0-9\\.\\_]+$"), domain, matchOptions)) {
if (domain == qsl("telegrampassport")) {
return showPassportForm(params);
} else if (regex_match(qsl("^[a-zA-Z0-9\\.\\_]+$"), domain, matchOptions)) {
auto start = qsl("start");
auto startToken = params.value(start);
if (startToken.isEmpty()) {
@ -909,25 +930,10 @@ bool Messenger::openLocalUrl(const QString &url) {
auto params = url_parse_params(proxyMatch->captured(1), UrlParamNameTransform::ToLower);
ProxiesBoxController::ShowApplyConfirmation(ProxyData::Type::Mtproto, params);
return true;
} else if (auto authMatch = regex_match(qsl("^secureid/?\\?(.+)(#|$)"), command, matchOptions)) {
const auto params = url_parse_params(
} else if (auto authMatch = regex_match(qsl("^passport/?\\?(.+)(#|$)"), command, matchOptions)) {
return showPassportForm(url_parse_params(
authMatch->captured(1),
UrlParamNameTransform::ToLower);
if (const auto botId = params.value("bot_id", QString()).toInt()) {
const auto scope = params.value("scope", QString());
const auto callback = params.value("callback_url", QString());
const auto publicKey = params.value("public_key", QString());
if (const auto window = App::wnd()) {
if (const auto controller = window->controller()) {
controller->showAuthForm(Passport::FormRequest(
botId,
scope,
callback,
publicKey));
return true;
}
}
}
UrlParamNameTransform::ToLower));
}
return false;
}

View File

@ -200,36 +200,36 @@ void FormController::decryptValues() {
Expects(!_secret.empty());
for (auto &[type, value] : _form.values) {
if (value.data.original.isEmpty()) {
continue;
}
decryptValue(value);
}
}
void FormController::decryptValue(Value &value) {
Expects(!_secret.empty());
Expects(!value.data.original.isEmpty());
if (!validateValueSecrets(value)) {
resetValue(value);
return;
}
value.data.parsed.fields = DeserializeData(DecryptData(
bytes::make_span(value.data.original),
value.data.hash,
value.data.secret));
if (!value.data.original.isEmpty()) {
value.data.parsed.fields = DeserializeData(DecryptData(
bytes::make_span(value.data.original),
value.data.hash,
value.data.secret));
}
}
bool FormController::validateValueSecrets(Value &value) {
value.data.secret = DecryptValueSecret(
value.data.encryptedSecret,
_secret,
value.data.hash);
if (value.data.secret.empty()) {
LOG(("API Error: Could not decrypt data secret. "
"Forgetting files and data :("));
return false;
if (!value.data.original.isEmpty()) {
value.data.secret = DecryptValueSecret(
value.data.encryptedSecret,
_secret,
value.data.hash);
if (value.data.secret.empty()) {
LOG(("API Error: Could not decrypt data secret. "
"Forgetting files and data :("));
return false;
}
}
for (auto &file : value.files) {
file.secret = DecryptValueSecret(
@ -451,6 +451,16 @@ auto FormController::scanUpdated() const
return _scanUpdated.events();
}
auto FormController::valueSaved() const
->rpl::producer<not_null<const Value*>> {
return _valueSaved.events();
}
auto FormController::verificationNeeded() const
->rpl::producer<not_null<const Value*>> {
return _verificationNeeded.events();
}
const Form &FormController::form() const {
return _form;
}
@ -465,6 +475,9 @@ not_null<Value*> FormController::findValue(not_null<const Value*> value) {
}
void FormController::startValueEdit(not_null<const Value*> value) {
if (value->saveRequestId) {
return;
}
const auto nonconst = findValue(value);
loadFiles(nonconst->files);
nonconst->filesInEdit = ranges::view::all(
@ -472,6 +485,7 @@ void FormController::startValueEdit(not_null<const Value*> value) {
) | ranges::view::transform([=](const File &file) {
return EditFile(value, file, nullptr);
}) | ranges::to_vector;
nonconst->data.parsedInEdit = nonconst->data.parsed;
}
void FormController::loadFiles(std::vector<File> &files) {
@ -557,8 +571,14 @@ void FormController::fileLoadFail(FileKey key) {
}
void FormController::cancelValueEdit(not_null<const Value*> value) {
if (value->saveRequestId) {
return;
}
const auto nonconst = findValue(value);
nonconst->filesInEdit.clear();
nonconst->data.encryptedSecretInEdit.clear();
nonconst->data.hashInEdit.clear();
nonconst->data.parsedInEdit = ValueMap();
}
bool FormController::isEncryptedValue(Value::Type type) const {
@ -568,8 +588,11 @@ bool FormController::isEncryptedValue(Value::Type type) const {
void FormController::saveValueEdit(
not_null<const Value*> value,
ValueMap &&data) {
if (value->saveRequestId) {
return;
}
const auto nonconst = findValue(value);
nonconst->data.parsed = std::move(data);
nonconst->data.parsedInEdit = std::move(data);
if (isEncryptedValue(nonconst->type)) {
saveEncryptedValue(nonconst);
@ -616,13 +639,13 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
value->data.secret = GenerateSecretBytes();
}
const auto encryptedData = EncryptData(
SerializeData(value->data.parsed.fields),
SerializeData(value->data.parsedInEdit.fields),
value->data.secret);
value->data.hash = encryptedData.hash;
value->data.encryptedSecret = EncryptValueSecret(
value->data.hashInEdit = encryptedData.hash;
value->data.encryptedSecretInEdit = EncryptValueSecret(
value->data.secret,
_secret,
value->data.hash);
value->data.hashInEdit);
const auto selfie = value->selfieInEdit
? inputFile(*value->selfieInEdit)
@ -650,7 +673,7 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
Unexpected("Value type in saveEncryptedValue().");
}();
const auto flags = ((value->filesInEdit.empty()
&& value->data.parsed.fields.empty())
&& value->data.parsedInEdit.fields.empty())
? MTPDinputSecureValue::Flag(0)
: MTPDinputSecureValue::Flag::f_data)
| (value->filesInEdit.empty()
@ -659,26 +682,24 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
| (value->selfieInEdit
? MTPDinputSecureValue::Flag::f_selfie
: MTPDinputSecureValue::Flag(0));
if (!flags) {
request(MTPaccount_DeleteSecureValue(MTP_vector<MTPSecureValueType>(1, type))).send();
} else {
sendSaveRequest(value, MTP_inputSecureValue(
MTP_flags(flags),
type,
MTP_secureData(
MTP_bytes(encryptedData.bytes),
MTP_bytes(value->data.hash),
MTP_bytes(value->data.encryptedSecret)),
MTP_vector<MTPInputSecureFile>(inputFiles),
MTPSecurePlainData(),
selfie));
}
Assert(flags != MTPDinputSecureValue::Flags(0));
sendSaveRequest(value, MTP_inputSecureValue(
MTP_flags(flags),
type,
MTP_secureData(
MTP_bytes(encryptedData.bytes),
MTP_bytes(value->data.hashInEdit),
MTP_bytes(value->data.encryptedSecretInEdit)),
MTP_vector<MTPInputSecureFile>(inputFiles),
MTPSecurePlainData(),
selfie));
}
void FormController::savePlainTextValue(not_null<Value*> value) {
Expects(!isEncryptedValue(value->type));
const auto text = value->data.parsed.fields[QString("value")];
const auto text = value->data.parsedInEdit.fields["value"];
const auto type = [&] {
switch (value->type) {
case Value::Type::Phone: return MTP_secureValueTypePhone();
@ -705,7 +726,9 @@ void FormController::savePlainTextValue(not_null<Value*> value) {
void FormController::sendSaveRequest(
not_null<Value*> value,
const MTPInputSecureValue &data) {
request(MTPaccount_SaveSecureValue(
Expects(value->saveRequestId == 0);
value->saveRequestId = request(MTPaccount_SaveSecureValue(
data,
MTP_long(_secretId)
)).done([=](const MTPSecureValue &result) {
@ -715,9 +738,27 @@ void FormController::sendSaveRequest(
value->files = parseFiles(
data.vfiles.v,
base::take(value->filesInEdit));
value->data.encryptedSecret = std::move(
value->data.encryptedSecretInEdit);
value->data.parsed = std::move(value->data.parsedInEdit);
value->data.hash = std::move(value->data.hashInEdit);
_view->show(Box<InformBox>("Saved"));
value->saveRequestId = 0;
_valueSaved.fire_copy(value);
}).fail([=](const RPCError &error) {
value->saveRequestId = 0;
if (error.type() == qstr("PHONE_VERIFICATION_NEEDED")) {
if (value->type == Value::Type::Phone) {
_verificationNeeded.fire_copy(value);
return;
}
} else if (error.type() == qstr("EMAIL_VERIFICATION_NEEDED")) {
if (value->type == Value::Type::Email) {
_verificationNeeded.fire_copy(value);
return;
}
}
_view->show(Box<InformBox>("Error saving value:\n" + error.type()));
}).send();
}
@ -1019,7 +1060,7 @@ void FormController::cancel() {
if (!_cancelled) {
_cancelled = true;
crl::on_main(this, [=] {
_controller->clearAuthForm();
_controller->clearPassportForm();
});
}
}

View File

@ -102,10 +102,13 @@ struct ValueMap {
struct ValueData {
QByteArray original;
bytes::vector secret;
ValueMap parsed;
bytes::vector hash;
bytes::vector secret;
bytes::vector encryptedSecret;
ValueMap parsedInEdit;
bytes::vector hashInEdit;
bytes::vector encryptedSecretInEdit;
};
struct Value {
@ -132,6 +135,8 @@ struct Value {
std::vector<EditFile> filesInEdit;
base::optional<File> selfie;
base::optional<EditFile> selfieInEdit;
mtpRequestId saveRequestId = 0;
};
struct Form {
@ -200,6 +205,8 @@ public:
QString defaultPhoneNumber() const;
rpl::producer<not_null<const EditFile*>> scanUpdated() const;
rpl::producer<not_null<const Value*>> valueSaved() const;
rpl::producer<not_null<const Value*>> verificationNeeded() const;
const Form &form() const;
void startValueEdit(not_null<const Value*> value);
@ -288,6 +295,8 @@ private:
std::map<FileKey, std::unique_ptr<mtpFileLoader>> _fileLoaders;
rpl::event_stream<not_null<const EditFile*>> _scanUpdated;
rpl::event_stream<not_null<const Value*>> _valueSaved;
rpl::event_stream<not_null<const Value*>> _verificationNeeded;
bytes::vector _secret;
uint64 _secretId = 0;

View File

@ -423,42 +423,77 @@ void PanelController::editScope(int index) {
_panel.get(),
this,
std::move(GetDocumentScheme(_editScope->type)),
_editScope->fields->data.parsed,
_editScope->files[_editScopeFilesIndex]->data.parsed,
_editScope->fields->data.parsedInEdit,
_editScope->files[_editScopeFilesIndex]->data.parsedInEdit,
valueFiles(*_editScope->files[_editScopeFilesIndex]))
: object_ptr<PanelEditDocument>(
_panel.get(),
this,
std::move(GetDocumentScheme(_editScope->type)),
_editScope->fields->data.parsed);
_editScope->fields->data.parsedInEdit);
case Scope::Type::Phone:
case Scope::Type::Email: {
const auto &fields = _editScope->fields->data.parsed.fields;
const auto valueIt = fields.find("value");
const auto &parsed = _editScope->fields->data.parsedInEdit;
const auto valueIt = parsed.fields.find("value");
return object_ptr<PanelEditContact>(
_panel.get(),
this,
std::move(GetContactScheme(_editScope->type)),
(valueIt == end(fields)) ? QString() : valueIt->second,
(valueIt == end(parsed.fields)
? QString()
: valueIt->second),
getDefaultContactValue(_editScope->type));
} break;
}
Unexpected("Type in PanelController::editScope().");
}();
if (content) {
_panel->setBackAllowed(true);
_panel->backRequests(
) | rpl::start_with_next([=] {
cancelValueEdit(index);
_panel->setBackAllowed(true);
_panel->backRequests(
) | rpl::start_with_next([=] {
_panel->showForm();
}, content->lifetime());
content->lifetime().add([=] {
cancelValueEdit();
});
_form->valueSaved(
) | rpl::start_with_next([=](not_null<const Value*> value) {
processValueSaved(value);
}, content->lifetime());
_form->verificationNeeded(
) | rpl::start_with_next([=](not_null<const Value*> value) {
processVerificationNeeded(value);
}, content->lifetime());
_panel->showEditValue(std::move(content));
}
void PanelController::processValueSaved(not_null<const Value*> value) {
Expects(_editScope != nullptr);
const auto value1 = _editScope->fields;
const auto value2 = (_editScopeFilesIndex >= 0)
? _editScope->files[_editScopeFilesIndex].get()
: nullptr;
if (value == value1 || value == value2) {
if (!value1->saveRequestId && (!value2 || !value2->saveRequestId)) {
_panel->showForm();
}, content->lifetime());
_panel->showEditValue(std::move(content));
} else {
cancelValueEdit(index);
show(Box<InformBox>("Saved"));
}
}
}
std::vector<ScanInfo> PanelController::valueFiles(const Value &value) const {
void PanelController::processVerificationNeeded(
not_null<const Value*> value) {
show(Box<InformBox>("Verification needed :("));
}
std::vector<ScanInfo> PanelController::valueFiles(
const Value &value) const {
auto result = std::vector<ScanInfo>();
for (const auto &file : value.filesInEdit) {
result.push_back(collectScanInfo(file));
@ -466,12 +501,12 @@ std::vector<ScanInfo> PanelController::valueFiles(const Value &value) const {
return result;
}
void PanelController::cancelValueEdit(int index) {
void PanelController::cancelValueEdit() {
if (const auto scope = base::take(_editScope)) {
_form->startValueEdit(scope->fields);
_form->cancelValueEdit(scope->fields);
const auto index = std::exchange(_editScopeFilesIndex, -1);
if (index >= 0) {
_form->startValueEdit(scope->files[index]);
_form->cancelValueEdit(scope->files[index]);
}
}
}
@ -480,16 +515,14 @@ void PanelController::saveScope(ValueMap &&data, ValueMap &&filesData) {
Expects(_panel != nullptr);
Expects(_editScope != nullptr);
const auto scope = base::take(_editScope);
_form->saveValueEdit(scope->fields, std::move(data));
const auto index = std::exchange(_editScopeFilesIndex, -1);
if (index >= 0) {
_form->saveValueEdit(scope->files[index], std::move(filesData));
_form->saveValueEdit(_editScope->fields, std::move(data));
if (_editScopeFilesIndex >= 0) {
_form->saveValueEdit(
_editScope->files[_editScopeFilesIndex],
std::move(filesData));
} else {
Assert(filesData.fields.empty());
}
_panel->showForm();
}
void PanelController::cancelAuth() {

View File

@ -81,8 +81,10 @@ public:
private:
void ensurePanelCreated();
void cancelValueEdit(int index);
void cancelValueEdit();
std::vector<ScanInfo> valueFiles(const Value &value) const;
void processValueSaved(not_null<const Value*> value);
void processVerificationNeeded(not_null<const Value*> value);
ScanInfo collectScanInfo(const EditFile &file) const;
QString getDefaultContactValue(Scope::Type type) const;

View File

@ -115,14 +115,15 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
return QString();
};
for (const auto &row : _scheme.rows) {
for (auto i = 0, count = int(_scheme.rows.size()); i != count; ++i) {
const auto &row = _scheme.rows[i];
auto fields = (row.type == Scheme::ValueType::Fields)
? &data
: scanData;
if (!fields) {
continue;
}
_details.push_back(inner->add(object_ptr<PanelDetailsRow>(
_details.emplace(i, inner->add(object_ptr<PanelDetailsRow>(
inner,
row.label,
valueOrEmpty(*fields, row.key))));
@ -132,7 +133,7 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
}
void PanelEditDocument::focusInEvent(QFocusEvent *e) {
for (const auto row : _details) {
for (const auto [index, row] : _details) {
if (row->setFocusFast()) {
return;
}
@ -157,11 +158,9 @@ void PanelEditDocument::updateControlsGeometry() {
}
void PanelEditDocument::save() {
Expects(_details.size() == _scheme.rows.size());
auto data = ValueMap();
auto scanData = ValueMap();
for (auto i = 0, count = int(_details.size()); i != count; ++i) {
for (const auto [i, field] : _details) {
const auto &row = _scheme.rows[i];
auto &fields = (row.type == Scheme::ValueType::Fields)
? data

View File

@ -81,7 +81,7 @@ private:
object_ptr<Ui::PlainShadow> _bottomShadow;
QPointer<EditScans> _editScans;
std::vector<QPointer<PanelDetailsRow>> _details;
std::map<int, QPointer<PanelDetailsRow>> _details;
object_ptr<Ui::RoundButton> _done;

View File

@ -405,13 +405,15 @@ void Controller::showJumpToDate(Dialogs::Key chat, QDate requestedDate) {
Ui::show(std::move(box));
}
void Controller::showAuthForm(const Passport::FormRequest &request) {
_authForm = std::make_unique<Passport::FormController>(this, request);
_authForm->show();
void Controller::showPassportForm(const Passport::FormRequest &request) {
_passportForm = std::make_unique<Passport::FormController>(
this,
request);
_passportForm->show();
}
void Controller::clearAuthForm() {
_authForm = nullptr;
void Controller::clearPassportForm() {
_passportForm = nullptr;
}
void Controller::updateColumnLayout() {

View File

@ -205,8 +205,8 @@ public:
Dialogs::Key chat,
QDate requestedDate);
void showAuthForm(const Passport::FormRequest &request);
void clearAuthForm();
void showPassportForm(const Passport::FormRequest &request);
void clearPassportForm();
base::Variable<bool> &dialogsListFocused() {
return _dialogsListFocused;
@ -254,7 +254,7 @@ private:
not_null<MainWindow*> _window;
std::unique_ptr<Passport::FormController> _authForm;
std::unique_ptr<Passport::FormController> _passportForm;
GifPauseReasons _gifPauseReasons = 0;
base::Observable<void> _gifPauseLevelChanged;