diff --git a/Telegram/Resources/langs/cloud_lang.strings b/Telegram/Resources/langs/cloud_lang.strings new file mode 100644 index 0000000000..ad20ff187a --- /dev/null +++ b/Telegram/Resources/langs/cloud_lang.strings @@ -0,0 +1,55 @@ +/* +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 +*/ + +"cloud_lng_passport_in_ar" = "Arabic"; +"cloud_lng_passport_in_az" = "Azerbaijani"; +"cloud_lng_passport_in_bg" = "Bulgarian"; +"cloud_lng_passport_in_bn" = "Bangla"; +"cloud_lng_passport_in_cs" = "Czech"; +"cloud_lng_passport_in_da" = "Danish"; +"cloud_lng_passport_in_de" = "German"; +"cloud_lng_passport_in_dv" = "Divehi"; +"cloud_lng_passport_in_dz" = "Dzongkha"; +"cloud_lng_passport_in_el" = "Greek"; +"cloud_lng_passport_in_en" = "English"; +"cloud_lng_passport_in_es" = "Spanish"; +"cloud_lng_passport_in_et" = "Estonian"; +"cloud_lng_passport_in_fa" = "Persian"; +"cloud_lng_passport_in_fr" = "French"; +"cloud_lng_passport_in_he" = "Hebrew"; +"cloud_lng_passport_in_hr" = "Croatian"; +"cloud_lng_passport_in_hu" = "Hungarian"; +"cloud_lng_passport_in_hy" = "Armenian"; +"cloud_lng_passport_in_id" = "Indonesian"; +"cloud_lng_passport_in_is" = "Icelandic"; +"cloud_lng_passport_in_it" = "Italian"; +"cloud_lng_passport_in_ja" = "Japanese"; +"cloud_lng_passport_in_ka" = "Georgian"; +"cloud_lng_passport_in_km" = "Khmer"; +"cloud_lng_passport_in_ko" = "Korean"; +"cloud_lng_passport_in_lo" = "Lao"; +"cloud_lng_passport_in_lt" = "Lithuanian"; +"cloud_lng_passport_in_lv" = "Latvian"; +"cloud_lng_passport_in_mk" = "Macedonian"; +"cloud_lng_passport_in_mn" = "Mongolian"; +"cloud_lng_passport_in_ms" = "Malay"; +"cloud_lng_passport_in_my" = "Burmese"; +"cloud_lng_passport_in_ne" = "Nepali"; +"cloud_lng_passport_in_nl" = "Dutch"; +"cloud_lng_passport_in_pl" = "Polish"; +"cloud_lng_passport_in_pt" = "Portuguese"; +"cloud_lng_passport_in_ro" = "Romanian"; +"cloud_lng_passport_in_ru" = "Russian"; +"cloud_lng_passport_in_sk" = "Slovak"; +"cloud_lng_passport_in_sl" = "Slovenian"; +"cloud_lng_passport_in_th" = "Thai"; +"cloud_lng_passport_in_tk" = "Turkmen"; +"cloud_lng_passport_in_tr" = "Turkish"; +"cloud_lng_passport_in_uk" = "Ukrainian"; +"cloud_lng_passport_in_uz" = "Uzbek"; +"cloud_lng_passport_in_vi" = "Vietnamese"; diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 00053b0080..25abe7f5c6 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1553,7 +1553,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passport_password_wrong" = "The password you entered is not valid."; "lng_passport_header" = "Requested information"; "lng_passport_identity_title" = "Identity document"; -"lng_passport_identity_description" = "Upload a scan of your passport or another ID"; +"lng_passport_identity_description" = "Upload proof of your identity"; "lng_passport_identity_passport" = "Passport"; "lng_passport_identity_passport_upload" = "Upload a scan of your passport"; "lng_passport_identity_card" = "Identity card"; @@ -1632,6 +1632,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passport_expiry_date" = "Expiry date"; "lng_passport_native_name_title" = "Name in document language"; "lng_passport_native_name_about" = "Your name in the language of the country ({country}) that issued the document."; +"lng_passport_native_name_language" = "Your name in {language}"; +"lng_passport_native_name_language_about" = "Your name in the language of the country that issued the document."; "lng_passport_address" = "Address"; "lng_passport_address_enter" = "Please provide your address"; "lng_passport_street" = "Street"; diff --git a/Telegram/SourceFiles/lang/lang_cloud_manager.cpp b/Telegram/SourceFiles/lang/lang_cloud_manager.cpp index 09f1f73662..5a692a366e 100644 --- a/Telegram/SourceFiles/lang/lang_cloud_manager.cpp +++ b/Telegram/SourceFiles/lang/lang_cloud_manager.cpp @@ -19,7 +19,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Lang { -CloudManager::CloudManager(Instance &langpack, not_null mtproto) : MTP::Sender() +CloudManager::CloudManager( + Instance &langpack, + not_null mtproto) +: MTP::Sender() , _langpack(langpack) { requestLangPackDifference(); } diff --git a/Telegram/SourceFiles/lang/lang_instance.cpp b/Telegram/SourceFiles/lang/lang_instance.cpp index 9297b472cd..9bc0a43af4 100644 --- a/Telegram/SourceFiles/lang/lang_instance.cpp +++ b/Telegram/SourceFiles/lang/lang_instance.cpp @@ -433,7 +433,8 @@ void Instance::applyDifference(const MTPDlangPackDifference &difference) { _updated.notify(); } -std::map Instance::ParseStrings(const MTPVector &strings) { +std::map Instance::ParseStrings( + const MTPVector &strings) { auto result = std::map(); for (auto &mtpString : strings.v) { HandleString(mtpString, [&result](auto &&key, auto &&value) { @@ -449,10 +450,16 @@ std::map Instance::ParseStrings(const MTPVector -LangKey Instance::ParseKeyValue(const QByteArray &key, const QByteArray &value, Result &result) { +LangKey Instance::ParseKeyValue( + const QByteArray &key, + const QByteArray &value, + Result &result) { auto keyIndex = GetKeyIndex(QLatin1String(key)); if (keyIndex == kLangKeysCount) { - LOG(("Lang Error: Unknown key '%1'").arg(QString::fromLatin1(key))); + if (!key.startsWith("cloud_")) { + LOG(("Lang Warning: Unknown key '%1'" + ).arg(QString::fromLatin1(key))); + } return kLangKeysCount; } @@ -464,6 +471,13 @@ LangKey Instance::ParseKeyValue(const QByteArray &key, const QByteArray &value, return kLangKeysCount; } +QString Instance::getNonDefaultValue(const QByteArray &key) const { + const auto i = _nonDefaultValues.find(key); + return (i != end(_nonDefaultValues)) + ? QString::fromUtf8(i->second) + : QString(); +} + void Instance::applyValue(const QByteArray &key, const QByteArray &value) { _nonDefaultValues[key] = value; auto index = ParseKeyValue(key, value, _values); diff --git a/Telegram/SourceFiles/lang/lang_instance.h b/Telegram/SourceFiles/lang/lang_instance.h index 586e1d9fc9..4e51494de0 100644 --- a/Telegram/SourceFiles/lang/lang_instance.h +++ b/Telegram/SourceFiles/lang/lang_instance.h @@ -85,6 +85,7 @@ public: return _values[key]; } + QString getNonDefaultValue(const QByteArray &key) const; bool isNonDefaultPlural(LangKey key) const { Expects(key >= 0 && key < kLangKeysCount); Expects(_nonDefaultSet.size() == kLangKeysCount); diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index 114132d211..d43e2dd617 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "passport/passport_encryption.h" #include "passport/passport_panel_controller.h" +#include "passport/passport_panel_edit_document.h" #include "boxes/confirm_box.h" #include "boxes/passcode_box.h" #include "lang/lang_keys.h" @@ -34,6 +35,8 @@ constexpr auto kTranslationScansLimit = 20; constexpr auto kShortPollTimeout = TimeMs(3000); constexpr auto kRememberCredentialsDelay = TimeMs(1800 * 1000); +Config GlobalConfig; + bool ForwardServiceErrorRequired(const QString &error) { return (error == qstr("BOT_INVALID")) || (error == qstr("PUBLIC_KEY_REQUIRED")) @@ -228,6 +231,44 @@ QString ValidateUrl(const QString &url) { } // namespace +Config &ConfigInstance() { + return GlobalConfig; +} + +Config ParseConfig(const MTPhelp_PassportConfig &data) { + return data.match([](const MTPDhelp_passportConfig &data) { + auto result = Config(); + result.hash = data.vhash.v; + auto error = QJsonParseError{ 0, QJsonParseError::NoError }; + const auto document = QJsonDocument::fromJson( + data.vcountries_langs.c_dataJSON().vdata.v, + &error); + if (error.error != QJsonParseError::NoError) { + LOG(("API Error: Failed to parse passport config, error: %1." + ).arg(error.errorString())); + return result; + } else if (!document.isObject()) { + LOG(("API Error: Not an object received in passport config.")); + return result; + } + const auto object = document.object(); + for (auto i = object.constBegin(); i != object.constEnd(); ++i) { + const auto countryCode = i.key(); + const auto language = i.value(); + if (!language.isString()) { + LOG(("API Error: Not a string in passport config item.")); + continue; + } + result.languagesByCountryCode.emplace( + countryCode, + language.toString()); + } + return result; + }, [](const MTPDhelp_passportConfigNotModified &data) { + return ConfigInstance(); + }); +} + QString NonceNameByScope(const QString &scope) { if (scope.startsWith('{') && scope.endsWith('}')) { return qsl("nonce"); @@ -579,6 +620,7 @@ FormController::FormController( void FormController::show() { requestForm(); requestPassword(); + requestConfig(); } UserData *FormController::bot() const { @@ -1082,6 +1124,7 @@ void FormController::decryptValues() { decryptValue(value); } fillErrors(); + fillNativeFromFallback(); } void FormController::fillErrors() { @@ -1178,6 +1221,42 @@ void FormController::fillErrors() { } } +void FormController::fillNativeFromFallback() { + const auto i = _form.values.find(Value::Type::PersonalDetails); + if (i == end(_form.values) || !i->second.nativeNames) { + return; + } + const auto scheme = GetDocumentScheme( + Scope::Type::PersonalDetails, + base::none, + true); + auto changed = false; + auto values = i->second.data.parsed; + using Scheme = EditDocumentScheme; + for (const auto &row : scheme.rows) { + if (row.valueClass == Scheme::ValueClass::Additional) { + auto &field = values.fields[row.key]; + if (!field.text.isEmpty() || !field.error.isEmpty()) { + return; + } + const auto i = values.fields.find(row.additionalFallbackKey); + const auto value = (i == end(values.fields)) + ? QString() + : i->second.text; + if (row.error(value).has_value()) { + return; + } else if (field.text != value) { + field.text = value; + changed = true; + } + } + } + if (changed) { + startValueEdit(&i->second); + saveValueEdit(&i->second, std::move(values)); + } +} + void FormController::decryptValue(Value &value) const { Expects(!_secret.empty()); @@ -2375,11 +2454,29 @@ auto FormController::findFile(const FileKey &key) void FormController::formDone(const MTPaccount_AuthorizationForm &result) { if (!parseForm(result)) { _view->showCriticalError(lang(lng_passport_form_error)); - } else if (!_passwordRequestId) { + } else { showForm(); } } +void FormController::requestConfig() { + const auto i = _form.values.find(Value::Type::PersonalDetails); + if (i == end(_form.values) || !i->second.nativeNames) { + return; + } + const auto hash = ConfigInstance().hash; + _configRequestId = request(MTPhelp_GetPassportConfig( + MTP_int(hash) + )).done([=](const MTPhelp_PassportConfig &result) { + _configRequestId = 0; + ConfigInstance() = ParseConfig(result); + showForm(); + }).fail([=](const RPCError &error) { + _configRequestId = 0; + showForm(); + }).send(); +} + bool FormController::parseForm(const MTPaccount_AuthorizationForm &result) { Expects(result.type() == mtpc_account_authorizationForm); @@ -2458,7 +2555,7 @@ void FormController::passwordDone(const MTPaccount_Password &result) { Expects(result.type() == mtpc_account_password); const auto changed = applyPassword(result.c_account_password()); - if (changed && !_formRequestId) { + if (changed) { showForm(); } shortPollEmailConfirmation(); @@ -2473,7 +2570,9 @@ void FormController::shortPollEmailConfirmation() { } void FormController::showForm() { - if (!_bot) { + if (_formRequestId || _passwordRequestId || _configRequestId) { + return; + } else if (!_bot) { formFail(Lang::Hard::NoAuthorizationBot()); return; } diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h index f84b6e5c36..ec6b09cbe9 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_controller.h @@ -25,6 +25,13 @@ class Controller; namespace Passport { +struct Config { + int32 hash = 0; + std::map languagesByCountryCode; +}; +Config &ConfigInstance(); +Config ParseConfig(const MTPhelp_PassportConfig &data); + struct SavedCredentials { bytes::vector hashForAuth; bytes::vector hashForSecret; @@ -387,6 +394,7 @@ private: void requestForm(); void requestPassword(); + void requestConfig(); void formDone(const MTPaccount_AuthorizationForm &result); void formFail(const QString &error); @@ -436,6 +444,7 @@ private: bool validateValueSecrets(Value &value) const; void resetValue(Value &value) const; void fillErrors(); + void fillNativeFromFallback(); void loadFile(File &file); void fileLoadDone(FileKey key, const QByteArray &bytes); @@ -505,6 +514,7 @@ private: mtpRequestId _formRequestId = 0; mtpRequestId _passwordRequestId = 0; mtpRequestId _passwordCheckRequestId = 0; + mtpRequestId _configRequestId = 0; PasswordSettings _password; TimeMs _lastSrpIdInvalidTime = 0; diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp index 7cc5282491..9daaa5c7bd 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp @@ -32,6 +32,7 @@ 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 = [&] { @@ -282,8 +283,33 @@ EditDocumentScheme GetDocumentScheme( }; if (nativeNames) { result.additionalDependencyKey = qsl("residence_country_code"); - result.additionalHeader = lang(lng_passport_native_name_title); - result.additionalDescription = [](const QString &countryCode) { + + const auto languageValue = [](const QString &countryCode) { + if (countryCode.isEmpty()) { + return QString(); + } + const auto &config = ConfigInstance(); + const auto i = config.languagesByCountryCode.find( + countryCode); + if (i == end(config.languagesByCountryCode)) { + return QString(); + } + return Lang::Current().getNonDefaultValue( + kLanguageNamePrefix + i->second.toUtf8()); + }; + result.additionalHeader = [=](const QString &countryCode) { + const auto language = languageValue(countryCode); + return language.isEmpty() + ? lang(lng_passport_native_name_title) + : lng_passport_native_name_language( + lt_language, + language); + }; + result.additionalDescription = [=](const QString &countryCode) { + const auto language = languageValue(countryCode); + if (!language.isEmpty()) { + return lang(lng_passport_native_name_language_about); + } const auto name = CountrySelectBox::NameByISO(countryCode); Assert(!name.isEmpty()); return lng_passport_native_name_about( @@ -291,7 +317,18 @@ EditDocumentScheme GetDocumentScheme( name); }; result.additionalShown = [](const QString &countryCode) { - return !countryCode.isEmpty(); + using Result = EditDocumentScheme::AdditionalVisibility; + if (countryCode.isEmpty()) { + return Result::Hidden; + } + const auto &config = ConfigInstance(); + const auto i = config.languagesByCountryCode.find( + countryCode); + if (i != end(config.languagesByCountryCode) + && i->second == "en") { + return Result::OnlyIfError; + } + return Result::Shown; }; using Row = EditDocumentScheme::Row; auto additional = std::initializer_list{ @@ -303,6 +340,8 @@ EditDocumentScheme GetDocumentScheme( NativeNameValidate, DontFormat, kMaxNameSize, + QString(), + qsl("first_name"), }, { ValueClass::Additional, @@ -313,6 +352,7 @@ EditDocumentScheme GetDocumentScheme( DontFormat, kMaxNameSize, qsl("first_name_native"), + qsl("middle_name"), }, { ValueClass::Additional, @@ -323,6 +363,7 @@ EditDocumentScheme GetDocumentScheme( DontFormat, kMaxNameSize, qsl("first_name_native"), + qsl("last_name"), }, }; for (auto &row : additional) { diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp index 2f0ece3fb6..d560a933cb 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp @@ -404,11 +404,47 @@ not_null PanelEditDocument::setupContent( inner, object_ptr(inner))); const auto added = wrap->entity(); + + auto showIfError = false; + enumerateRows([&]( + int i, + const Scheme::Row &row, + const ValueMap &fields) { + if (row.valueClass != Scheme::ValueClass::Additional) { + return; + } + const auto it = fields.fields.find(row.key); + if (it == end(fields.fields)) { + return; + } else if (!it->second.error.isEmpty()) { + showIfError = true; + } else if (it->second.text.isEmpty()) { + return; + } + const auto fallbackIt = fields.fields.find( + row.additionalFallbackKey); + if (fallbackIt != end(fields.fields) + && fallbackIt->second.text != it->second.text) { + showIfError = true; + } + }); + const auto shown = [=](const QString &code) { + using Result = Scheme::AdditionalVisibility; + const auto value = _scheme.additionalShown(code); + return (value == Result::Shown) + || (value == Result::OnlyIfError && showIfError); + }; + + auto title = row->value( + ) | rpl::filter( + shown + ) | rpl::map([=](const QString &code) { + return _scheme.additionalHeader(code); + }); added->add( object_ptr( added, - _scheme.additionalHeader, - Ui::FlatLabel::InitType::Simple, + std::move(title), st::passportFormHeader), st::passportNativeNameHeaderPadding); @@ -422,9 +458,9 @@ not_null PanelEditDocument::setupContent( }); auto description = row->value( - ) | rpl::filter([=](const QString &code) { - return _scheme.additionalShown(code); - }) | rpl::map([=](const QString &code) { + ) | rpl::filter( + shown + ) | rpl::map([=](const QString &code) { return _scheme.additionalDescription(code); }); added->add( @@ -437,11 +473,15 @@ not_null PanelEditDocument::setupContent( st::passportFormLabelPadding), st::passportNativeNameAboutMargin); - wrap->toggleOn(row->value( - ) | rpl::map([=](const QString &code) { - return _scheme.additionalShown(code); - })); + wrap->toggleOn(row->value() | rpl::map(shown)); wrap->finishAnimating(); + + row->value( + ) | rpl::map( + shown + ) | rpl::start_with_next([=](bool visible) { + _additionalShown = visible; + }, lifetime()); } inner->add( @@ -469,8 +509,8 @@ void PanelEditDocument::createDetailsRow( const ValueMap &fields, int maxLabelWidth) { const auto valueOrEmpty = [&]( - const ValueMap &values, - const QString &key) { + const ValueMap &values, + const QString &key) { const auto &fields = values.fields; if (const auto i = fields.find(key); i != fields.end()) { return i->second; @@ -561,11 +601,31 @@ PanelEditDocument::Result PanelEditDocument::collect() const { auto &fields = (row.valueClass == Scheme::ValueClass::Scans) ? result.filesData : result.data; + if (row.valueClass == Scheme::ValueClass::Additional + && !_additionalShown) { + continue; + } fields.fields[row.key].text = field->valueCurrent(); } + if (!_additionalShown) { + fillAdditionalFromFallbacks(result); + } return result; } +void PanelEditDocument::fillAdditionalFromFallbacks(Result &result) const { + for (const auto &row : _scheme.rows) { + if (row.valueClass != Scheme::ValueClass::Additional) { + continue; + } + Assert(!row.additionalFallbackKey.isEmpty()); + auto &fields = result.data; + const auto j = fields.fields.find(row.additionalFallbackKey); + Assert(j != end(fields.fields)); + fields.fields[row.key] = j->second; + } +} + bool PanelEditDocument::validate() { auto error = _editScans ? _editScans->validateGetErrorTop() @@ -585,6 +645,10 @@ bool PanelEditDocument::validate() { auto first = QPointer(); for (const auto [i, field] : base::reversed(_details)) { const auto &row = _scheme.rows[i]; + if (row.valueClass == Scheme::ValueClass::Additional + && !_additionalShown) { + continue; + } if (field->errorShown()) { field->showError(); first = field; diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.h b/Telegram/SourceFiles/passport/passport_panel_edit_document.h index 99dde2c9d2..bb37adc146 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.h +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.h @@ -44,6 +44,11 @@ struct EditDocumentScheme { Additional, Scans, }; + enum class AdditionalVisibility { + Hidden, + OnlyIfError, + Shown, + }; struct Row { using Validator = Fn(const QString &value)>; using Formatter = Fn; @@ -54,7 +59,8 @@ struct EditDocumentScheme { Validator error; Formatter format; int lengthLimit = 0; - QString keyForAttachmentTo; // attach [last|middle]_name to first_* + QString keyForAttachmentTo; // Attach [last|middle]_name to first_*. + QString additionalFallbackKey; // *_name_native from *_name. }; std::vector rows; QString fieldsHeader; @@ -62,8 +68,8 @@ struct EditDocumentScheme { QString scansHeader; QString additionalDependencyKey; - Fn additionalShown; - QString additionalHeader; + Fn additionalShown; + Fn additionalHeader; Fn additionalDescription; }; @@ -127,6 +133,7 @@ private: void updateCommonError(); Result collect() const; + void fillAdditionalFromFallbacks(Result &result) const; bool validate(); void save(); @@ -149,6 +156,7 @@ private: QPointer> _commonError; std::map> _details; bool _fieldsChanged = false; + bool _additionalShown = false; QPointer _delete; diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 43cce710f1..8312147042 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -109,6 +109,7 @@ '<@(style_files)', '