Allow deleting documents in passport.

This commit is contained in:
John Preston 2018-04-13 20:43:17 +04:00
parent e82430cb50
commit e4ae5bfcad
11 changed files with 319 additions and 96 deletions

View File

@ -1584,6 +1584,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passport_confirm_email" = "We've sent a confirmation code to your email {email}.";
"lng_passport_sure_cancel" = "If you continue your changes will be lost.";
"lng_passport_scans_limit_reached" = "Scans limit reached.";
"lng_passport_delete_document" = "Delete document";
"lng_passport_delete_document_sure" = "Are you sure you want to delete this document?";
"lng_passport_delete_details" = "Delete personal details";
"lng_passport_delete_details_sure" = "Are you sure you want to delete your personal details?";
"lng_passport_delete_address" = "Delete address information";
"lng_passport_delete_address_sure" = "Are you sure you wnat to delete your address information?";
"lng_passport_delete_email" = "Delete email";
"lng_passport_delete_email_sure" = "Are you sure you want to delete your email?";
"lng_passport_delete_phone" = "Delete phone number";
"lng_passport_delete_phone_sure" = "Are you sure you want to delete your phone number?";
// Wnd specific

View File

@ -141,6 +141,7 @@ passportContactNewFieldPadding: margins(22px, 0px, 22px, 28px);
passportContactFieldPadding: margins(22px, 14px, 22px, 28px);
passportRowPadding: margins(22px, 8px, 25px, 8px);
passportRowIconSkip: 10px;
passportRowSkip: 2px;
passportRowRipple: RippleAnimation(defaultRippleAnimation) {
color: windowBgOver;
@ -166,6 +167,10 @@ passportUploadButton: InfoProfileButton {
}
passportUploadButtonPadding: margins(0px, 10px, 0px, 10px);
passportUploadHeaderPadding: margins(22px, 14px, 22px, 3px);
passportDeleteButton: InfoProfileButton(passportUploadButton) {
textFg: attentionButtonFg;
textFgOver: attentionButtonFgOver;
}
passportScanNameStyle: TextStyle(defaultTextStyle) {
font: font(boxFontSize semibold);

View File

@ -337,10 +337,7 @@ bytes::vector DecryptValueSecret(
uint64 CountSecureSecretHash(bytes::const_span secret) {
const auto full = openssl::Sha256(secret);
const auto part = bytes::make_span(full).subspan(
full.size() - sizeof(uint64),
sizeof(uint64));
return *reinterpret_cast<const uint64*>(part.data());
return *reinterpret_cast<const uint64*>(full.data());
}
bytes::vector EncryptCredentialsSecret(

View File

@ -1017,6 +1017,26 @@ void FormController::saveValueEdit(
}
}
void FormController::deleteValueEdit(not_null<const Value*> value) {
if (savingValue(value)) {
return;
}
const auto nonconst = findValue(value);
nonconst->saveRequestId = request(MTPaccount_DeleteSecureValue(
MTP_vector<MTPSecureValueType>(1, ConvertType(nonconst->type))
)).done([=](const MTPBool &result) {
const auto editScreens = value->editScreens;
*nonconst = Value(nonconst->type);
nonconst->editScreens = editScreens;
_valueSaveFinished.fire_copy(value);
}).fail([=](const RPCError &error) {
nonconst->saveRequestId = 0;
valueSaveFailed(nonconst, error);
}).send();
}
void FormController::saveEncryptedValue(not_null<Value*> value) {
Expects(isEncryptedValue(value->type));

View File

@ -240,6 +240,7 @@ public:
not_null<const Value*> value,
const ValueMap &data) const;
void saveValueEdit(not_null<const Value*> value, ValueMap &&data);
void deleteValueEdit(not_null<const Value*> value);
bool savingValue(not_null<const Value*> value) const;
void cancel();

View File

@ -131,9 +131,8 @@ QString ComputeScopeRowReadyString(const Scope &scope) {
}
}());
}
if (document
&& (document->scans.empty()
|| (scope.selfieRequired && !document->selfie))) {
if (!scope.documents.empty()
&& (!document || (scope.selfieRequired && !document->selfie))) {
return QString();
}
const auto scheme = GetDocumentScheme(scope.type);

View File

@ -391,79 +391,60 @@ QString PanelController::defaultPhoneNumber() const {
bool PanelController::canAddScan() const {
Expects(_editScope != nullptr);
Expects(_editDocumentIndex >= 0
&& _editDocumentIndex < _editScope->documents.size());
Expects(_editDocument != nullptr);
return _form->canAddScan(_editScope->documents[_editDocumentIndex]);
return _form->canAddScan(_editDocument);
}
void PanelController::uploadScan(QByteArray &&content) {
Expects(_editScope != nullptr);
Expects(_editDocumentIndex >= 0
&& _editDocumentIndex < _editScope->documents.size());
Expects(_editDocument != nullptr);
_form->uploadScan(
_editScope->documents[_editDocumentIndex],
std::move(content));
_form->uploadScan(_editDocument, std::move(content));
}
void PanelController::deleteScan(int fileIndex) {
Expects(_editScope != nullptr);
Expects(_editDocumentIndex >= 0
&& _editDocumentIndex < _editScope->documents.size());
Expects(_editDocument != nullptr);
_form->deleteScan(
_editScope->documents[_editDocumentIndex],
fileIndex);
_form->deleteScan(_editDocument, fileIndex);
}
void PanelController::restoreScan(int fileIndex) {
Expects(_editScope != nullptr);
Expects(_editDocumentIndex >= 0
&& _editDocumentIndex < _editScope->documents.size());
Expects(_editDocument != nullptr);
_form->restoreScan(
_editScope->documents[_editDocumentIndex],
fileIndex);
_form->restoreScan(_editDocument, fileIndex);
}
void PanelController::uploadSelfie(QByteArray &&content) {
Expects(_editScope != nullptr);
Expects(_editDocumentIndex >= 0
&& _editDocumentIndex < _editScope->documents.size());
Expects(_editDocument != nullptr);
Expects(_editScope->selfieRequired);
_form->uploadSelfie(
_editScope->documents[_editDocumentIndex],
std::move(content));
_form->uploadSelfie(_editDocument, std::move(content));
}
void PanelController::deleteSelfie() {
Expects(_editScope != nullptr);
Expects(_editDocumentIndex >= 0
&& _editDocumentIndex < _editScope->documents.size());
Expects(_editDocument != nullptr);
Expects(_editScope->selfieRequired);
_form->deleteSelfie(
_editScope->documents[_editDocumentIndex]);
_form->deleteSelfie(_editDocument);
}
void PanelController::restoreSelfie() {
Expects(_editScope != nullptr);
Expects(_editDocumentIndex >= 0
&& _editDocumentIndex < _editScope->documents.size());
Expects(_editDocument != nullptr);
Expects(_editScope->selfieRequired);
_form->restoreSelfie(
_editScope->documents[_editDocumentIndex]);
_form->restoreSelfie(_editDocument);
}
rpl::producer<ScanInfo> PanelController::scanUpdated() const {
return _form->scanUpdated(
) | rpl::filter([=](not_null<const EditFile*> file) {
return (_editScope != nullptr)
&& (_editDocumentIndex >= 0)
&& (file->value == _editScope->documents[_editDocumentIndex]);
return (file->value == _editDocument);
}) | rpl::map([=](not_null<const EditFile*> file) {
return collectScanInfo(*file);
});
@ -471,7 +452,7 @@ rpl::producer<ScanInfo> PanelController::scanUpdated() const {
ScanInfo PanelController::collectScanInfo(const EditFile &file) const {
Expects(_editScope != nullptr);
Expects(_editDocumentIndex >= 0);
Expects(_editDocument != nullptr);
const auto status = [&] {
if (file.fields.accessHash) {
@ -502,10 +483,9 @@ ScanInfo PanelController::collectScanInfo(const EditFile &file) const {
return formatDownloadText(0, file.fields.size);
}
}();
const auto &documents = _editScope->documents;
auto isSelfie = (file.value == documents[_editDocumentIndex])
&& (documents[_editDocumentIndex]->selfieInEdit.has_value())
&& (&file == &*documents[_editDocumentIndex]->selfieInEdit);
auto isSelfie = (file.value == _editDocument)
&& (_editDocument->selfieInEdit.has_value())
&& (&file == &*_editDocument->selfieInEdit);
return {
FileKey{ file.fields.id, file.fields.dcId },
status,
@ -514,6 +494,96 @@ ScanInfo PanelController::collectScanInfo(const EditFile &file) const {
isSelfie };
}
auto PanelController::deleteValueLabel() const
-> base::optional<rpl::producer<QString>> {
Expects(_editScope != nullptr);
if (hasValueDocument()) {
return Lang::Viewer(lng_passport_delete_document);
}
if (!hasValueFields()) {
return base::none;
}
switch (_editScope->type) {
case Scope::Type::Identity:
return Lang::Viewer(lng_passport_delete_details);
case Scope::Type::Address:
return Lang::Viewer(lng_passport_delete_address);
case Scope::Type::Email:
return Lang::Viewer(lng_passport_delete_email);
case Scope::Type::Phone:
return Lang::Viewer(lng_passport_delete_phone);
}
Unexpected("Type in PanelController::deleteValueLabel.");
}
bool PanelController::hasValueDocument() const {
Expects(_editScope != nullptr);
if (!_editDocument) {
return false;
}
return !_editDocument->data.parsed.fields.empty()
|| !_editDocument->scans.empty()
|| _editDocument->selfie.has_value();
}
bool PanelController::hasValueFields() const {
Expects(_editValue != nullptr);
return !_editValue->data.parsed.fields.empty();
}
void PanelController::deleteValue() {
Expects(_editScope != nullptr);
if (savingScope()) {
return;
}
const auto text = [&] {
switch (_editScope->type) {
case Scope::Type::Identity:
return lang(hasValueDocument()
? lng_passport_delete_document_sure
: lng_passport_delete_details_sure);
case Scope::Type::Address:
return lang(hasValueDocument()
? lng_passport_delete_document_sure
: lng_passport_delete_address_sure);
case Scope::Type::Phone:
return lang(lng_passport_delete_phone_sure);
case Scope::Type::Email:
return lang(lng_passport_delete_email_sure);
}
Unexpected("Type in deleteValue.");
}();
const auto checkbox = (hasValueDocument() && hasValueFields()) ? [&] {
switch (_editScope->type) {
case Scope::Type::Identity:
return lang(lng_passport_delete_details);
case Scope::Type::Address:
return lang(lng_passport_delete_address);
}
Unexpected("Type in deleteValue.");
}() : QString();
_editScopeBoxes.emplace_back(show(ConfirmDeleteDocument(
[=](bool withDetails) { deleteValueSure(withDetails); },
text,
checkbox)));
}
void PanelController::deleteValueSure(bool withDetails) {
Expects(_editValue != nullptr);
if (hasValueDocument()) {
_form->deleteValueEdit(_editDocument);
}
if (withDetails || !hasValueDocument()) {
_form->deleteValueEdit(_editValue);
}
}
QString PanelController::getDefaultContactValue(Scope::Type type) const {
switch (type) {
case Scope::Type::Phone:
@ -653,36 +723,38 @@ void PanelController::editScope(int index, int documentIndex) {
&& documentIndex < _scopes[index].documents.size()));
_editScope = &_scopes[index];
_editDocumentIndex = documentIndex;
_editValue = _editScope->fields;
_editDocument = (documentIndex >= 0)
? _scopes[index].documents[documentIndex].get()
: nullptr;
_form->startValueEdit(_editScope->fields);
if (_editDocumentIndex >= 0) {
_form->startValueEdit(_editScope->documents[_editDocumentIndex]);
_form->startValueEdit(_editValue);
if (_editDocument) {
_form->startValueEdit(_editDocument);
}
auto content = [&]() -> object_ptr<Ui::RpWidget> {
switch (_editScope->type) {
case Scope::Type::Identity:
case Scope::Type::Address: {
const auto &documents = _editScope->documents;
auto result = (_editDocumentIndex >= 0)
auto result = _editDocument
? object_ptr<PanelEditDocument>(
_panel.get(),
this,
GetDocumentScheme(
_editScope->type,
documents[_editDocumentIndex]->type),
_editScope->fields->data.parsedInEdit,
documents[_editDocumentIndex]->data.parsedInEdit,
valueFiles(*documents[_editDocumentIndex]),
_editDocument->type),
_editValue->data.parsedInEdit,
_editDocument->data.parsedInEdit,
valueFiles(*_editDocument),
(_editScope->selfieRequired
? valueSelfie(*documents[_editDocumentIndex])
? valueSelfie(*_editDocument)
: nullptr))
: object_ptr<PanelEditDocument>(
_panel.get(),
this,
GetDocumentScheme(_editScope->type),
_editScope->fields->data.parsedInEdit);
_editValue->data.parsedInEdit);
const auto weak = make_weak(result.data());
_panelHasUnsavedChanges = [=] {
return weak ? weak->hasUnsavedChanges() : false;
@ -691,7 +763,7 @@ void PanelController::editScope(int index, int documentIndex) {
} break;
case Scope::Type::Phone:
case Scope::Type::Email: {
const auto &parsed = _editScope->fields->data.parsedInEdit;
const auto &parsed = _editValue->data.parsedInEdit;
const auto valueIt = parsed.fields.find("value");
_panelHasUnsavedChanges = nullptr;
return object_ptr<PanelEditContact>(
@ -736,18 +808,18 @@ void PanelController::processValueSaveFinished(
_verificationBoxes.erase(boxIt);
}
const auto value1 = _editScope->fields;
const auto value2 = (_editDocumentIndex >= 0)
? _editScope->documents[_editDocumentIndex].get()
: nullptr;
if (value == value1 || value == value2) {
if (!_form->savingValue(value1)
&& (!value2 || !_form->savingValue(value2))) {
_panel->showForm();
}
if (!savingScope()) {
_panel->showForm();
}
}
bool PanelController::savingScope() const {
Expects(_editValue != nullptr);
return _form->savingValue(_editValue)
|| (_editDocument && _form->savingValue(_editDocument));
}
void PanelController::processVerificationNeeded(
not_null<const Value*> value) {
const auto i = _verificationBoxes.find(value);
@ -828,24 +900,27 @@ std::unique_ptr<ScanInfo> PanelController::valueSelfie(
}
void PanelController::cancelValueEdit() {
if (const auto scope = base::take(_editScope)) {
_form->cancelValueEdit(scope->fields);
const auto index = std::exchange(_editDocumentIndex, -1);
if (index >= 0) {
_form->cancelValueEdit(scope->documents[index]);
}
Expects(_editScope != nullptr);
_editScopeBoxes.clear();
_form->cancelValueEdit(base::take(_editValue));
if (const auto document = base::take(_editDocument)) {
_form->cancelValueEdit(document);
}
_editScope = nullptr;
}
void PanelController::saveScope(ValueMap &&data, ValueMap &&filesData) {
Expects(_panel != nullptr);
Expects(_editScope != nullptr);
Expects(_editValue != nullptr);
_form->saveValueEdit(_editScope->fields, std::move(data));
if (_editDocumentIndex >= 0) {
_form->saveValueEdit(
_editScope->documents[_editDocumentIndex],
std::move(filesData));
if (savingScope()) {
return;
}
_form->saveValueEdit(_editValue, std::move(data));
if (_editDocument) {
_form->saveValueEdit(_editDocument, std::move(filesData));
} else {
Assert(filesData.fields.empty());
}
@ -854,14 +929,12 @@ void PanelController::saveScope(ValueMap &&data, ValueMap &&filesData) {
bool PanelController::editScopeChanged(
const ValueMap &data,
const ValueMap &filesData) const {
Expects(_editScope != nullptr);
Expects(_editValue != nullptr);
if (_form->editValueChanged(_editScope->fields, data)) {
if (_form->editValueChanged(_editValue, data)) {
return true;
} else if (_editDocumentIndex >= 0) {
return _form->editValueChanged(
_editScope->documents[_editDocumentIndex],
filesData);
} else if (_editDocument) {
return _form->editValueChanged(_editDocument, filesData);
}
return false;
}
@ -871,13 +944,11 @@ void PanelController::cancelEditScope() {
if (_panelHasUnsavedChanges && _panelHasUnsavedChanges()) {
if (!_confirmForgetChangesBox) {
_confirmForgetChangesBox = BoxPointer(show(Box<ConfirmBox>(
_confirmForgetChangesBox = show(Box<ConfirmBox>(
lang(lng_passport_sure_cancel),
lang(lng_continue),
[=] {
_panel->showForm();
base::take(_confirmForgetChangesBox);
})).data());
[=] { _panel->showForm(); }));
_editScopeBoxes.emplace_back(_confirmForgetChangesBox);
}
} else {
_panel->showForm();

View File

@ -69,6 +69,9 @@ public:
void restoreSelfie();
rpl::producer<ScanInfo> scanUpdated() const;
base::optional<rpl::producer<QString>> deleteValueLabel() const;
void deleteValue();
QString defaultEmail() const;
QString defaultPhoneNumber() const;
@ -112,16 +115,23 @@ private:
void processValueSaveFinished(not_null<const Value*> value);
void processVerificationNeeded(not_null<const Value*> value);
bool savingScope() const;
bool hasValueDocument() const;
bool hasValueFields() const;
ScanInfo collectScanInfo(const EditFile &file) const;
QString getDefaultContactValue(Scope::Type type) const;
void deleteValueSure(bool withDetails);
not_null<FormController*> _form;
std::vector<Scope> _scopes;
std::unique_ptr<Panel> _panel;
base::lambda<bool()> _panelHasUnsavedChanges;
BoxPointer _confirmForgetChangesBox;
QPointer<BoxContent> _confirmForgetChangesBox;
std::vector<BoxPointer> _editScopeBoxes;
Scope *_editScope = nullptr;
const Value *_editValue = nullptr;
const Value *_editDocument = nullptr;
int _editDocumentIndex = -1;
BoxPointer _scopeDocumentTypeBox;
std::map<not_null<const Value*>, BoxPointer> _verificationBoxes;

View File

@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_panel_controller.h"
#include "passport/passport_panel_details_row.h"
#include "passport/passport_panel_edit_scans.h"
#include "info/profile/info_profile_button.h"
#include "info/profile/info_profile_values.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/labels.h"
@ -52,6 +54,28 @@ private:
};
class DeleteDocumentBox : public BoxContent {
public:
DeleteDocumentBox(
QWidget*,
const QString &text,
const QString &detailsCheckbox,
base::lambda<void(bool withDetails)> submit);
protected:
void prepare() override;
private:
void setupControls(
const QString &text,
const QString &detailsCheckbox,
base::lambda<void(bool withDetails)> submit);
base::lambda<void()> _submit;
int _height = 0;
};
RequestTypeBox::RequestTypeBox(
QWidget*,
const QString &title,
@ -122,6 +146,58 @@ void RequestTypeBox::setupControls(
};
}
DeleteDocumentBox::DeleteDocumentBox(
QWidget*,
const QString &text,
const QString &detailsCheckbox,
base::lambda<void(bool withDetails)> submit) {
setupControls(text, detailsCheckbox, submit);
}
void DeleteDocumentBox::prepare() {
addButton(langFactory(lng_box_delete), _submit);
addButton(langFactory(lng_cancel), [=] { closeBox(); });
setDimensions(st::boxWidth, _height);
}
void DeleteDocumentBox::setupControls(
const QString &text,
const QString &detailsCheckbox,
base::lambda<void(bool withDetails)> submit) {
const auto label = Ui::CreateChild<Ui::FlatLabel>(
this,
text,
Ui::FlatLabel::InitType::Simple,
st::boxLabel);
const auto details = !detailsCheckbox.isEmpty()
? Ui::CreateChild<Ui::Checkbox>(
this,
detailsCheckbox,
false,
st::defaultBoxCheckbox)
: nullptr;
_height = st::boxPadding.top();
const auto availableWidth = st::boxWidth
- st::boxPadding.left()
- st::boxPadding.right();
label->resizeToWidth(availableWidth);
label->moveToLeft(st::boxPadding.left(), _height);
_height += label->height();
if (details) {
_height += st::boxPadding.bottom();
details->moveToLeft(st::boxPadding.left(), _height);
_height += details->heightNoMargins();
}
_height += st::boxPadding.bottom();
_submit = [=] {
submit(details ? details->checked() : false);
};
}
} // namespace
struct PanelEditDocument::Result {
@ -252,6 +328,17 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
inner->add(
object_ptr<Ui::FixedHeightWidget>(inner, st::passportDetailsSkip));
if (auto text = _controller->deleteValueLabel()) {
_delete = inner->add(
object_ptr<Info::Profile::Button>(
inner,
std::move(*text) | Info::Profile::ToUpperValue(),
st::passportDeleteButton),
st::passportUploadButtonPadding);
_delete->addClickHandler([=] {
_controller->deleteValue();
});
}
return inner;
}
@ -301,12 +388,14 @@ PanelEditDocument::Result PanelEditDocument::collect() const {
}
bool PanelEditDocument::validate() {
if (const auto error = _editScans->validateGetErrorTop()) {
const auto error = _editScans
? _editScans->validateGetErrorTop()
: base::none;
if (error) {
const auto errortop = _editScans->mapToGlobal(QPoint(0, *error));
const auto scrolltop = _scroll->mapToGlobal(QPoint(0, 0));
const auto scrolldelta = errortop.y() - scrolltop.y();
_scroll->scrollToY(_scroll->scrollTop() + scrolldelta);
return false;
}
auto first = QPointer<PanelDetailsRow>();
for (const auto [i, field] : base::reversed(_details)) {
@ -317,7 +406,7 @@ bool PanelEditDocument::validate() {
}
}
if (!first) {
return true;
return !error;
}
const auto firsttop = first->mapToGlobal(QPoint(0, 0));
const auto scrolltop = _scroll->mapToGlobal(QPoint(0, 0));
@ -356,4 +445,11 @@ object_ptr<BoxContent> RequestAddressType(
submit);
}
object_ptr<BoxContent> ConfirmDeleteDocument(
base::lambda<void(bool withDetails)> submit,
const QString &text,
const QString &detailsCheckbox) {
return Box<DeleteDocumentBox>(text, detailsCheckbox, submit);
}
} // namespace Passport

View File

@ -17,6 +17,12 @@ class PlainShadow;
class RoundButton;
} // namespace Ui
namespace Info {
namespace Profile {
class Button;
} // namespace Profile
} // namespace Info
namespace Passport {
class PanelController;
@ -98,6 +104,8 @@ private:
QPointer<EditScans> _editScans;
std::map<int, QPointer<PanelDetailsRow>> _details;
QPointer<Info::Profile::Button> _delete;
object_ptr<Ui::RoundButton> _done;
};
@ -109,4 +117,9 @@ object_ptr<BoxContent> RequestAddressType(
base::lambda<void(int index)> submit,
std::vector<QString> labels);
object_ptr<BoxContent> ConfirmDeleteDocument(
base::lambda<void(bool withDetails)> submit,
const QString &text,
const QString &detailsCheckbox = QString());
} // namespace Passport

View File

@ -92,7 +92,8 @@ int PanelForm::Row::countAvailableWidth(int newWidth) const {
- st::passportRowPadding.right()
- (_ready
? st::passportRowReadyIcon
: st::passportRowEmptyIcon).width();
: st::passportRowEmptyIcon).width()
- st::passportRowIconSkip;
}
int PanelForm::Row::countAvailableWidth() const {