mirror of https://github.com/procxx/kepka.git
				
				
				
			Allow to add selfie in passport.
This commit is contained in:
		
							parent
							
								
									11fd757e99
								
							
						
					
					
						commit
						ccb57a6d69
					
				|  | @ -1548,6 +1548,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | |||
| "lng_passport_scan_index" = "Scan {index}"; | ||||
| "lng_passport_upload_scans" = "Upload scans"; | ||||
| "lng_passport_upload_more" = "Upload additional scans"; | ||||
| "lng_passport_selfie_title" = "Selfie"; | ||||
| "lng_passport_selfie_name" = "Photo"; | ||||
| "lng_passport_selfie_description" = "Take a picture of yourself holding hour document."; | ||||
| "lng_passport_upload_selfie" = "Upload selfie"; | ||||
| "lng_passport_personal_details" = "Personal details"; | ||||
| "lng_passport_choose_image" = "Choose scan image"; | ||||
| "lng_passport_delete_scan_undo" = "Undo"; | ||||
|  |  | |||
|  | @ -242,6 +242,18 @@ bool FormController::validateValueSecrets(Value &value) { | |||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
| 	if (value.selfie) { | ||||
| 		auto &file = *value.selfie; | ||||
| 		file.secret = DecryptValueSecret( | ||||
| 			file.encryptedSecret, | ||||
| 			_secret, | ||||
| 			file.hash); | ||||
| 		if (file.secret.empty()) { | ||||
| 			LOG(("API Error: Could not decrypt selfie secret. " | ||||
| 				"Forgetting files and data :(")); | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
|  | @ -266,8 +278,53 @@ void FormController::uploadScan( | |||
| 		nonconst, | ||||
| 		File(), | ||||
| 		nullptr); | ||||
| 	const auto fileId = rand_value<uint64>(); | ||||
| 	auto &file = nonconst->filesInEdit.back(); | ||||
| 	encryptFile(file, std::move(content), [=](UploadScanData &&result) { | ||||
| 		Expects(fileIndex >= 0 && fileIndex < nonconst->filesInEdit.size()); | ||||
| 
 | ||||
| 		uploadEncryptedFile( | ||||
| 			nonconst->filesInEdit[fileIndex], | ||||
| 			std::move(result)); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| void FormController::deleteScan( | ||||
| 		not_null<const Value*> value, | ||||
| 		int fileIndex) { | ||||
| 	scanDeleteRestore(value, fileIndex, true); | ||||
| } | ||||
| 
 | ||||
| void FormController::restoreScan( | ||||
| 		not_null<const Value*> value, | ||||
| 		int fileIndex) { | ||||
| 	scanDeleteRestore(value, fileIndex, false); | ||||
| } | ||||
| 
 | ||||
| void FormController::uploadSelfie( | ||||
| 		not_null<const Value*> value, | ||||
| 		QByteArray &&content) { | ||||
| 	const auto nonconst = findValue(value); | ||||
| 	nonconst->selfieInEdit = EditFile{ nonconst, File(), nullptr }; | ||||
| 	auto &file = *nonconst->selfieInEdit; | ||||
| 	encryptFile(file, std::move(content), [=](UploadScanData &&result) { | ||||
| 		uploadEncryptedFile( | ||||
| 			*nonconst->selfieInEdit, | ||||
| 			std::move(result)); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| void FormController::deleteSelfie(not_null<const Value*> value) { | ||||
| 	selfieDeleteRestore(value, true); | ||||
| } | ||||
| 
 | ||||
| void FormController::restoreSelfie(not_null<const Value*> value) { | ||||
| 	selfieDeleteRestore(value, false); | ||||
| } | ||||
| 
 | ||||
| void FormController::prepareFile( | ||||
| 		EditFile &file, | ||||
| 		const QByteArray &content) { | ||||
| 	const auto fileId = rand_value<uint64>(); | ||||
| 	file.fields.size = content.size(); | ||||
| 	file.fields.id = fileId; | ||||
| 	file.fields.dcId = MTP::maindc(); | ||||
|  | @ -277,17 +334,14 @@ void FormController::uploadScan( | |||
| 	file.fields.downloadOffset = file.fields.size; | ||||
| 
 | ||||
| 	_scanUpdated.fire(&file); | ||||
| 
 | ||||
| 	encryptScan(nonconst, fileIndex, std::move(content)); | ||||
| } | ||||
| 
 | ||||
| void FormController::encryptScan( | ||||
| 		not_null<Value*> value, | ||||
| 		int fileIndex, | ||||
| 		QByteArray &&content) { | ||||
| 	Expects(fileIndex >= 0 && fileIndex < value->filesInEdit.size()); | ||||
| void FormController::encryptFile( | ||||
| 		EditFile &file, | ||||
| 		QByteArray &&content, | ||||
| 		base::lambda<void(UploadScanData &&result)> callback) { | ||||
| 	prepareFile(file, content); | ||||
| 
 | ||||
| 	const auto &file = value->filesInEdit[fileIndex]; | ||||
| 	const auto weak = std::weak_ptr<bool>(file.guard); | ||||
| 	crl::async([ | ||||
| 		=, | ||||
|  | @ -309,27 +363,12 @@ void FormController::encryptScan( | |||
| 			result.md5checksum.data()); | ||||
| 		crl::on_main([=, encrypted = std::move(result)]() mutable { | ||||
| 			if (weak.lock()) { | ||||
| 				uploadEncryptedScan( | ||||
| 					value , | ||||
| 					fileIndex, | ||||
| 					std::move(encrypted)); | ||||
| 				callback(std::move(encrypted)); | ||||
| 			} | ||||
| 		}); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| void FormController::deleteScan( | ||||
| 		not_null<const Value*> value, | ||||
| 		int fileIndex) { | ||||
| 	scanDeleteRestore(value, fileIndex, true); | ||||
| } | ||||
| 
 | ||||
| void FormController::restoreScan( | ||||
| 		not_null<const Value*> value, | ||||
| 		int fileIndex) { | ||||
| 	scanDeleteRestore(value, fileIndex, false); | ||||
| } | ||||
| 
 | ||||
| void FormController::scanDeleteRestore( | ||||
| 		not_null<const Value*> value, | ||||
| 		int fileIndex, | ||||
|  | @ -342,6 +381,17 @@ void FormController::scanDeleteRestore( | |||
| 	_scanUpdated.fire(&file); | ||||
| } | ||||
| 
 | ||||
| void FormController::selfieDeleteRestore( | ||||
| 		not_null<const Value*> value, | ||||
| 		bool deleted) { | ||||
| 	Expects(value->selfieInEdit.has_value()); | ||||
| 
 | ||||
| 	const auto nonconst = findValue(value); | ||||
| 	auto &file = *nonconst->selfieInEdit; | ||||
| 	file.deleted = deleted; | ||||
| 	_scanUpdated.fire(&file); | ||||
| } | ||||
| 
 | ||||
| void FormController::subscribeToUploader() { | ||||
| 	if (_uploaderSubscriptions) { | ||||
| 		return; | ||||
|  | @ -365,15 +415,11 @@ void FormController::subscribeToUploader() { | |||
| 	}, _uploaderSubscriptions); | ||||
| } | ||||
| 
 | ||||
| void FormController::uploadEncryptedScan( | ||||
| 		not_null<Value*> value, | ||||
| 		int fileIndex, | ||||
| void FormController::uploadEncryptedFile( | ||||
| 		EditFile &file, | ||||
| 		UploadScanData &&data) { | ||||
| 	Expects(fileIndex >= 0 && fileIndex < value->filesInEdit.size()); | ||||
| 
 | ||||
| 	subscribeToUploader(); | ||||
| 
 | ||||
| 	auto &file = value->filesInEdit[fileIndex]; | ||||
| 	file.uploadData = std::make_unique<UploadScanData>(std::move(data)); | ||||
| 
 | ||||
| 	auto prepared = std::make_shared<FileLoadResult>( | ||||
|  | @ -548,53 +594,67 @@ void FormController::startValueEdit(not_null<const Value*> value) { | |||
| 	if (savingValue(nonconst)) { | ||||
| 		return; | ||||
| 	} | ||||
| 	loadFiles(nonconst->files); | ||||
| 	for (auto &file : nonconst->files) { | ||||
| 		loadFile(file); | ||||
| 	} | ||||
| 	if (nonconst->selfie) { | ||||
| 		loadFile(*nonconst->selfie); | ||||
| 	} | ||||
| 	nonconst->filesInEdit = ranges::view::all( | ||||
| 		nonconst->files | ||||
| 	) | ranges::view::transform([=](const File &file) { | ||||
| 		return EditFile(nonconst, file, nullptr); | ||||
| 	}) | ranges::to_vector; | ||||
| 
 | ||||
| 	if (nonconst->selfie) { | ||||
| 		nonconst->selfieInEdit = EditFile( | ||||
| 			nonconst, | ||||
| 			*nonconst->selfie, | ||||
| 			nullptr); | ||||
| 	} else { | ||||
| 		nonconst->selfieInEdit = base::none; | ||||
| 	} | ||||
| 
 | ||||
| 	nonconst->data.parsedInEdit = nonconst->data.parsed; | ||||
| } | ||||
| 
 | ||||
| void FormController::loadFiles(std::vector<File> &files) { | ||||
| 	for (auto &file : files) { | ||||
| 		if (!file.image.isNull()) { | ||||
| 			file.downloadOffset = file.size; | ||||
| 			continue; | ||||
| 		} | ||||
| 
 | ||||
| 		const auto key = FileKey{ file.id, file.dcId }; | ||||
| 		const auto i = _fileLoaders.find(key); | ||||
| 		if (i == _fileLoaders.end()) { | ||||
| 			file.downloadOffset = 0; | ||||
| 			const auto [i, ok] = _fileLoaders.emplace( | ||||
| 				key, | ||||
| 				std::make_unique<mtpFileLoader>( | ||||
| 					file.dcId, | ||||
| 					file.id, | ||||
| 					file.accessHash, | ||||
| 					0, | ||||
| 					SecureFileLocation, | ||||
| 					QString(), | ||||
| 					file.size, | ||||
| 					LoadToCacheAsWell, | ||||
| 					LoadFromCloudOrLocal, | ||||
| 					false)); | ||||
| 			const auto loader = i->second.get(); | ||||
| 			loader->connect(loader, &mtpFileLoader::progress, [=] { | ||||
| 				if (loader->finished()) { | ||||
| 					fileLoadDone(key, loader->bytes()); | ||||
| 				} else { | ||||
| 					fileLoadProgress(key, loader->currentOffset()); | ||||
| 				} | ||||
| 			}); | ||||
| 			loader->connect(loader, &mtpFileLoader::failed, [=] { | ||||
| 				fileLoadFail(key); | ||||
| 			}); | ||||
| 			loader->start(); | ||||
| 		} | ||||
| void FormController::loadFile(File &file) { | ||||
| 	if (!file.image.isNull()) { | ||||
| 		file.downloadOffset = file.size; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	const auto key = FileKey{ file.id, file.dcId }; | ||||
| 	const auto i = _fileLoaders.find(key); | ||||
| 	if (i != _fileLoaders.end()) { | ||||
| 		return; | ||||
| 	} | ||||
| 	file.downloadOffset = 0; | ||||
| 	const auto [j, ok] = _fileLoaders.emplace( | ||||
| 		key, | ||||
| 		std::make_unique<mtpFileLoader>( | ||||
| 			file.dcId, | ||||
| 			file.id, | ||||
| 			file.accessHash, | ||||
| 			0, | ||||
| 			SecureFileLocation, | ||||
| 			QString(), | ||||
| 			file.size, | ||||
| 			LoadToCacheAsWell, | ||||
| 			LoadFromCloudOrLocal, | ||||
| 			false)); | ||||
| 	const auto loader = j->second.get(); | ||||
| 	loader->connect(loader, &mtpFileLoader::progress, [=] { | ||||
| 		if (loader->finished()) { | ||||
| 			fileLoadDone(key, loader->bytes()); | ||||
| 		} else { | ||||
| 			fileLoadProgress(key, loader->currentOffset()); | ||||
| 		} | ||||
| 	}); | ||||
| 	loader->connect(loader, &mtpFileLoader::failed, [=] { | ||||
| 		fileLoadFail(key); | ||||
| 	}); | ||||
| 	loader->start(); | ||||
| } | ||||
| 
 | ||||
| void FormController::fileLoadDone(FileKey key, const QByteArray &bytes) { | ||||
|  | @ -666,6 +726,7 @@ void FormController::clearValueEdit(not_null<Value*> value) { | |||
| 		return; | ||||
| 	} | ||||
| 	value->filesInEdit.clear(); | ||||
| 	value->selfieInEdit = base::none; | ||||
| 	value->data.encryptedSecretInEdit.clear(); | ||||
| 	value->data.hashInEdit.clear(); | ||||
| 	value->data.parsedInEdit = ValueMap(); | ||||
|  | @ -787,7 +848,6 @@ void FormController::saveEncryptedValue(not_null<Value*> value) { | |||
| 		inputFiles.push_back(inputFile(file)); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	if (value->data.secret.empty()) { | ||||
| 		value->data.secret = GenerateSecretBytes(); | ||||
| 	} | ||||
|  | @ -890,9 +950,18 @@ void FormController::sendSaveRequest( | |||
| 		value->saveRequestId = 0; | ||||
| 
 | ||||
| 		const auto &data = result.c_secureValue(); | ||||
| 		value->files = parseFiles( | ||||
| 			data.vfiles.v, | ||||
| 			base::take(value->filesInEdit)); | ||||
| 		value->files = data.has_files() | ||||
| 			? parseFiles( | ||||
| 				data.vfiles.v, | ||||
| 				base::take(value->filesInEdit)) | ||||
| 			: std::vector<File>(); | ||||
| 		auto selfiesInEdit = std::vector<EditFile>(); | ||||
| 		if (auto selfie = base::take(value->selfieInEdit)) { | ||||
| 			selfiesInEdit.push_back(std::move(*selfie)); | ||||
| 		} | ||||
| 		value->selfie = data.has_selfie() | ||||
| 			? parseFile(data.vselfie, selfiesInEdit) | ||||
| 			: base::none; | ||||
| 		value->data.encryptedSecret = std::move( | ||||
| 			value->data.encryptedSecretInEdit); | ||||
| 		value->data.parsed = std::move(value->data.parsedInEdit); | ||||
|  | @ -1110,32 +1179,40 @@ auto FormController::parseFiles( | |||
| 	auto result = std::vector<File>(); | ||||
| 	result.reserve(data.size()); | ||||
| 
 | ||||
| 	auto index = 0; | ||||
| 	for (const auto &file : data) { | ||||
| 		switch (file.type()) { | ||||
| 		case mtpc_secureFileEmpty: { | ||||
| 
 | ||||
| 		} break; | ||||
| 		case mtpc_secureFile: { | ||||
| 			const auto &fields = file.c_secureFile(); | ||||
| 			auto normal = File(); | ||||
| 			normal.id = fields.vid.v; | ||||
| 			normal.accessHash = fields.vaccess_hash.v; | ||||
| 			normal.size = fields.vsize.v; | ||||
| 			normal.date = fields.vdate.v; | ||||
| 			normal.dcId = fields.vdc_id.v; | ||||
| 			normal.hash = bytes::make_vector(fields.vfile_hash.v); | ||||
| 			normal.encryptedSecret = bytes::make_vector(fields.vsecret.v); | ||||
| 			fillDownloadedFile(normal, editData); | ||||
| 			result.push_back(std::move(normal)); | ||||
| 		} break; | ||||
| 		if (auto normal = parseFile(file, editData)) { | ||||
| 			result.push_back(std::move(*normal)); | ||||
| 		} | ||||
| 		++index; | ||||
| 	} | ||||
| 
 | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| auto FormController::parseFile( | ||||
| 	const MTPSecureFile &data, | ||||
| 	const std::vector<EditFile> &editData) const | ||||
| -> base::optional<File> { | ||||
| 	switch (data.type()) { | ||||
| 	case mtpc_secureFileEmpty: | ||||
| 		return base::none; | ||||
| 
 | ||||
| 	case mtpc_secureFile: { | ||||
| 		const auto &fields = data.c_secureFile(); | ||||
| 		auto result = File(); | ||||
| 		result.id = fields.vid.v; | ||||
| 		result.accessHash = fields.vaccess_hash.v; | ||||
| 		result.size = fields.vsize.v; | ||||
| 		result.date = fields.vdate.v; | ||||
| 		result.dcId = fields.vdc_id.v; | ||||
| 		result.hash = bytes::make_vector(fields.vfile_hash.v); | ||||
| 		result.encryptedSecret = bytes::make_vector(fields.vsecret.v); | ||||
| 		fillDownloadedFile(result, editData); | ||||
| 		return result; | ||||
| 	} break; | ||||
| 	} | ||||
| 	Unexpected("Type in FormController::parseFile."); | ||||
| } | ||||
| 
 | ||||
| void FormController::fillDownloadedFile( | ||||
| 		File &destination, | ||||
| 		const std::vector<EditFile> &source) const { | ||||
|  | @ -1180,6 +1257,9 @@ auto FormController::parseValue( | |||
| 	if (data.has_files()) { | ||||
| 		result.files = parseFiles(data.vfiles.v); | ||||
| 	} | ||||
| 	if (data.has_selfie()) { | ||||
| 		result.selfie = parseFile(data.vselfie); | ||||
| 	} | ||||
| 	if (data.has_plain_data()) { | ||||
| 		switch (data.vplain_data.type()) { | ||||
| 		case mtpc_securePlainPhone: { | ||||
|  | @ -1197,35 +1277,53 @@ auto FormController::parseValue( | |||
| } | ||||
| 
 | ||||
| auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* { | ||||
| 	const auto found = [&](const EditFile &file) { | ||||
| 		return (file.uploadData && file.uploadData->fullId == fullId); | ||||
| 	}; | ||||
| 	for (auto &[type, value] : _form.values) { | ||||
| 		for (auto &file : value.filesInEdit) { | ||||
| 			if (file.uploadData && file.uploadData->fullId == fullId) { | ||||
| 			if (found(file)) { | ||||
| 				return &file; | ||||
| 			} | ||||
| 		} | ||||
| 		if (value.selfieInEdit && found(*value.selfieInEdit)) { | ||||
| 			return &*value.selfieInEdit; | ||||
| 		} | ||||
| 	} | ||||
| 	return nullptr; | ||||
| } | ||||
| 
 | ||||
| auto FormController::findEditFile(const FileKey &key) -> EditFile* { | ||||
| 	const auto found = [&](const EditFile &file) { | ||||
| 		return (file.fields.dcId == key.dcId && file.fields.id == key.id); | ||||
| 	}; | ||||
| 	for (auto &[type, value] : _form.values) { | ||||
| 		for (auto &file : value.filesInEdit) { | ||||
| 			if (file.fields.dcId == key.dcId && file.fields.id == key.id) { | ||||
| 			if (found(file)) { | ||||
| 				return &file; | ||||
| 			} | ||||
| 		} | ||||
| 		if (value.selfieInEdit && found(*value.selfieInEdit)) { | ||||
| 			return &*value.selfieInEdit; | ||||
| 		} | ||||
| 	} | ||||
| 	return nullptr; | ||||
| } | ||||
| 
 | ||||
| auto FormController::findFile(const FileKey &key) | ||||
| -> std::pair<Value*, File*> { | ||||
| 	const auto found = [&](const File &file) { | ||||
| 		return (file.dcId == key.dcId) && (file.id == key.id); | ||||
| 	}; | ||||
| 	for (auto &[type, value] : _form.values) { | ||||
| 		for (auto &file : value.files) { | ||||
| 			if (file.dcId == key.dcId && file.id == key.id) { | ||||
| 			if (found(file)) { | ||||
| 				return { &value, &file }; | ||||
| 			} | ||||
| 		} | ||||
| 		if (value.selfie && found(*value.selfie)) { | ||||
| 			return { &value, &*value.selfie }; | ||||
| 		} | ||||
| 	} | ||||
| 	return { nullptr, nullptr }; | ||||
| } | ||||
|  |  | |||
|  | @ -212,6 +212,9 @@ public: | |||
| 	void uploadScan(not_null<const Value*> value, QByteArray &&content); | ||||
| 	void deleteScan(not_null<const Value*> value, int fileIndex); | ||||
| 	void restoreScan(not_null<const Value*> value, int fileIndex); | ||||
| 	void uploadSelfie(not_null<const Value*> value, QByteArray &&content); | ||||
| 	void deleteSelfie(not_null<const Value*> value); | ||||
| 	void restoreSelfie(not_null<const Value*> value); | ||||
| 
 | ||||
| 	rpl::producer<> secretReadyEvents() const; | ||||
| 
 | ||||
|  | @ -257,6 +260,9 @@ private: | |||
| 	std::vector<File> parseFiles( | ||||
| 		const QVector<MTPSecureFile> &data, | ||||
| 		const std::vector<EditFile> &editData = {}) const; | ||||
| 	base::optional<File> parseFile( | ||||
| 		const MTPSecureFile &data, | ||||
| 		const std::vector<EditFile> &editData = {}) const; | ||||
| 	void fillDownloadedFile( | ||||
| 		File &destination, | ||||
| 		const std::vector<EditFile> &source) const; | ||||
|  | @ -275,25 +281,33 @@ private: | |||
| 	bool validateValueSecrets(Value &value); | ||||
| 	void resetValue(Value &value); | ||||
| 
 | ||||
| 	void loadFiles(std::vector<File> &files); | ||||
| 	void loadFile(File &file); | ||||
| 	void fileLoadDone(FileKey key, const QByteArray &bytes); | ||||
| 	void fileLoadProgress(FileKey key, int offset); | ||||
| 	void fileLoadFail(FileKey key); | ||||
| 	void generateSecret(bytes::const_span password); | ||||
| 
 | ||||
| 	void subscribeToUploader(); | ||||
| 	void encryptScan( | ||||
| 		not_null<Value*> value, | ||||
| 		int fileIndex, | ||||
| 		QByteArray &&content); | ||||
| 	void uploadEncryptedScan( | ||||
| 		not_null<Value*> value, | ||||
| 		int fileIndex, | ||||
| 	void encryptFile( | ||||
| 		EditFile &file, | ||||
| 		QByteArray &&content, | ||||
| 		base::lambda<void(UploadScanData &&result)> callback); | ||||
| 	void prepareFile( | ||||
| 		EditFile &file, | ||||
| 		const QByteArray &content); | ||||
| 	void uploadEncryptedFile( | ||||
| 		EditFile &file, | ||||
| 		UploadScanData &&data); | ||||
| 	void scanUploadDone(const Storage::UploadSecureDone &data); | ||||
| 	void scanUploadProgress(const Storage::UploadSecureProgress &data); | ||||
| 	void scanUploadFail(const FullMsgId &fullId); | ||||
| 	void scanDeleteRestore(not_null<const Value*> value, int fileIndex, bool deleted); | ||||
| 	void scanDeleteRestore( | ||||
| 		not_null<const Value*> value, | ||||
| 		int fileIndex, | ||||
| 		bool deleted); | ||||
| 	void selfieDeleteRestore( | ||||
| 		not_null<const Value*> value, | ||||
| 		bool deleted); | ||||
| 
 | ||||
| 	QString getPhoneFromValue(not_null<const Value*> value) const; | ||||
| 	QString getEmailFromValue(not_null<const Value*> value) const; | ||||
|  |  | |||
|  | @ -356,7 +356,8 @@ QString PanelController::defaultPhoneNumber() const { | |||
| 
 | ||||
| void PanelController::uploadScan(QByteArray &&content) { | ||||
| 	Expects(_editScope != nullptr); | ||||
| 	Expects(_editScopeFilesIndex >= 0); | ||||
| 	Expects(_editScopeFilesIndex >= 0 | ||||
| 		&& _editScopeFilesIndex < _editScope->files.size()); | ||||
| 
 | ||||
| 	_form->uploadScan( | ||||
| 		_editScope->files[_editScopeFilesIndex], | ||||
|  | @ -365,7 +366,8 @@ void PanelController::uploadScan(QByteArray &&content) { | |||
| 
 | ||||
| void PanelController::deleteScan(int fileIndex) { | ||||
| 	Expects(_editScope != nullptr); | ||||
| 	Expects(_editScopeFilesIndex >= 0); | ||||
| 	Expects(_editScopeFilesIndex >= 0 | ||||
| 		&& _editScopeFilesIndex < _editScope->files.size()); | ||||
| 
 | ||||
| 	_form->deleteScan( | ||||
| 		_editScope->files[_editScopeFilesIndex], | ||||
|  | @ -374,13 +376,45 @@ void PanelController::deleteScan(int fileIndex) { | |||
| 
 | ||||
| void PanelController::restoreScan(int fileIndex) { | ||||
| 	Expects(_editScope != nullptr); | ||||
| 	Expects(_editScopeFilesIndex >= 0); | ||||
| 	Expects(_editScopeFilesIndex >= 0 | ||||
| 		&& _editScopeFilesIndex < _editScope->files.size()); | ||||
| 
 | ||||
| 	_form->restoreScan( | ||||
| 		_editScope->files[_editScopeFilesIndex], | ||||
| 		fileIndex); | ||||
| } | ||||
| 
 | ||||
| void PanelController::uploadSelfie(QByteArray &&content) { | ||||
| 	Expects(_editScope != nullptr); | ||||
| 	Expects(_editScopeFilesIndex >= 0 | ||||
| 		&& _editScopeFilesIndex < _editScope->files.size()); | ||||
| 	Expects(_editScope->selfieRequired); | ||||
| 
 | ||||
| 	_form->uploadSelfie( | ||||
| 		_editScope->files[_editScopeFilesIndex], | ||||
| 		std::move(content)); | ||||
| } | ||||
| 
 | ||||
| void PanelController::deleteSelfie() { | ||||
| 	Expects(_editScope != nullptr); | ||||
| 	Expects(_editScopeFilesIndex >= 0 | ||||
| 		&& _editScopeFilesIndex < _editScope->files.size()); | ||||
| 	Expects(_editScope->selfieRequired); | ||||
| 
 | ||||
| 	_form->deleteSelfie( | ||||
| 		_editScope->files[_editScopeFilesIndex]); | ||||
| } | ||||
| 
 | ||||
| void PanelController::restoreSelfie() { | ||||
| 	Expects(_editScope != nullptr); | ||||
| 	Expects(_editScopeFilesIndex >= 0 | ||||
| 		&& _editScopeFilesIndex < _editScope->files.size()); | ||||
| 	Expects(_editScope->selfieRequired); | ||||
| 
 | ||||
| 	_form->restoreSelfie( | ||||
| 		_editScope->files[_editScopeFilesIndex]); | ||||
| } | ||||
| 
 | ||||
| rpl::producer<ScanInfo> PanelController::scanUpdated() const { | ||||
| 	return _form->scanUpdated( | ||||
| 	) | rpl::filter([=](not_null<const EditFile*> file) { | ||||
|  | @ -422,11 +456,17 @@ ScanInfo PanelController::collectScanInfo(const EditFile &file) const { | |||
| 			return formatDownloadText(0, file.fields.size); | ||||
| 		} | ||||
| 	}(); | ||||
| 	auto isSelfie = (_editScope != nullptr) | ||||
| 		&& (_editScopeFilesIndex >= 0) | ||||
| 		&& (file.value == _editScope->files[_editScopeFilesIndex]) | ||||
| 		&& (_editScope->files[_editScopeFilesIndex]->selfieInEdit.has_value()) | ||||
| 		&& (&file == &*_editScope->files[_editScopeFilesIndex]->selfieInEdit); | ||||
| 	return { | ||||
| 		FileKey{ file.fields.id, file.fields.dcId }, | ||||
| 		status, | ||||
| 		file.fields.image, | ||||
| 		file.deleted }; | ||||
| 		file.deleted, | ||||
| 		isSelfie }; | ||||
| } | ||||
| 
 | ||||
| QString PanelController::getDefaultContactValue(Scope::Type type) const { | ||||
|  | @ -594,7 +634,10 @@ void PanelController::editScope(int index, int filesIndex) { | |||
| 						_editScope->files[_editScopeFilesIndex]->type), | ||||
| 					_editScope->fields->data.parsedInEdit, | ||||
| 					_editScope->files[_editScopeFilesIndex]->data.parsedInEdit, | ||||
| 					valueFiles(*_editScope->files[_editScopeFilesIndex])) | ||||
| 					valueFiles(*_editScope->files[_editScopeFilesIndex]), | ||||
| 					(_editScope->selfieRequired | ||||
| 						? valueSelfie(*_editScope->files[_editScopeFilesIndex]) | ||||
| 						: nullptr)) | ||||
| 				: object_ptr<PanelEditDocument>( | ||||
| 					_panel.get(), | ||||
| 					this, | ||||
|  | @ -735,6 +778,15 @@ std::vector<ScanInfo> PanelController::valueFiles( | |||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<ScanInfo> PanelController::valueSelfie( | ||||
| 		const Value &value) const { | ||||
| 	if (value.selfieInEdit) { | ||||
| 		return std::make_unique<ScanInfo>( | ||||
| 			collectScanInfo(*value.selfieInEdit)); | ||||
| 	} | ||||
| 	return std::make_unique<ScanInfo>(); | ||||
| } | ||||
| 
 | ||||
| void PanelController::cancelValueEdit() { | ||||
| 	if (const auto scope = base::take(_editScope)) { | ||||
| 		_form->cancelValueEdit(scope->fields); | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ struct ScanInfo { | |||
| 	QString status; | ||||
| 	QImage thumb; | ||||
| 	bool deleted = false; | ||||
| 	bool selfie = false; | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
|  | @ -54,6 +55,9 @@ public: | |||
| 	void uploadScan(QByteArray &&content); | ||||
| 	void deleteScan(int fileIndex); | ||||
| 	void restoreScan(int fileIndex); | ||||
| 	void uploadSelfie(QByteArray &&content); | ||||
| 	void deleteSelfie(); | ||||
| 	void restoreSelfie(); | ||||
| 	rpl::producer<ScanInfo> scanUpdated() const; | ||||
| 
 | ||||
| 	QString defaultEmail() const; | ||||
|  | @ -92,6 +96,7 @@ private: | |||
| 	void requestScopeFilesType(int index); | ||||
| 	void cancelValueEdit(); | ||||
| 	std::vector<ScanInfo> valueFiles(const Value &value) const; | ||||
| 	std::unique_ptr<ScanInfo> valueSelfie(const Value &value) const; | ||||
| 	void processValueSaveFinished(not_null<const Value*> value); | ||||
| 	void processVerificationNeeded(not_null<const Value*> value); | ||||
| 
 | ||||
|  |  | |||
|  | @ -135,7 +135,8 @@ PanelEditDocument::PanelEditDocument( | |||
| 	Scheme scheme, | ||||
| 	const ValueMap &data, | ||||
| 	const ValueMap &scanData, | ||||
| 	std::vector<ScanInfo> &&files) | ||||
| 	std::vector<ScanInfo> &&files, | ||||
| 	std::unique_ptr<ScanInfo> &&selfie) | ||||
| : _controller(controller) | ||||
| , _scheme(std::move(scheme)) | ||||
| , _scroll(this, st::passportPanelScroll) | ||||
|  | @ -145,7 +146,7 @@ PanelEditDocument::PanelEditDocument( | |||
| 		this, | ||||
| 		langFactory(lng_passport_save_value), | ||||
| 		st::passportPanelSaveValue) { | ||||
| 	setupControls(data, &scanData, std::move(files)); | ||||
| 	setupControls(data, &scanData, std::move(files), std::move(selfie)); | ||||
| } | ||||
| 
 | ||||
| PanelEditDocument::PanelEditDocument( | ||||
|  | @ -162,14 +163,19 @@ PanelEditDocument::PanelEditDocument( | |||
| 		this, | ||||
| 		langFactory(lng_passport_save_value), | ||||
| 		st::passportPanelSaveValue) { | ||||
| 	setupControls(data, nullptr, {}); | ||||
| 	setupControls(data, nullptr, {}, nullptr); | ||||
| } | ||||
| 
 | ||||
| void PanelEditDocument::setupControls( | ||||
| 		const ValueMap &data, | ||||
| 		const ValueMap *scanData, | ||||
| 		std::vector<ScanInfo> &&files) { | ||||
| 	const auto inner = setupContent(data, scanData, std::move(files)); | ||||
| 		std::vector<ScanInfo> &&files, | ||||
| 		std::unique_ptr<ScanInfo> &&selfie) { | ||||
| 	const auto inner = setupContent( | ||||
| 		data, | ||||
| 		scanData, | ||||
| 		std::move(files), | ||||
| 		std::move(selfie)); | ||||
| 
 | ||||
| 	using namespace rpl::mappers; | ||||
| 
 | ||||
|  | @ -185,7 +191,8 @@ void PanelEditDocument::setupControls( | |||
| not_null<Ui::RpWidget*> PanelEditDocument::setupContent( | ||||
| 		const ValueMap &data, | ||||
| 		const ValueMap *scanData, | ||||
| 		std::vector<ScanInfo> &&files) { | ||||
| 		std::vector<ScanInfo> &&files, | ||||
| 		std::unique_ptr<ScanInfo> &&selfie) { | ||||
| 	const auto inner = _scroll->setOwnedWidget( | ||||
| 		object_ptr<Ui::VerticalLayout>(this)); | ||||
| 	_scroll->widthValue( | ||||
|  | @ -199,12 +206,14 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent( | |||
| 				inner, | ||||
| 				_controller, | ||||
| 				_scheme.scansHeader, | ||||
| 				std::move(files))); | ||||
| 				std::move(files), | ||||
| 				std::move(selfie))); | ||||
| 	} else { | ||||
| 		inner->add(object_ptr<BoxContentDivider>( | ||||
| 			inner, | ||||
| 			st::passportFormDividerHeight)); | ||||
| 	} | ||||
| 
 | ||||
| 	inner->add(object_ptr<BoxContentDivider>( | ||||
| 		inner, | ||||
| 		st::passportFormDividerHeight)); | ||||
| 	inner->add( | ||||
| 		object_ptr<Ui::FlatLabel>( | ||||
| 			inner, | ||||
|  |  | |||
|  | @ -52,7 +52,8 @@ public: | |||
| 		Scheme scheme, | ||||
| 		const ValueMap &data, | ||||
| 		const ValueMap &scanData, | ||||
| 		std::vector<ScanInfo> &&files); | ||||
| 		std::vector<ScanInfo> &&files, | ||||
| 		std::unique_ptr<ScanInfo> &&selfie); | ||||
| 	PanelEditDocument( | ||||
| 		QWidget *parent, | ||||
| 		not_null<PanelController*> controller, | ||||
|  | @ -70,11 +71,13 @@ private: | |||
| 	void setupControls( | ||||
| 		const ValueMap &data, | ||||
| 		const ValueMap *scanData, | ||||
| 		std::vector<ScanInfo> &&files); | ||||
| 		std::vector<ScanInfo> &&files, | ||||
| 		std::unique_ptr<ScanInfo> &&selfie); | ||||
| 	not_null<Ui::RpWidget*> setupContent( | ||||
| 		const ValueMap &data, | ||||
| 		const ValueMap *scanData, | ||||
| 		std::vector<ScanInfo> &&files); | ||||
| 		std::vector<ScanInfo> &&files, | ||||
| 		std::unique_ptr<ScanInfo> &&selfie); | ||||
| 	void updateControlsGeometry(); | ||||
| 
 | ||||
| 	Result collect() const; | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | |||
| #include "passport/passport_panel_edit_scans.h" | ||||
| 
 | ||||
| #include "passport/passport_panel_controller.h" | ||||
| #include "passport/passport_panel_details_row.h" | ||||
| #include "info/profile/info_profile_button.h" | ||||
| #include "info/profile/info_profile_values.h" | ||||
| #include "ui/widgets/buttons.h" | ||||
|  | @ -185,10 +186,12 @@ EditScans::EditScans( | |||
| 	QWidget *parent, | ||||
| 	not_null<PanelController*> controller, | ||||
| 	const QString &header, | ||||
| 	std::vector<ScanInfo> &&files) | ||||
| 	std::vector<ScanInfo> &&files, | ||||
| 	std::unique_ptr<ScanInfo> &&selfie) | ||||
| : RpWidget(parent) | ||||
| , _controller(controller) | ||||
| , _files(std::move(files)) | ||||
| , _selfie(std::move(selfie)) | ||||
| , _content(this) { | ||||
| 	setupContent(header); | ||||
| } | ||||
|  | @ -233,6 +236,48 @@ void EditScans::setupContent(const QString &header) { | |||
| 	_upload->addClickHandler([=] { | ||||
| 		chooseScan(); | ||||
| 	}); | ||||
| 
 | ||||
| 	inner->add(object_ptr<BoxContentDivider>( | ||||
| 		inner, | ||||
| 		st::passportFormDividerHeight)); | ||||
| 
 | ||||
| 	if (_selfie) { | ||||
| 		_selfieHeader = inner->add( | ||||
| 			object_ptr<Ui::SlideWrap<Ui::FlatLabel>>( | ||||
| 				inner, | ||||
| 				object_ptr<Ui::FlatLabel>( | ||||
| 					inner, | ||||
| 					lang(lng_passport_selfie_title), | ||||
| 					Ui::FlatLabel::InitType::Simple, | ||||
| 					st::passportFormHeader), | ||||
| 				st::passportUploadHeaderPadding)); | ||||
| 		_selfieHeader->toggle(_selfie->key.id != 0, anim::type::instant); | ||||
| 		_selfieWrap = inner->add(object_ptr<Ui::VerticalLayout>(inner)); | ||||
| 		if (_selfie->key.id) { | ||||
| 			createSelfieRow(*_selfie); | ||||
| 		} | ||||
| 		_selfieUpload = inner->add( | ||||
| 			object_ptr<Info::Profile::Button>( | ||||
| 				inner, | ||||
| 				Lang::Viewer( | ||||
| 					lng_passport_upload_selfie | ||||
| 				) | Info::Profile::ToUpperValue(), | ||||
| 				st::passportUploadButton), | ||||
| 			st::passportUploadButtonPadding); | ||||
| 		_selfieUpload->addClickHandler([=] { | ||||
| 			chooseSelfie(); | ||||
| 		}); | ||||
| 
 | ||||
| 		inner->add(object_ptr<PanelLabel>( | ||||
| 			inner, | ||||
| 			object_ptr<Ui::FlatLabel>( | ||||
| 				_content, | ||||
| 				lang(lng_passport_selfie_description), | ||||
| 				Ui::FlatLabel::InitType::Simple, | ||||
| 				st::passportFormLabel), | ||||
| 			st::passportFormLabelPadding)); | ||||
| 	} | ||||
| 
 | ||||
| 	_controller->scanUpdated( | ||||
| 	) | rpl::start_with_next([=](ScanInfo &&info) { | ||||
| 		updateScan(std::move(info)); | ||||
|  | @ -250,11 +295,32 @@ void EditScans::setupContent(const QString &header) { | |||
| } | ||||
| 
 | ||||
| void EditScans::updateScan(ScanInfo &&info) { | ||||
| 	const auto updateRow = [&]( | ||||
| 			not_null<ScanButton*> button, | ||||
| 			const ScanInfo &info) { | ||||
| 		button->setStatus(info.status); | ||||
| 		button->setImage(info.thumb); | ||||
| 		button->setDeleted(info.deleted); | ||||
| 	}; | ||||
| 	if (info.selfie) { | ||||
| 		Assert(info.key.id != 0); | ||||
| 		Assert(_selfie != nullptr); | ||||
| 		if (_selfie->key.id) { | ||||
| 			updateRow(_selfieRow->entity(), info); | ||||
| 		} else { | ||||
| 			createSelfieRow(info); | ||||
| 			_selfieWrap->resizeToWidth(width()); | ||||
| 			_selfieRow->show(anim::type::normal); | ||||
| 			_selfieHeader->show(anim::type::normal); | ||||
| 		} | ||||
| 		*_selfie = std::move(info); | ||||
| 		return; | ||||
| 	} | ||||
| 	const auto i = ranges::find(_files, info.key, [](const ScanInfo &file) { | ||||
| 		return file.key; | ||||
| 	}); | ||||
| 	if (i != _files.end()) { | ||||
| 		*i = info; | ||||
| 		*i = std::move(info); | ||||
| 		const auto scan = _rows[i - _files.begin()]->entity(); | ||||
| 		scan->setStatus(i->status); | ||||
| 		scan->setImage(i->thumb); | ||||
|  | @ -270,20 +336,33 @@ void EditScans::updateScan(ScanInfo &&info) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| void EditScans::createSelfieRow(const ScanInfo &info) { | ||||
| 	_selfieRow = createScan( | ||||
| 		_selfieWrap, | ||||
| 		info, | ||||
| 		lang(lng_passport_selfie_name)); | ||||
| 	const auto row = _selfieRow->entity(); | ||||
| 
 | ||||
| 	row->deleteClicks( | ||||
| 	) | rpl::start_with_next([=] { | ||||
| 		_controller->deleteSelfie(); | ||||
| 	}, row->lifetime()); | ||||
| 
 | ||||
| 	row->restoreClicks( | ||||
| 	) | rpl::start_with_next([=] { | ||||
| 		_controller->restoreSelfie(); | ||||
| 	}, row->lifetime()); | ||||
| } | ||||
| 
 | ||||
| void EditScans::pushScan(const ScanInfo &info) { | ||||
| 	const auto index = _rows.size(); | ||||
| 	_rows.push_back(base::unique_qptr<Ui::SlideWrap<ScanButton>>( | ||||
| 		_wrap->add(object_ptr<Ui::SlideWrap<ScanButton>>( | ||||
| 			_wrap, | ||||
| 			object_ptr<ScanButton>( | ||||
| 				_wrap, | ||||
| 				st::passportScanRow, | ||||
| 				lng_passport_scan_index(lt_index, QString::number(index + 1)), | ||||
| 				info.status, | ||||
| 				info.deleted))))); | ||||
| 	_rows.push_back(createScan( | ||||
| 		_wrap, | ||||
| 		info, | ||||
| 		lng_passport_scan_index(lt_index, QString::number(index + 1)))); | ||||
| 	_rows.back()->hide(anim::type::instant); | ||||
| 
 | ||||
| 	const auto scan = _rows.back()->entity(); | ||||
| 	scan->setImage(info.thumb); | ||||
| 
 | ||||
| 	scan->deleteClicks( | ||||
| 	) | rpl::start_with_next([=] { | ||||
|  | @ -296,12 +375,35 @@ void EditScans::pushScan(const ScanInfo &info) { | |||
| 	}, scan->lifetime()); | ||||
| } | ||||
| 
 | ||||
| base::unique_qptr<Ui::SlideWrap<ScanButton>> EditScans::createScan( | ||||
| 		not_null<Ui::VerticalLayout*> parent, | ||||
| 		const ScanInfo &info, | ||||
| 		const QString &name) { | ||||
| 	auto result = base::unique_qptr<Ui::SlideWrap<ScanButton>>( | ||||
| 		parent->add(object_ptr<Ui::SlideWrap<ScanButton>>( | ||||
| 			parent, | ||||
| 			object_ptr<ScanButton>( | ||||
| 				parent, | ||||
| 				st::passportScanRow, | ||||
| 				name, | ||||
| 				info.status, | ||||
| 				info.deleted)))); | ||||
| 	result->entity()->setImage(info.thumb); | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| void EditScans::chooseScan() { | ||||
| 	ChooseScan(base::lambda_guarded(this, [=](QByteArray &&content) { | ||||
| 		_controller->uploadScan(std::move(content)); | ||||
| 	})); | ||||
| } | ||||
| 
 | ||||
| void EditScans::chooseSelfie() { | ||||
| 	ChooseScan(base::lambda_guarded(this, [=](QByteArray &&content) { | ||||
| 		_controller->uploadSelfie(std::move(content)); | ||||
| 	})); | ||||
| } | ||||
| 
 | ||||
| void EditScans::ChooseScan(base::lambda<void(QByteArray&&)> callback) { | ||||
| 	const auto filter = FileDialog::AllFilesFilter() | ||||
| 		+ qsl(";;Image files (*") | ||||
|  |  | |||
|  | @ -36,20 +36,28 @@ public: | |||
| 		QWidget *parent, | ||||
| 		not_null<PanelController*> controller, | ||||
| 		const QString &header, | ||||
| 		std::vector<ScanInfo> &&files); | ||||
| 		std::vector<ScanInfo> &&files, | ||||
| 		std::unique_ptr<ScanInfo> &&selfie); | ||||
| 
 | ||||
| 	static void ChooseScan(base::lambda<void(QByteArray&&)> callback); | ||||
| 
 | ||||
| private: | ||||
| 	void setupContent(const QString &header); | ||||
| 	void chooseScan(); | ||||
| 	void chooseSelfie(); | ||||
| 	void updateScan(ScanInfo &&info); | ||||
| 	void pushScan(const ScanInfo &info); | ||||
| 	void createSelfieRow(const ScanInfo &info); | ||||
| 	base::unique_qptr<Ui::SlideWrap<ScanButton>> createScan( | ||||
| 		not_null<Ui::VerticalLayout*> parent, | ||||
| 		const ScanInfo &info, | ||||
| 		const QString &name); | ||||
| 
 | ||||
| 	rpl::producer<QString> uploadButtonText() const; | ||||
| 
 | ||||
| 	not_null<PanelController*> _controller; | ||||
| 	std::vector<ScanInfo> _files; | ||||
| 	std::unique_ptr<ScanInfo> _selfie; | ||||
| 
 | ||||
| 	object_ptr<Ui::VerticalLayout> _content; | ||||
| 	QPointer<Ui::SlideWrap<BoxContentDivider>> _divider; | ||||
|  | @ -59,6 +67,11 @@ private: | |||
| 	QPointer<Info::Profile::Button> _upload; | ||||
| 	rpl::event_stream<rpl::producer<QString>> _uploadTexts; | ||||
| 
 | ||||
| 	QPointer<Ui::SlideWrap<Ui::FlatLabel>> _selfieHeader; | ||||
| 	QPointer<Ui::VerticalLayout> _selfieWrap; | ||||
| 	base::unique_qptr<Ui::SlideWrap<ScanButton>> _selfieRow; | ||||
| 	QPointer<Info::Profile::Button> _selfieUpload; | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| } // namespace Passport
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue