1479 lines
40 KiB
C++
1479 lines
40 KiB
C++
/*
|
|
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_panel_controller.h"
|
|
|
|
#include "main/main_account.h"
|
|
#include "main/main_session.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "passport/passport_panel_edit_document.h"
|
|
#include "passport/passport_panel_edit_contact.h"
|
|
#include "passport/passport_panel_edit_scans.h"
|
|
#include "passport/passport_panel.h"
|
|
#include "passport/ui/passport_details_row.h"
|
|
#include "base/unixtime.h"
|
|
#include "boxes/passcode_box.h"
|
|
#include "ui/boxes/confirm_box.h"
|
|
#include "window/window_session_controller.h"
|
|
#include "ui/toast/toast.h"
|
|
#include "ui/rp_widget.h"
|
|
#include "ui/countryinput.h"
|
|
#include "ui/text/format_values.h"
|
|
#include "ui/widgets/sent_code_field.h"
|
|
#include "core/update_checker.h"
|
|
#include "countries/countries_instance.h"
|
|
#include "styles/style_layers.h"
|
|
|
|
namespace Passport {
|
|
namespace {
|
|
|
|
constexpr auto kMaxNameSize = 255;
|
|
constexpr auto kMaxDocumentSize = 24;
|
|
constexpr auto kMaxStreetSize = 64;
|
|
constexpr auto kMinCitySize = 2;
|
|
constexpr auto kMaxCitySize = 64;
|
|
constexpr auto kMaxPostcodeSize = 10;
|
|
const auto kLanguageNamePrefix = "cloud_lng_passport_in_";
|
|
|
|
ScanInfo CollectScanInfo(const EditFile &file) {
|
|
const auto status = [&] {
|
|
if (file.fields.accessHash) {
|
|
switch (file.fields.downloadStatus.status()) {
|
|
case LoadStatus::Status::Failed:
|
|
return tr::lng_attach_failed(tr::now);
|
|
case LoadStatus::Status::InProgress:
|
|
return Ui::FormatDownloadText(
|
|
file.fields.downloadStatus.offset(),
|
|
file.fields.size);
|
|
case LoadStatus::Status::Done:
|
|
return tr::lng_passport_scan_uploaded(
|
|
tr::now,
|
|
lt_date,
|
|
langDateTimeFull(
|
|
base::unixtime::parse(file.fields.date)));
|
|
}
|
|
Unexpected("LoadStatus value in CollectScanInfo.");
|
|
} else if (file.uploadData) {
|
|
switch (file.uploadData->status.status()) {
|
|
case LoadStatus::Status::Failed:
|
|
return tr::lng_attach_failed(tr::now);
|
|
case LoadStatus::Status::InProgress:
|
|
return Ui::FormatDownloadText(
|
|
file.uploadData->status.offset(),
|
|
file.uploadData->bytes.size());
|
|
case LoadStatus::Status::Done:
|
|
return tr::lng_passport_scan_uploaded(
|
|
tr::now,
|
|
lt_date,
|
|
langDateTimeFull(
|
|
base::unixtime::parse(file.fields.date)));
|
|
}
|
|
Unexpected("LoadStatus value in CollectScanInfo.");
|
|
} else {
|
|
return Ui::FormatDownloadText(0, file.fields.size);
|
|
}
|
|
}();
|
|
return {
|
|
file.type,
|
|
FileKey{ file.fields.id },
|
|
!file.fields.error.isEmpty() ? file.fields.error : status,
|
|
file.fields.image,
|
|
file.deleted,
|
|
file.fields.error };
|
|
}
|
|
|
|
ScanListData PrepareScanListData(const Value &value, FileType type) {
|
|
auto result = ScanListData();
|
|
for (const auto &scan : value.filesInEdit(type)) {
|
|
result.files.push_back(CollectScanInfo(scan));
|
|
}
|
|
result.errorMissing = value.fileMissingError(type);
|
|
return result;
|
|
}
|
|
|
|
std::map<FileType, ScanInfo> PrepareSpecialFiles(const Value &value) {
|
|
auto result = std::map<FileType, ScanInfo>();
|
|
const auto types = {
|
|
FileType::FrontSide,
|
|
FileType::ReverseSide,
|
|
FileType::Selfie
|
|
};
|
|
for (const auto type : types) {
|
|
if (value.requiresSpecialScan(type)) {
|
|
const auto i = value.specialScansInEdit.find(type);
|
|
result.emplace(
|
|
type,
|
|
(i != end(value.specialScansInEdit)
|
|
? CollectScanInfo(i->second)
|
|
: ScanInfo(type)));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
EditDocumentScheme GetDocumentScheme(
|
|
Scope::Type type,
|
|
std::optional<Value::Type> scansType,
|
|
bool nativeNames,
|
|
preferredLangCallback &&preferredLanguage) {
|
|
using Scheme = EditDocumentScheme;
|
|
using ValueClass = Scheme::ValueClass;
|
|
const auto DontFormat = nullptr;
|
|
const auto CountryFormat = [](const QString &value) {
|
|
const auto result = Countries::Instance().countryNameByISO2(value);
|
|
return result.isEmpty() ? value : result;
|
|
};
|
|
const auto GenderFormat = [](const QString &value) {
|
|
if (value == u"male"_q) {
|
|
return tr::lng_passport_gender_male(tr::now);
|
|
} else if (value == u"female"_q) {
|
|
return tr::lng_passport_gender_female(tr::now);
|
|
}
|
|
return value;
|
|
};
|
|
const auto DontValidate = nullptr;
|
|
const auto FromBoolean = [](auto validation) {
|
|
return [=](const QString &value) {
|
|
return validation(value)
|
|
? std::nullopt
|
|
: base::make_optional(QString());
|
|
};
|
|
};
|
|
const auto LimitedValidate = [=](int max, int min = 1) {
|
|
return FromBoolean([=](const QString &value) {
|
|
return (value.size() >= min) && (value.size() <= max);
|
|
});
|
|
};
|
|
using Result = std::optional<QString>;
|
|
const auto NameValidate = [](const QString &value) -> Result {
|
|
if (value.isEmpty() || value.size() > kMaxNameSize) {
|
|
return QString();
|
|
} else if (!QRegularExpression(
|
|
"^[a-zA-Z0-9\\.,/&\\-' ]+$"
|
|
).match(value).hasMatch()) {
|
|
return tr::lng_passport_bad_name(tr::now);
|
|
}
|
|
return std::nullopt;
|
|
};
|
|
const auto NativeNameValidate = LimitedValidate(kMaxNameSize);
|
|
const auto NativeNameOrEmptyValidate = LimitedValidate(kMaxNameSize, 0);
|
|
const auto DocumentValidate = LimitedValidate(kMaxDocumentSize);
|
|
const auto StreetValidate = LimitedValidate(kMaxStreetSize);
|
|
const auto CityValidate = LimitedValidate(kMaxCitySize, kMinCitySize);
|
|
const auto PostcodeValidate = FromBoolean([](const QString &value) {
|
|
return QRegularExpression(
|
|
QString("^[a-zA-Z0-9\\-]{2,%1}$").arg(kMaxPostcodeSize)
|
|
).match(value).hasMatch();
|
|
});
|
|
const auto DateValidateBoolean = [](const QString &value) {
|
|
return QRegularExpression(
|
|
"^\\d{2}\\.\\d{2}\\.\\d{4}$"
|
|
).match(value).hasMatch();
|
|
};
|
|
const auto DateValidate = FromBoolean(DateValidateBoolean);
|
|
const auto DateOrEmptyValidate = FromBoolean([=](const QString &value) {
|
|
return value.isEmpty() || DateValidateBoolean(value);
|
|
});
|
|
const auto GenderValidate = FromBoolean([](const QString &value) {
|
|
return value == u"male"_q || value == u"female"_q;
|
|
});
|
|
const auto CountryValidate = FromBoolean([=](const QString &value) {
|
|
return !CountryFormat(value).isEmpty();
|
|
});
|
|
const auto NameOrEmptyValidate = [=](const QString &value) -> Result {
|
|
if (value.isEmpty()) {
|
|
return std::nullopt;
|
|
}
|
|
return NameValidate(value);
|
|
};
|
|
|
|
switch (type) {
|
|
case Scope::Type::PersonalDetails:
|
|
case Scope::Type::Identity: {
|
|
auto result = Scheme();
|
|
result.detailsHeader = tr::lng_passport_personal_details(tr::now);
|
|
result.fieldsHeader = tr::lng_passport_document_details(tr::now);
|
|
if (scansType) {
|
|
result.scansHeader = [&] {
|
|
switch (*scansType) {
|
|
case Value::Type::Passport:
|
|
return tr::lng_passport_identity_passport(tr::now);
|
|
case Value::Type::DriverLicense:
|
|
return tr::lng_passport_identity_license(tr::now);
|
|
case Value::Type::IdentityCard:
|
|
return tr::lng_passport_identity_card(tr::now);
|
|
case Value::Type::InternalPassport:
|
|
return tr::lng_passport_identity_internal(tr::now);
|
|
default:
|
|
Unexpected("scansType in GetDocumentScheme:Identity.");
|
|
}
|
|
}();
|
|
}
|
|
result.rows = {
|
|
{
|
|
ValueClass::Fields,
|
|
Ui::PanelDetailsType::Text,
|
|
"first_name"_q,
|
|
tr::lng_passport_first_name(tr::now),
|
|
NameValidate,
|
|
DontFormat,
|
|
kMaxNameSize,
|
|
},
|
|
{
|
|
ValueClass::Fields,
|
|
Ui::PanelDetailsType::Text,
|
|
"middle_name"_q,
|
|
tr::lng_passport_middle_name(tr::now),
|
|
NameOrEmptyValidate,
|
|
DontFormat,
|
|
kMaxNameSize,
|
|
"first_name"_q,
|
|
},
|
|
{
|
|
ValueClass::Fields,
|
|
Ui::PanelDetailsType::Text,
|
|
"last_name"_q,
|
|
tr::lng_passport_last_name(tr::now),
|
|
NameValidate,
|
|
DontFormat,
|
|
kMaxNameSize,
|
|
"first_name"_q,
|
|
},
|
|
{
|
|
ValueClass::Fields,
|
|
Ui::PanelDetailsType::Date,
|
|
"birth_date"_q,
|
|
tr::lng_passport_birth_date(tr::now),
|
|
DateValidate,
|
|
DontFormat,
|
|
},
|
|
{
|
|
ValueClass::Fields,
|
|
Ui::PanelDetailsType::Gender,
|
|
"gender"_q,
|
|
tr::lng_passport_gender(tr::now),
|
|
GenderValidate,
|
|
GenderFormat,
|
|
},
|
|
{
|
|
ValueClass::Fields,
|
|
Ui::PanelDetailsType::Country,
|
|
"country_code"_q,
|
|
tr::lng_passport_country(tr::now),
|
|
CountryValidate,
|
|
CountryFormat,
|
|
},
|
|
{
|
|
ValueClass::Fields,
|
|
Ui::PanelDetailsType::Country,
|
|
"residence_country_code"_q,
|
|
tr::lng_passport_residence_country(tr::now),
|
|
CountryValidate,
|
|
CountryFormat,
|
|
},
|
|
{
|
|
ValueClass::Scans,
|
|
Ui::PanelDetailsType::Text,
|
|
"document_no"_q,
|
|
tr::lng_passport_document_number(tr::now),
|
|
DocumentValidate,
|
|
DontFormat,
|
|
kMaxDocumentSize,
|
|
},
|
|
{
|
|
ValueClass::Scans,
|
|
Ui::PanelDetailsType::Date,
|
|
"expiry_date"_q,
|
|
tr::lng_passport_expiry_date(tr::now),
|
|
DateOrEmptyValidate,
|
|
DontFormat,
|
|
},
|
|
};
|
|
if (nativeNames) {
|
|
result.additionalDependencyKey = "residence_country_code"_q;
|
|
|
|
result.preferredLanguage = preferredLanguage
|
|
? std::move(preferredLanguage)
|
|
: [](const QString &) {
|
|
return rpl::single(EditDocumentCountry());
|
|
};
|
|
const auto languageValue = [](const QString &langCode) {
|
|
return Lang::GetNonDefaultValue(kLanguageNamePrefix
|
|
+ langCode.toUtf8());
|
|
};
|
|
result.additionalHeader = [=](const EditDocumentCountry &info) {
|
|
const auto language = languageValue(info.languageCode);
|
|
return language.isEmpty()
|
|
? tr::lng_passport_native_name_title(tr::now)
|
|
: tr::lng_passport_native_name_language(
|
|
tr::now,
|
|
lt_language,
|
|
language);
|
|
};
|
|
result.additionalDescription = [=](
|
|
const EditDocumentCountry &info) {
|
|
const auto language = languageValue(info.languageCode);
|
|
if (!language.isEmpty()) {
|
|
return tr::lng_passport_native_name_language_about(
|
|
tr::now);
|
|
}
|
|
const auto name = Countries::Instance().countryNameByISO2(
|
|
info.countryCode);
|
|
Assert(!name.isEmpty());
|
|
return tr::lng_passport_native_name_about(
|
|
tr::now,
|
|
lt_country,
|
|
name);
|
|
};
|
|
result.additionalShown = [](const EditDocumentCountry &info) {
|
|
using Result = EditDocumentScheme::AdditionalVisibility;
|
|
return (info.countryCode.isEmpty())
|
|
? Result::Hidden
|
|
: (info.languageCode == "en")
|
|
? Result::OnlyIfError
|
|
: Result::Shown;
|
|
};
|
|
using Row = EditDocumentScheme::Row;
|
|
auto additional = std::initializer_list<Row>{
|
|
{
|
|
ValueClass::Additional,
|
|
Ui::PanelDetailsType::Text,
|
|
"first_name_native"_q,
|
|
tr::lng_passport_first_name(tr::now),
|
|
NativeNameValidate,
|
|
DontFormat,
|
|
kMaxNameSize,
|
|
QString(),
|
|
"first_name"_q,
|
|
},
|
|
{
|
|
ValueClass::Additional,
|
|
Ui::PanelDetailsType::Text,
|
|
"middle_name_native"_q,
|
|
tr::lng_passport_middle_name(tr::now),
|
|
NativeNameOrEmptyValidate,
|
|
DontFormat,
|
|
kMaxNameSize,
|
|
"first_name_native"_q,
|
|
"middle_name"_q,
|
|
},
|
|
{
|
|
ValueClass::Additional,
|
|
Ui::PanelDetailsType::Text,
|
|
"last_name_native"_q,
|
|
tr::lng_passport_last_name(tr::now),
|
|
NativeNameValidate,
|
|
DontFormat,
|
|
kMaxNameSize,
|
|
"first_name_native"_q,
|
|
"last_name"_q,
|
|
},
|
|
};
|
|
for (auto &row : additional) {
|
|
result.rows.push_back(std::move(row));
|
|
}
|
|
}
|
|
return result;
|
|
} break;
|
|
|
|
case Scope::Type::AddressDetails:
|
|
case Scope::Type::Address: {
|
|
auto result = Scheme();
|
|
result.detailsHeader = tr::lng_passport_address(tr::now);
|
|
if (scansType) {
|
|
switch (*scansType) {
|
|
case Value::Type::UtilityBill:
|
|
result.scansHeader = tr::lng_passport_address_bill(tr::now);
|
|
break;
|
|
case Value::Type::BankStatement:
|
|
result.scansHeader = tr::lng_passport_address_statement(tr::now);
|
|
break;
|
|
case Value::Type::RentalAgreement:
|
|
result.scansHeader = tr::lng_passport_address_agreement(tr::now);
|
|
break;
|
|
case Value::Type::PassportRegistration:
|
|
result.scansHeader = tr::lng_passport_address_registration(tr::now);
|
|
break;
|
|
case Value::Type::TemporaryRegistration:
|
|
result.scansHeader = tr::lng_passport_address_temporary(tr::now);
|
|
break;
|
|
default:
|
|
Unexpected("scansType in GetDocumentScheme:Address.");
|
|
}
|
|
}
|
|
result.rows = {
|
|
{
|
|
ValueClass::Fields,
|
|
Ui::PanelDetailsType::Text,
|
|
"street_line1"_q,
|
|
tr::lng_passport_street(tr::now),
|
|
StreetValidate,
|
|
DontFormat,
|
|
kMaxStreetSize,
|
|
},
|
|
{
|
|
ValueClass::Fields,
|
|
Ui::PanelDetailsType::Text,
|
|
"street_line2"_q,
|
|
tr::lng_passport_street(tr::now),
|
|
DontValidate,
|
|
DontFormat,
|
|
kMaxStreetSize,
|
|
},
|
|
{
|
|
ValueClass::Fields,
|
|
Ui::PanelDetailsType::Text,
|
|
"city"_q,
|
|
tr::lng_passport_city(tr::now),
|
|
CityValidate,
|
|
DontFormat,
|
|
kMaxStreetSize,
|
|
},
|
|
{
|
|
ValueClass::Fields,
|
|
Ui::PanelDetailsType::Text,
|
|
"state"_q,
|
|
tr::lng_passport_state(tr::now),
|
|
DontValidate,
|
|
DontFormat,
|
|
kMaxStreetSize,
|
|
},
|
|
{
|
|
ValueClass::Fields,
|
|
Ui::PanelDetailsType::Country,
|
|
"country_code"_q,
|
|
tr::lng_passport_country(tr::now),
|
|
CountryValidate,
|
|
CountryFormat,
|
|
},
|
|
{
|
|
ValueClass::Fields,
|
|
Ui::PanelDetailsType::Postcode,
|
|
"post_code"_q,
|
|
tr::lng_passport_postcode(tr::now),
|
|
PostcodeValidate,
|
|
DontFormat,
|
|
kMaxPostcodeSize,
|
|
},
|
|
};
|
|
return result;
|
|
} break;
|
|
}
|
|
Unexpected("Type in GetDocumentScheme().");
|
|
}
|
|
|
|
EditContactScheme GetContactScheme(Scope::Type type) {
|
|
using Scheme = EditContactScheme;
|
|
using ValueType = Scheme::ValueType;
|
|
|
|
switch (type) {
|
|
case Scope::Type::Phone: {
|
|
auto result = Scheme(ValueType::Phone);
|
|
result.aboutExisting = tr::lng_passport_use_existing_phone(tr::now);
|
|
result.newHeader = tr::lng_passport_new_phone(tr::now);
|
|
result.aboutNew = tr::lng_passport_new_phone_code(tr::now);
|
|
result.validate = [](const QString &value) {
|
|
return QRegularExpression(
|
|
"^\\d{2,12}$"
|
|
).match(value).hasMatch();
|
|
};
|
|
result.format = [](const QString &value) {
|
|
return Ui::FormatPhone(value);
|
|
};
|
|
result.postprocess = [](QString value) {
|
|
return value.replace(
|
|
TextUtilities::RegExpDigitsExclude(),
|
|
QString());
|
|
};
|
|
return result;
|
|
} break;
|
|
|
|
case Scope::Type::Email: {
|
|
auto result = Scheme(ValueType::Text);
|
|
result.aboutExisting = tr::lng_passport_use_existing_email(tr::now);
|
|
result.newHeader = tr::lng_passport_new_email(tr::now);
|
|
result.newPlaceholder = tr::lng_passport_email_title();
|
|
result.aboutNew = tr::lng_passport_new_email_code(tr::now);
|
|
result.validate = [](const QString &value) {
|
|
const auto at = value.indexOf('@');
|
|
const auto dot = value.lastIndexOf('.');
|
|
return (at > 0) && (dot > at);
|
|
};
|
|
result.format = result.postprocess = [](const QString &value) {
|
|
return value.trimmed();
|
|
};
|
|
return result;
|
|
} break;
|
|
}
|
|
Unexpected("Type in GetContactScheme().");
|
|
}
|
|
|
|
const std::map<QString, QString> &LatinToNativeMap() {
|
|
static const auto result = std::map<QString, QString> {
|
|
{ "first_name"_q, "first_name_native"_q },
|
|
{ "last_name"_q, "last_name_native"_q },
|
|
{ "middle_name"_q, "middle_name_native"_q },
|
|
};
|
|
return result;
|
|
}
|
|
|
|
const std::map<QString, QString> &NativeToLatinMap() {
|
|
static const auto result = std::map<QString, QString> {
|
|
{ "first_name_native"_q, "first_name"_q },
|
|
{ "last_name_native"_q, "last_name"_q },
|
|
{ "middle_name_native"_q, "middle_name"_q },
|
|
};
|
|
return result;
|
|
}
|
|
|
|
QString AdjustKeyName(not_null<const Value*> value, const QString &key) {
|
|
if (!value->nativeNames) {
|
|
return key;
|
|
}
|
|
const auto &map = LatinToNativeMap();
|
|
const auto i = map.find(key);
|
|
return (i == end(map)) ? key : i->second;
|
|
}
|
|
|
|
bool SkipFieldCheck(not_null<const Value*> value, const QString &key) {
|
|
if (value->type != Value::Type::PersonalDetails) {
|
|
return false;
|
|
}
|
|
const auto &dontCheckNames = value->nativeNames
|
|
? LatinToNativeMap()
|
|
: NativeToLatinMap();
|
|
return dontCheckNames.find(key) != end(dontCheckNames);
|
|
}
|
|
|
|
ScanInfo::ScanInfo(FileType type) : type(type) {
|
|
}
|
|
|
|
ScanInfo::ScanInfo(
|
|
FileType type,
|
|
const FileKey &key,
|
|
const QString &status,
|
|
const QImage &thumb,
|
|
bool deleted,
|
|
const QString &error)
|
|
: type(type)
|
|
, key(key)
|
|
, status(status)
|
|
, thumb(thumb)
|
|
, deleted(deleted)
|
|
, error(error) {
|
|
}
|
|
|
|
PanelController::PanelController(not_null<FormController*> form)
|
|
: _form(form)
|
|
, _scopes(ComputeScopes(_form->form())) {
|
|
_form->secretReadyEvents(
|
|
) | rpl::start_with_next([=] {
|
|
ensurePanelCreated();
|
|
_panel->showForm();
|
|
}, lifetime());
|
|
|
|
_form->verificationNeeded(
|
|
) | rpl::start_with_next([=](not_null<const Value*> value) {
|
|
processVerificationNeeded(value);
|
|
}, lifetime());
|
|
|
|
_form->verificationUpdate(
|
|
) | rpl::filter([=](not_null<const Value*> field) {
|
|
return (field->verification.codeLength == 0);
|
|
}) | rpl::start_with_next([=](not_null<const Value*> field) {
|
|
_verificationBoxes.erase(field);
|
|
}, lifetime());
|
|
}
|
|
|
|
not_null<UserData*> PanelController::bot() const {
|
|
return _form->bot();
|
|
}
|
|
|
|
QString PanelController::privacyPolicyUrl() const {
|
|
return _form->privacyPolicyUrl();
|
|
}
|
|
|
|
void PanelController::fillRows(
|
|
Fn<void(
|
|
QString title,
|
|
QString description,
|
|
bool ready,
|
|
bool error)> callback) {
|
|
if (_scopes.empty()) {
|
|
_scopes = ComputeScopes(_form->form());
|
|
}
|
|
for (const auto &scope : _scopes) {
|
|
const auto row = ComputeScopeRow(scope);
|
|
const auto main = scope.details
|
|
? not_null<const Value*>(scope.details)
|
|
: scope.documents[0];
|
|
if (main && !row.ready.isEmpty()) {
|
|
_submitErrors.erase(
|
|
ranges::remove(_submitErrors, main),
|
|
_submitErrors.end());
|
|
}
|
|
const auto submitError = base::contains(_submitErrors, main);
|
|
callback(
|
|
row.title,
|
|
(!row.error.isEmpty()
|
|
? row.error
|
|
: !row.ready.isEmpty()
|
|
? row.ready
|
|
: row.description),
|
|
!row.ready.isEmpty(),
|
|
!row.error.isEmpty() || submitError);
|
|
}
|
|
}
|
|
|
|
rpl::producer<> PanelController::refillRows() const {
|
|
return rpl::merge(
|
|
_submitFailed.events(),
|
|
_form->valueSaveFinished() | rpl::to_empty);
|
|
}
|
|
|
|
void PanelController::submitForm() {
|
|
_submitErrors = _form->submitGetErrors();
|
|
if (!_submitErrors.empty()) {
|
|
_submitFailed.fire({});
|
|
}
|
|
}
|
|
|
|
void PanelController::submitPassword(const QByteArray &password) {
|
|
_form->submitPassword(password);
|
|
}
|
|
|
|
void PanelController::recoverPassword() {
|
|
_form->recoverPassword();
|
|
}
|
|
|
|
rpl::producer<QString> PanelController::passwordError() const {
|
|
return _form->passwordError();
|
|
}
|
|
|
|
QString PanelController::passwordHint() const {
|
|
return _form->passwordSettings().hint;
|
|
}
|
|
|
|
QString PanelController::unconfirmedEmailPattern() const {
|
|
return _form->passwordSettings().unconfirmedPattern;
|
|
}
|
|
|
|
QString PanelController::defaultEmail() const {
|
|
return _form->defaultEmail();
|
|
}
|
|
|
|
QString PanelController::defaultPhoneNumber() const {
|
|
return _form->defaultPhoneNumber();
|
|
}
|
|
|
|
void PanelController::setupPassword() {
|
|
Expects(_panel != nullptr);
|
|
|
|
const auto &settings = _form->passwordSettings();
|
|
if (settings.unknownAlgo
|
|
|| v::is_null(settings.newAlgo)
|
|
|| v::is_null(settings.newSecureAlgo)) {
|
|
showUpdateAppBox();
|
|
return;
|
|
} else if (settings.request) {
|
|
showAskPassword();
|
|
return;
|
|
}
|
|
|
|
auto fields = PasscodeBox::CloudFields{
|
|
.mtp = PasscodeBox::CloudFields::Mtp{
|
|
.newAlgo = settings.newAlgo,
|
|
.newSecureSecretAlgo = settings.newSecureAlgo,
|
|
},
|
|
.hasRecovery = settings.hasRecovery,
|
|
.pendingResetDate = settings.pendingResetDate,
|
|
};
|
|
|
|
// MSVC x64 (non-LTO) Release build fails with a linker error:
|
|
// - unresolved external variant::variant(variant const &)
|
|
// It looks like a MSVC bug and this works like a workaround.
|
|
const auto force = fields.mtp.newSecureSecretAlgo;
|
|
|
|
auto box = show(Box<PasscodeBox>(&_form->window()->session(), fields));
|
|
box->newPasswordSet(
|
|
) | rpl::start_with_next([=](const QByteArray &password) {
|
|
if (password.isEmpty()) {
|
|
_form->reloadPassword();
|
|
} else {
|
|
_form->reloadAndSubmitPassword(password);
|
|
}
|
|
}, box->lifetime());
|
|
|
|
box->passwordReloadNeeded(
|
|
) | rpl::start_with_next([=] {
|
|
_form->reloadPassword();
|
|
}, box->lifetime());
|
|
|
|
box->clearUnconfirmedPassword(
|
|
) | rpl::start_with_next([=] {
|
|
_form->cancelPassword();
|
|
}, box->lifetime());
|
|
}
|
|
|
|
void PanelController::cancelPasswordSubmit() {
|
|
show(Ui::MakeConfirmBox({
|
|
.text = tr::lng_passport_stop_password_sure(),
|
|
.confirmed = [=](Fn<void()> &&close) {
|
|
close();
|
|
_form->cancelPassword();
|
|
},
|
|
.confirmText = tr::lng_passport_stop(),
|
|
}));
|
|
}
|
|
|
|
void PanelController::validateRecoveryEmail() {
|
|
auto validation = ConfirmRecoveryEmail(
|
|
&_form->session(),
|
|
unconfirmedEmailPattern());
|
|
|
|
std::move(
|
|
validation.reloadRequests
|
|
) | rpl::start_with_next([=] {
|
|
_form->reloadPassword();
|
|
}, validation.box->lifetime());
|
|
|
|
std::move(
|
|
validation.cancelRequests
|
|
) | rpl::start_with_next([=] {
|
|
_form->cancelPassword();
|
|
}, validation.box->lifetime());
|
|
|
|
show(std::move(validation.box));
|
|
}
|
|
|
|
bool PanelController::canAddScan(FileType type) const {
|
|
Expects(_editScope != nullptr);
|
|
Expects(_editDocument != nullptr);
|
|
|
|
return _form->canAddScan(_editDocument, type);
|
|
}
|
|
|
|
void PanelController::uploadScan(FileType type, QByteArray &&content) {
|
|
Expects(_editScope != nullptr);
|
|
Expects(_editDocument != nullptr);
|
|
Expects(_editDocument->requiresScan(type));
|
|
|
|
_form->uploadScan(_editDocument, type, std::move(content));
|
|
}
|
|
|
|
void PanelController::deleteScan(
|
|
FileType type,
|
|
std::optional<int> fileIndex) {
|
|
Expects(_editScope != nullptr);
|
|
Expects(_editDocument != nullptr);
|
|
Expects(_editDocument->requiresScan(type));
|
|
|
|
_form->deleteScan(_editDocument, type, fileIndex);
|
|
}
|
|
|
|
void PanelController::restoreScan(
|
|
FileType type,
|
|
std::optional<int> fileIndex) {
|
|
Expects(_editScope != nullptr);
|
|
Expects(_editDocument != nullptr);
|
|
Expects(_editDocument->requiresScan(type));
|
|
|
|
_form->restoreScan(_editDocument, type, fileIndex);
|
|
}
|
|
|
|
rpl::producer<ScanInfo> PanelController::scanUpdated() const {
|
|
return _form->scanUpdated(
|
|
) | rpl::filter([=](not_null<const EditFile*> file) {
|
|
return (file->value == _editDocument);
|
|
}) | rpl::map([](not_null<const EditFile*> file) {
|
|
return CollectScanInfo(*file);
|
|
});
|
|
}
|
|
|
|
rpl::producer<ScopeError> PanelController::saveErrors() const {
|
|
return _saveErrors.events();
|
|
}
|
|
|
|
std::vector<ScopeError> PanelController::collectSaveErrors(
|
|
not_null<const Value*> value) const {
|
|
auto result = std::vector<ScopeError>();
|
|
for (const auto &[key, value] : value->data.parsedInEdit.fields) {
|
|
if (!value.error.isEmpty()) {
|
|
result.push_back({ key, value.error });
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
auto PanelController::deleteValueLabel() const
|
|
-> std::optional<rpl::producer<QString>> {
|
|
Expects(_editScope != nullptr);
|
|
|
|
if (hasValueDocument()) {
|
|
return tr::lng_passport_delete_document();
|
|
} else if (!hasValueFields()) {
|
|
return std::nullopt;
|
|
}
|
|
switch (_editScope->type) {
|
|
case Scope::Type::PersonalDetails:
|
|
case Scope::Type::Identity:
|
|
return tr::lng_passport_delete_details();
|
|
case Scope::Type::AddressDetails:
|
|
case Scope::Type::Address:
|
|
return tr::lng_passport_delete_address();
|
|
case Scope::Type::Email:
|
|
return tr::lng_passport_delete_email();
|
|
case Scope::Type::Phone:
|
|
return tr::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->files(FileType::Scan).empty()
|
|
|| !_editDocument->files(FileType::Translation).empty()
|
|
|| !_editDocument->specialScans.empty();
|
|
}
|
|
|
|
bool PanelController::hasValueFields() const {
|
|
return _editValue && !_editValue->data.parsed.fields.empty();
|
|
}
|
|
|
|
void PanelController::deleteValue() {
|
|
Expects(_editScope != nullptr);
|
|
Expects(hasValueDocument() || hasValueFields());
|
|
|
|
if (savingScope()) {
|
|
return;
|
|
}
|
|
const auto text = [&] {
|
|
switch (_editScope->type) {
|
|
case Scope::Type::PersonalDetails:
|
|
return tr::lng_passport_delete_details_sure(tr::now);
|
|
case Scope::Type::Identity:
|
|
return tr::lng_passport_delete_document_sure(tr::now);
|
|
case Scope::Type::AddressDetails:
|
|
return tr::lng_passport_delete_address_sure(tr::now);
|
|
case Scope::Type::Address:
|
|
return tr::lng_passport_delete_document_sure(tr::now);
|
|
case Scope::Type::Phone:
|
|
return tr::lng_passport_delete_phone_sure(tr::now);
|
|
case Scope::Type::Email:
|
|
return tr::lng_passport_delete_email_sure(tr::now);
|
|
}
|
|
Unexpected("Type in deleteValue.");
|
|
}();
|
|
const auto checkbox = (hasValueDocument() && hasValueFields()) ? [&] {
|
|
switch (_editScope->type) {
|
|
case Scope::Type::Identity:
|
|
return tr::lng_passport_delete_details(tr::now);
|
|
case Scope::Type::Address:
|
|
return tr::lng_passport_delete_address(tr::now);
|
|
}
|
|
Unexpected("Type in deleteValue.");
|
|
}() : QString();
|
|
|
|
_editScopeBoxes.emplace_back(show(ConfirmDeleteDocument(
|
|
[=](bool withDetails) { deleteValueSure(withDetails); },
|
|
text,
|
|
checkbox)));
|
|
}
|
|
|
|
void PanelController::deleteValueSure(bool withDetails) {
|
|
Expects(!withDetails || _editValue != nullptr);
|
|
|
|
if (hasValueDocument()) {
|
|
_form->deleteValueEdit(_editDocument);
|
|
}
|
|
if (withDetails || !hasValueDocument()) {
|
|
_form->deleteValueEdit(_editValue);
|
|
}
|
|
}
|
|
|
|
void PanelController::suggestReset(Fn<void()> callback) {
|
|
_resetBox = Ui::BoxPointer(show(Ui::MakeConfirmBox({
|
|
.text = Lang::Hard::PassportCorrupted(),
|
|
.confirmed = [=] { resetPassport(callback); },
|
|
.cancelled = [=] { cancelReset(); },
|
|
.confirmText = Lang::Hard::PassportCorruptedReset(),
|
|
})).data());
|
|
}
|
|
|
|
void PanelController::resetPassport(Fn<void()> callback) {
|
|
const auto box = show(Ui::MakeConfirmBox({
|
|
.text = Lang::Hard::PassportCorruptedResetSure(),
|
|
.confirmed = [=] { base::take(_resetBox); callback(); },
|
|
.cancelled = [=] { suggestReset(callback); },
|
|
.confirmText = Lang::Hard::PassportCorruptedReset(),
|
|
.confirmStyle = &st::attentionBoxButton,
|
|
}));
|
|
_resetBox = Ui::BoxPointer(box.data());
|
|
}
|
|
|
|
void PanelController::cancelReset() {
|
|
const auto weak = base::take(_resetBox);
|
|
_form->cancelSure();
|
|
}
|
|
|
|
QString PanelController::getDefaultContactValue(Scope::Type type) const {
|
|
switch (type) {
|
|
case Scope::Type::Phone:
|
|
return _form->defaultPhoneNumber();
|
|
case Scope::Type::Email:
|
|
return _form->defaultEmail();
|
|
}
|
|
Unexpected("Type in PanelController::getDefaultContactValue().");
|
|
}
|
|
|
|
void PanelController::showAskPassword() {
|
|
ensurePanelCreated();
|
|
_panel->showAskPassword();
|
|
}
|
|
|
|
void PanelController::showNoPassword() {
|
|
ensurePanelCreated();
|
|
_panel->showNoPassword();
|
|
}
|
|
|
|
void PanelController::showCriticalError(const QString &error) {
|
|
ensurePanelCreated();
|
|
_panel->showCriticalError(error);
|
|
}
|
|
|
|
void PanelController::showUpdateAppBox() {
|
|
ensurePanelCreated();
|
|
|
|
const auto callback = [=] {
|
|
_form->cancelSure();
|
|
Core::UpdateApplication();
|
|
};
|
|
show(
|
|
Ui::MakeConfirmBox({
|
|
.text = tr::lng_passport_app_out_of_date(),
|
|
.confirmed = callback,
|
|
.cancelled = [=] { _form->cancelSure(); },
|
|
.confirmText = tr::lng_menu_update(),
|
|
}),
|
|
Ui::LayerOption::KeepOther,
|
|
anim::type::instant);
|
|
}
|
|
|
|
void PanelController::ensurePanelCreated() {
|
|
if (!_panel) {
|
|
_panel = std::make_unique<Panel>(this);
|
|
}
|
|
}
|
|
|
|
std::optional<int> PanelController::findBestDocumentIndex(
|
|
const Scope &scope) const {
|
|
Expects(!scope.documents.empty());
|
|
|
|
const auto &documents = scope.documents;
|
|
const auto i = ranges::min_element(
|
|
documents,
|
|
std::less<>(),
|
|
[](not_null<const Value*> document) {
|
|
return document->whatNotFilled();
|
|
});
|
|
return ((*i)->whatNotFilled() == Value::kNothingFilled)
|
|
? std::nullopt
|
|
: base::make_optional(int(i - begin(documents)));
|
|
return -1;
|
|
}
|
|
|
|
void PanelController::editScope(int index) {
|
|
Expects(_panel != nullptr);
|
|
Expects(index >= 0 && index < _scopes.size());
|
|
|
|
const auto &scope = _scopes[index];
|
|
if (scope.documents.empty()) {
|
|
editScope(index, std::nullopt);
|
|
} else {
|
|
const auto documentIndex = findBestDocumentIndex(scope);
|
|
if (documentIndex || scope.documents.size() == 1) {
|
|
editScope(index, documentIndex ? *documentIndex : 0);
|
|
} else {
|
|
requestScopeFilesType(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PanelController::requestScopeFilesType(int index) {
|
|
Expects(_panel != nullptr);
|
|
Expects(index >= 0 && index < _scopes.size());
|
|
|
|
const auto type = _scopes[index].type;
|
|
_scopeDocumentTypeBox = [&] {
|
|
if (type == Scope::Type::Identity) {
|
|
return show(RequestIdentityType(
|
|
[=](int documentIndex) {
|
|
editWithUpload(index, documentIndex);
|
|
},
|
|
ranges::views::all(
|
|
_scopes[index].documents
|
|
) | ranges::views::transform([](auto value) {
|
|
return value->type;
|
|
}) | ranges::views::transform([](Value::Type type) {
|
|
switch (type) {
|
|
case Value::Type::Passport:
|
|
return tr::lng_passport_identity_passport(tr::now);
|
|
case Value::Type::IdentityCard:
|
|
return tr::lng_passport_identity_card(tr::now);
|
|
case Value::Type::DriverLicense:
|
|
return tr::lng_passport_identity_license(tr::now);
|
|
case Value::Type::InternalPassport:
|
|
return tr::lng_passport_identity_internal(tr::now);
|
|
default:
|
|
Unexpected("IdentityType in requestScopeFilesType");
|
|
}
|
|
}) | ranges::to_vector));
|
|
} else if (type == Scope::Type::Address) {
|
|
return show(RequestAddressType(
|
|
[=](int documentIndex) {
|
|
editWithUpload(index, documentIndex);
|
|
},
|
|
ranges::views::all(
|
|
_scopes[index].documents
|
|
) | ranges::views::transform([](auto value) {
|
|
return value->type;
|
|
}) | ranges::views::transform([](Value::Type type) {
|
|
switch (type) {
|
|
case Value::Type::UtilityBill:
|
|
return tr::lng_passport_address_bill(tr::now);
|
|
case Value::Type::BankStatement:
|
|
return tr::lng_passport_address_statement(tr::now);
|
|
case Value::Type::RentalAgreement:
|
|
return tr::lng_passport_address_agreement(tr::now);
|
|
case Value::Type::PassportRegistration:
|
|
return tr::lng_passport_address_registration(tr::now);
|
|
case Value::Type::TemporaryRegistration:
|
|
return tr::lng_passport_address_temporary(tr::now);
|
|
default:
|
|
Unexpected("AddressType in requestScopeFilesType");
|
|
}
|
|
}) | ranges::to_vector));
|
|
} else {
|
|
Unexpected("Type in processVerificationNeeded.");
|
|
}
|
|
}();
|
|
}
|
|
|
|
void PanelController::editWithUpload(int index, int documentIndex) {
|
|
Expects(_panel != nullptr);
|
|
Expects(index >= 0 && index < _scopes.size());
|
|
Expects(documentIndex >= 0
|
|
&& documentIndex < _scopes[index].documents.size());
|
|
|
|
const auto document = _scopes[index].documents[documentIndex];
|
|
const auto type = document->requiresSpecialScan(FileType::FrontSide)
|
|
? FileType::FrontSide
|
|
: FileType::Scan;
|
|
const auto widget = _panel->widget();
|
|
EditScans::ChooseScan(widget.get(), type, [=](QByteArray &&content) {
|
|
if (_scopeDocumentTypeBox) {
|
|
_scopeDocumentTypeBox = Ui::BoxPointer();
|
|
}
|
|
if (!_editScope || !_editDocument) {
|
|
startScopeEdit(index, documentIndex);
|
|
}
|
|
uploadScan(type, std::move(content));
|
|
}, [=](ReadScanError error) {
|
|
readScanError(error);
|
|
});
|
|
}
|
|
|
|
void PanelController::readScanError(ReadScanError error) {
|
|
show(Ui::MakeInformBox([&]() -> rpl::producer<QString> {
|
|
switch (error) {
|
|
case ReadScanError::FileTooLarge:
|
|
return tr::lng_passport_error_too_large();
|
|
case ReadScanError::BadImageSize:
|
|
return tr::lng_passport_error_bad_size();
|
|
case ReadScanError::CantReadImage:
|
|
return tr::lng_passport_error_cant_read();
|
|
case ReadScanError::Unknown:
|
|
return rpl::single(Lang::Hard::UnknownSecureScanError());
|
|
}
|
|
Unexpected("Error type in PanelController::readScanError.");
|
|
}()));
|
|
}
|
|
|
|
bool PanelController::editRequiresScanUpload(
|
|
int index,
|
|
std::optional<int> documentIndex) const {
|
|
Expects(index >= 0 && index < _scopes.size());
|
|
Expects(!documentIndex
|
|
|| (*documentIndex >= 0
|
|
&& *documentIndex < _scopes[index].documents.size()));
|
|
|
|
if (!documentIndex) {
|
|
return false;
|
|
}
|
|
const auto document = _scopes[index].documents[*documentIndex];
|
|
if (document->requiresSpecialScan(FileType::FrontSide)) {
|
|
const auto &scans = document->specialScans;
|
|
return (scans.find(FileType::FrontSide) == end(scans));
|
|
}
|
|
return document->files(FileType::Scan).empty();
|
|
}
|
|
|
|
void PanelController::editScope(
|
|
int index,
|
|
std::optional<int> documentIndex) {
|
|
if (editRequiresScanUpload(index, documentIndex)) {
|
|
editWithUpload(index, *documentIndex);
|
|
} else {
|
|
startScopeEdit(index, documentIndex);
|
|
}
|
|
}
|
|
|
|
void PanelController::startScopeEdit(
|
|
int index,
|
|
std::optional<int> documentIndex) {
|
|
Expects(_panel != nullptr);
|
|
Expects(index >= 0 && index < _scopes.size());
|
|
Expects(_scopes[index].details != 0 || documentIndex.has_value());
|
|
Expects(!documentIndex.has_value()
|
|
|| (*documentIndex >= 0
|
|
&& *documentIndex < _scopes[index].documents.size()));
|
|
|
|
_editScope = &_scopes[index];
|
|
_editValue = _editScope->details;
|
|
_editDocument = documentIndex
|
|
? _scopes[index].documents[*documentIndex].get()
|
|
: nullptr;
|
|
|
|
if (_editValue) {
|
|
_form->startValueEdit(_editValue);
|
|
}
|
|
if (_editDocument) {
|
|
_form->startValueEdit(_editDocument);
|
|
}
|
|
|
|
auto preferredLanguage = [=](const QString &countryCode) {
|
|
return _form->preferredLanguage(countryCode);
|
|
};
|
|
|
|
auto content = [&]() -> object_ptr<Ui::RpWidget> {
|
|
switch (_editScope->type) {
|
|
case Scope::Type::Identity:
|
|
case Scope::Type::Address: {
|
|
Assert(_editDocument != nullptr);
|
|
auto scans = PrepareScanListData(
|
|
*_editDocument,
|
|
FileType::Scan);
|
|
auto translations = _editDocument->translationRequired
|
|
? base::make_optional(PrepareScanListData(
|
|
*_editDocument,
|
|
FileType::Translation))
|
|
: std::nullopt;
|
|
auto result = _editValue
|
|
? object_ptr<PanelEditDocument>(
|
|
_panel->widget(),
|
|
this,
|
|
GetDocumentScheme(
|
|
_editScope->type,
|
|
_editDocument->type,
|
|
_editValue->nativeNames,
|
|
std::move(preferredLanguage)),
|
|
_editValue->error,
|
|
_editValue->data.parsedInEdit,
|
|
_editDocument->error,
|
|
_editDocument->data.parsedInEdit,
|
|
std::move(scans),
|
|
std::move(translations),
|
|
PrepareSpecialFiles(*_editDocument))
|
|
: object_ptr<PanelEditDocument>(
|
|
_panel->widget(),
|
|
this,
|
|
GetDocumentScheme(
|
|
_editScope->type,
|
|
_editDocument->type,
|
|
false,
|
|
std::move(preferredLanguage)),
|
|
_editDocument->error,
|
|
_editDocument->data.parsedInEdit,
|
|
std::move(scans),
|
|
std::move(translations),
|
|
PrepareSpecialFiles(*_editDocument));
|
|
const auto weak = Ui::MakeWeak(result.data());
|
|
_panelHasUnsavedChanges = [=] {
|
|
return weak ? weak->hasUnsavedChanges() : false;
|
|
};
|
|
return result;
|
|
} break;
|
|
case Scope::Type::PersonalDetails:
|
|
case Scope::Type::AddressDetails: {
|
|
Assert(_editValue != nullptr);
|
|
auto result = object_ptr<PanelEditDocument>(
|
|
_panel->widget(),
|
|
this,
|
|
GetDocumentScheme(
|
|
_editScope->type,
|
|
std::nullopt,
|
|
_editValue->nativeNames,
|
|
std::move(preferredLanguage)),
|
|
_editValue->error,
|
|
_editValue->data.parsedInEdit);
|
|
const auto weak = Ui::MakeWeak(result.data());
|
|
_panelHasUnsavedChanges = [=] {
|
|
return weak ? weak->hasUnsavedChanges() : false;
|
|
};
|
|
return result;
|
|
} break;
|
|
case Scope::Type::Phone:
|
|
case Scope::Type::Email: {
|
|
Assert(_editValue != nullptr);
|
|
const auto &parsed = _editValue->data.parsedInEdit;
|
|
const auto valueIt = parsed.fields.find("value");
|
|
const auto value = (valueIt == end(parsed.fields)
|
|
? QString()
|
|
: valueIt->second.text);
|
|
const auto existing = getDefaultContactValue(_editScope->type);
|
|
_panelHasUnsavedChanges = nullptr;
|
|
return object_ptr<PanelEditContact>(
|
|
_panel->widget(),
|
|
this,
|
|
GetContactScheme(_editScope->type),
|
|
value,
|
|
(existing.toLower().trimmed() != value.toLower().trimmed()
|
|
? existing
|
|
: QString()));
|
|
} break;
|
|
}
|
|
Unexpected("Type in PanelController::editScope().");
|
|
}();
|
|
|
|
content->lifetime().add([=] {
|
|
cancelValueEdit();
|
|
});
|
|
|
|
_panel->setBackAllowed(true);
|
|
|
|
_panel->backRequests(
|
|
) | rpl::start_with_next([=] {
|
|
cancelEditScope();
|
|
}, content->lifetime());
|
|
|
|
_form->valueSaveFinished(
|
|
) | rpl::start_with_next([=](not_null<const Value*> value) {
|
|
processValueSaveFinished(value);
|
|
}, content->lifetime());
|
|
|
|
_panel->showEditValue(std::move(content));
|
|
}
|
|
|
|
void PanelController::processValueSaveFinished(
|
|
not_null<const Value*> value) {
|
|
Expects(_editScope != nullptr);
|
|
|
|
const auto boxIt = _verificationBoxes.find(value);
|
|
if (boxIt != end(_verificationBoxes)) {
|
|
const auto saved = std::move(boxIt->second);
|
|
_verificationBoxes.erase(boxIt);
|
|
}
|
|
|
|
if ((_editValue == value || _editDocument == value) && !savingScope()) {
|
|
if (auto errors = collectSaveErrors(value); !errors.empty()) {
|
|
for (auto &&error : errors) {
|
|
_saveErrors.fire(std::move(error));
|
|
}
|
|
} else {
|
|
_panel->showForm();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool PanelController::uploadingScopeScan() const {
|
|
return (_editValue && _editValue->uploadingScan())
|
|
|| (_editDocument && _editDocument->uploadingScan());
|
|
}
|
|
|
|
bool PanelController::savingScope() const {
|
|
return (_editValue && _editValue->saving())
|
|
|| (_editDocument && _editDocument->saving());
|
|
}
|
|
|
|
void PanelController::processVerificationNeeded(
|
|
not_null<const Value*> value) {
|
|
const auto i = _verificationBoxes.find(value);
|
|
if (i != _verificationBoxes.end()) {
|
|
LOG(("API Error: Requesting for verification repeatedly."));
|
|
return;
|
|
}
|
|
const auto textIt = value->data.parsedInEdit.fields.find("value");
|
|
Assert(textIt != end(value->data.parsedInEdit.fields));
|
|
const auto text = textIt->second.text;
|
|
const auto type = value->type;
|
|
const auto update = _form->verificationUpdate(
|
|
) | rpl::filter([=](not_null<const Value*> field) {
|
|
return (field == value);
|
|
});
|
|
const auto box = [&] {
|
|
if (type == Value::Type::Phone) {
|
|
const auto submit = [=](const QString &code) {
|
|
_form->verify(value, code);
|
|
};
|
|
const auto account = &_form->window()->session().account();
|
|
account->setHandleLoginCode(submit);
|
|
const auto box = show(VerifyPhoneBox(
|
|
text,
|
|
value->verification.codeLength,
|
|
value->verification.fragmentUrl,
|
|
submit,
|
|
value->verification.call ? rpl::single(
|
|
value->verification.call->getText()
|
|
) | rpl::then(rpl::duplicate(
|
|
update
|
|
) | rpl::filter([=](not_null<const Value*> field) {
|
|
return field->verification.call != nullptr;
|
|
}) | rpl::map([=](not_null<const Value*> field) {
|
|
return field->verification.call->getText();
|
|
})) : (rpl::single(QString()) | rpl::type_erased()),
|
|
|
|
rpl::duplicate(
|
|
update
|
|
) | rpl::map([=](not_null<const Value*> field) {
|
|
return field->verification.error;
|
|
}) | rpl::distinct_until_changed()));
|
|
box->boxClosing(
|
|
) | rpl::start_with_next([=] {
|
|
account->setHandleLoginCode(nullptr);
|
|
}, box->lifetime());
|
|
return box;
|
|
} else if (type == Value::Type::Email) {
|
|
return show(VerifyEmailBox(
|
|
text,
|
|
value->verification.codeLength,
|
|
[=](const QString &code) { _form->verify(value, code); },
|
|
nullptr, // resend
|
|
|
|
rpl::duplicate(
|
|
update
|
|
) | rpl::map([=](not_null<const Value*> field) {
|
|
return field->verification.error;
|
|
}) | rpl::distinct_until_changed(),
|
|
|
|
nullptr));
|
|
} else {
|
|
Unexpected("Type in processVerificationNeeded.");
|
|
}
|
|
}();
|
|
|
|
box->boxClosing(
|
|
) | rpl::start_with_next([=] {
|
|
_form->cancelValueVerification(value);
|
|
}, lifetime());
|
|
|
|
_verificationBoxes.emplace(value, box);
|
|
}
|
|
|
|
void PanelController::cancelValueEdit() {
|
|
Expects(_editScope != nullptr);
|
|
|
|
_editScopeBoxes.clear();
|
|
if (const auto value = base::take(_editValue)) {
|
|
_form->cancelValueEdit(value);
|
|
}
|
|
if (const auto document = base::take(_editDocument)) {
|
|
_form->cancelValueEdit(document);
|
|
}
|
|
_editScope = nullptr;
|
|
}
|
|
|
|
void PanelController::saveScope(ValueMap &&data, ValueMap &&filesData) {
|
|
Expects(_panel != nullptr);
|
|
|
|
if (uploadingScopeScan()) {
|
|
showToast(tr::lng_passport_wait_upload(tr::now));
|
|
return;
|
|
} else if (savingScope()) {
|
|
return;
|
|
}
|
|
|
|
if (_editValue) {
|
|
_form->saveValueEdit(_editValue, std::move(data));
|
|
} else {
|
|
Assert(data.fields.empty());
|
|
}
|
|
if (_editDocument) {
|
|
_form->saveValueEdit(_editDocument, std::move(filesData));
|
|
} else {
|
|
Assert(filesData.fields.empty());
|
|
}
|
|
}
|
|
|
|
bool PanelController::editScopeChanged(
|
|
const ValueMap &data,
|
|
const ValueMap &filesData) const {
|
|
if (_editValue && ValueChanged(_editValue, data)) {
|
|
return true;
|
|
} else if (_editDocument && ValueChanged(_editDocument, filesData)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void PanelController::cancelEditScope() {
|
|
Expects(_editScope != nullptr);
|
|
|
|
if (_panelHasUnsavedChanges && _panelHasUnsavedChanges()) {
|
|
if (!_confirmForgetChangesBox) {
|
|
_confirmForgetChangesBox = show(Ui::MakeConfirmBox({
|
|
.text = tr::lng_passport_sure_cancel(),
|
|
.confirmed = [=] { _panel->showForm(); },
|
|
.confirmText = tr::lng_continue(),
|
|
}));
|
|
_editScopeBoxes.emplace_back(_confirmForgetChangesBox);
|
|
}
|
|
} else {
|
|
_panel->showForm();
|
|
}
|
|
}
|
|
|
|
int PanelController::closeGetDuration() {
|
|
if (_panel) {
|
|
return _panel->hideAndDestroyGetDuration();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void PanelController::cancelAuth() {
|
|
_form->cancel();
|
|
}
|
|
|
|
void PanelController::cancelAuthSure() {
|
|
_form->cancelSure();
|
|
}
|
|
|
|
void PanelController::showBox(
|
|
object_ptr<Ui::BoxContent> box,
|
|
Ui::LayerOptions options,
|
|
anim::type animated) {
|
|
_panel->showBox(std::move(box), options, animated);
|
|
}
|
|
|
|
void PanelController::showToast(const QString &text) {
|
|
_panel->showToast(text);
|
|
}
|
|
|
|
rpl::lifetime &PanelController::lifetime() {
|
|
return _lifetime;
|
|
}
|
|
|
|
PanelController::~PanelController() = default;
|
|
|
|
} // namespace Passport
|