diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index d247168ff..eb01b13a8 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -42,8 +42,6 @@ constexpr auto kTranslationScansLimit = 20; constexpr auto kShortPollTimeout = crl::time(3000); constexpr auto kRememberCredentialsDelay = crl::time(1800 * 1000); -Config GlobalConfig; - bool ForwardServiceErrorRequired(const QString &error) { return (error == qstr("BOT_INVALID")) || (error == qstr("PUBLIC_KEY_REQUIRED")) @@ -239,46 +237,35 @@ QString ValidateUrl(const QString &url) { : result; } +auto ParseConfig(const QByteArray &json) { + auto languagesByCountryCode = std::map(); + auto error = QJsonParseError{ 0, QJsonParseError::NoError }; + const auto document = QJsonDocument::fromJson(json, &error); + if (error.error != QJsonParseError::NoError) { + LOG(("API Error: Failed to parse passport config, error: %1." + ).arg(error.errorString())); + return languagesByCountryCode; + } else if (!document.isObject()) { + LOG(("API Error: Not an object received in passport config.")); + return languagesByCountryCode; + } + 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; + } + languagesByCountryCode.emplace( + countryCode, + language.toString()); + } + return languagesByCountryCode; +} + } // 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"); @@ -638,7 +625,6 @@ Main::Session &FormController::session() const { void FormController::show() { requestForm(); requestPassword(); - requestConfig(); } UserData *FormController::bot() const { @@ -1242,6 +1228,44 @@ void FormController::fillErrors() { } } +rpl::producer FormController::preferredLanguage( + const QString &countryCode) { + const auto findLang = [=] { + if (countryCode.isEmpty()) { + return QString(); + } + auto &langs = _passportConfig.languagesByCountryCode; + const auto i = langs.find(countryCode); + return (i == end(langs)) ? QString() : i->second; + }; + return [=](auto consumer) { + const auto hash = _passportConfig.hash; + if (hash) { + consumer.put_next({ countryCode, findLang() }); + consumer.put_done(); + return rpl::lifetime() ; + } + + _api.request(MTPhelp_GetPassportConfig( + MTP_int(hash) + )).done([=](const MTPhelp_PassportConfig &result) { + result.match([&](const MTPDhelp_passportConfig &data) { + _passportConfig.hash = data.vhash().v; + _passportConfig.languagesByCountryCode = ParseConfig( + data.vcountries_langs().c_dataJSON().vdata().v); + }, [](const MTPDhelp_passportConfigNotModified &data) { + }); + consumer.put_next({ countryCode, findLang() }); + consumer.put_done(); + }).fail([=](const MTP::Error &error) { + consumer.put_next({ countryCode, QString() }); + consumer.put_done(); + }).send(); + + return rpl::lifetime(); + }; +} + void FormController::fillNativeFromFallback() { // Check if additional values (*_name_native) were requested. const auto i = _form.values.find(Value::Type::PersonalDetails); @@ -1254,48 +1278,58 @@ void FormController::fillNativeFromFallback() { const auto scheme = GetDocumentScheme( Scope::Type::PersonalDetails, std::nullopt, - true); + true, + [=](const QString &code) { return preferredLanguage(code); }); const auto dependencyIt = values.fields.find( scheme.additionalDependencyKey); const auto dependency = (dependencyIt == end(values.fields)) ? QString() : dependencyIt->second.text; - if (scheme.additionalShown(dependency) - != EditDocumentScheme::AdditionalVisibility::OnlyIfError) { - return; - } // Copy additional values from fallback if they're not filled yet. - auto changed = false; using Scheme = EditDocumentScheme; - for (const auto &row : scheme.rows) { - if (row.valueClass == Scheme::ValueClass::Additional) { - const auto nativeIt = values.fields.find(row.key); - const auto native = (nativeIt == end(values.fields)) - ? QString() - : nativeIt->second.text; - if (!native.isEmpty() - || (nativeIt != end(values.fields) - && !nativeIt->second.error.isEmpty())) { - return; - } - const auto latinIt = values.fields.find( - row.additionalFallbackKey); - const auto latin = (latinIt == end(values.fields)) - ? QString() - : latinIt->second.text; - if (row.error(latin).has_value()) { - return; - } else if (native != latin) { - values.fields[row.key].text = latin; - changed = true; + scheme.preferredLanguage( + dependency + ) | rpl::map( + scheme.additionalShown + ) | rpl::take( + 1 + ) | rpl::start_with_next([=](Scheme::AdditionalVisibility v) { + if (v != Scheme::AdditionalVisibility::OnlyIfError) { + return; + } + auto values = i->second.data.parsed; + auto changed = false; + + for (const auto &row : scheme.rows) { + if (row.valueClass == Scheme::ValueClass::Additional) { + const auto nativeIt = values.fields.find(row.key); + const auto native = (nativeIt == end(values.fields)) + ? QString() + : nativeIt->second.text; + if (!native.isEmpty() + || (nativeIt != end(values.fields) + && !nativeIt->second.error.isEmpty())) { + return; + } + const auto latinIt = values.fields.find( + row.additionalFallbackKey); + const auto latin = (latinIt == end(values.fields)) + ? QString() + : latinIt->second.text; + if (row.error(latin).has_value()) { + return; + } else if (native != latin) { + values.fields[row.key].text = latin; + changed = true; + } } } - } - if (changed) { - startValueEdit(&i->second); - saveValueEdit(&i->second, std::move(values)); - } + if (changed) { + startValueEdit(&i->second); + saveValueEdit(&i->second, std::move(values)); + } + }, _lifetime); } void FormController::decryptValue(Value &value) const { @@ -2507,20 +2541,6 @@ void FormController::formDone(const MTPaccount_AuthorizationForm &result) { } } -void FormController::requestConfig() { - const auto hash = ConfigInstance().hash; - _configRequestId = _api.request(MTPhelp_GetPassportConfig( - MTP_int(hash) - )).done([=](const MTPhelp_PassportConfig &result) { - _configRequestId = 0; - ConfigInstance() = ParseConfig(result); - showForm(); - }).fail([=](const MTP::Error &error) { - _configRequestId = 0; - showForm(); - }).send(); -} - bool FormController::parseForm(const MTPaccount_AuthorizationForm &result) { Expects(result.type() == mtpc_account_authorizationForm); @@ -2614,7 +2634,7 @@ void FormController::shortPollEmailConfirmation() { } void FormController::showForm() { - if (_formRequestId || _passwordRequestId || _configRequestId) { + if (_formRequestId || _passwordRequestId) { return; } else if (!_bot) { formFail(Lang::Hard::NoAuthorizationBot()); diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h index 1aa26be03..580778f9b 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_controller.h @@ -29,12 +29,7 @@ class Session; namespace Passport { -struct Config { - int32 hash = 0; - std::map languagesByCountryCode; -}; -Config &ConfigInstance(); -Config ParseConfig(const MTPhelp_PassportConfig &data); +struct EditDocumentCountry; struct SavedCredentials { bytes::vector hashForAuth; @@ -416,6 +411,9 @@ public: void cancel(); void cancelSure(); + [[nodiscard]] rpl::producer preferredLanguage( + const QString &countryCode); + rpl::lifetime &lifetime(); ~FormController(); @@ -439,7 +437,6 @@ private: void requestForm(); void requestPassword(); - void requestConfig(); void formDone(const MTPaccount_AuthorizationForm &result); void formFail(const QString &error); @@ -560,7 +557,6 @@ private: mtpRequestId _formRequestId = 0; mtpRequestId _passwordRequestId = 0; mtpRequestId _passwordCheckRequestId = 0; - mtpRequestId _configRequestId = 0; PasswordSettings _password; crl::time _lastSrpIdInvalidTime = 0; @@ -572,6 +568,11 @@ private: mtpRequestId _recoverRequestId = 0; base::flat_map> _fileLoaders; + struct { + int32 hash = 0; + std::map languagesByCountryCode; + } _passportConfig; + rpl::event_stream> _scanUpdated; rpl::event_stream> _valueSaveFinished; rpl::event_stream> _verificationNeeded; diff --git a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp index 1c07cb080..cd038acb5 100644 --- a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp @@ -377,7 +377,8 @@ QString ComputeScopeRowReadyString(const Scope &scope) { const auto scheme = GetDocumentScheme( scope.type, document ? base::make_optional(document->type) : std::nullopt, - scope.details ? scope.details->nativeNames : false); + scope.details ? scope.details->nativeNames : false, + nullptr); using ValueClass = EditDocumentScheme::ValueClass; const auto skipAdditional = [&] { if (!fields) { diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp index 900c4376d..c0f4d20e7 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp @@ -117,7 +117,8 @@ std::map PrepareSpecialFiles(const Value &value) { EditDocumentScheme GetDocumentScheme( Scope::Type type, std::optional scansType, - bool nativeNames) { + bool nativeNames, + preferredLangCallback &&preferredLanguage) { using Scheme = EditDocumentScheme; using ValueClass = Scheme::ValueClass; const auto DontFormat = nullptr; @@ -294,21 +295,17 @@ EditDocumentScheme GetDocumentScheme( if (nativeNames) { result.additionalDependencyKey = qsl("residence_country_code"); - 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::GetNonDefaultValue( - kLanguageNamePrefix + i->second.toUtf8()); + 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 QString &countryCode) { - const auto language = languageValue(countryCode); + 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( @@ -316,32 +313,28 @@ EditDocumentScheme GetDocumentScheme( lt_language, language); }; - result.additionalDescription = [=](const QString &countryCode) { - const auto language = languageValue(countryCode); + result.additionalDescription = [=]( + const EditDocumentCountry &info) { + const auto language = languageValue(info.languageCode); if (!language.isEmpty()) { - return tr::lng_passport_native_name_language_about(tr::now); + return tr::lng_passport_native_name_language_about( + tr::now); } const auto name = Countries::Instance().countryNameByISO2( - countryCode); + info.countryCode); Assert(!name.isEmpty()); return tr::lng_passport_native_name_about( tr::now, lt_country, name); }; - result.additionalShown = [](const QString &countryCode) { + result.additionalShown = [](const EditDocumentCountry &info) { 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; + return (info.countryCode.isEmpty()) + ? Result::Hidden + : (info.languageCode == "en") + ? Result::OnlyIfError + : Result::Shown; }; using Row = EditDocumentScheme::Row; auto additional = std::initializer_list{ @@ -1149,6 +1142,10 @@ void PanelController::startScopeEdit( _form->startValueEdit(_editDocument); } + auto preferredLanguage = [=](const QString &countryCode) { + return _form->preferredLanguage(countryCode); + }; + auto content = [&]() -> object_ptr { switch (_editScope->type) { case Scope::Type::Identity: @@ -1169,7 +1166,8 @@ void PanelController::startScopeEdit( GetDocumentScheme( _editScope->type, _editDocument->type, - _editValue->nativeNames), + _editValue->nativeNames, + std::move(preferredLanguage)), _editValue->error, _editValue->data.parsedInEdit, _editDocument->error, @@ -1183,7 +1181,8 @@ void PanelController::startScopeEdit( GetDocumentScheme( _editScope->type, _editDocument->type, - false), + false, + std::move(preferredLanguage)), _editDocument->error, _editDocument->data.parsedInEdit, std::move(scans), @@ -1204,7 +1203,8 @@ void PanelController::startScopeEdit( GetDocumentScheme( _editScope->type, std::nullopt, - _editValue->nativeNames), + _editValue->nativeNames, + std::move(preferredLanguage)), _editValue->error, _editValue->data.parsedInEdit); const auto weak = Ui::MakeWeak(result.data()); diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.h b/Telegram/SourceFiles/passport/passport_panel_controller.h index f0500dc38..94cd418c8 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.h +++ b/Telegram/SourceFiles/passport/passport_panel_controller.h @@ -20,15 +20,19 @@ namespace Passport { class FormController; class Panel; +struct EditDocumentCountry; struct EditDocumentScheme; struct EditContactScheme; enum class ReadScanError; +using preferredLangCallback = + Fn(const QString &)>; EditDocumentScheme GetDocumentScheme( Scope::Type type, std::optional scansType, - bool nativeNames); + bool nativeNames, + preferredLangCallback &&preferredLanguage); EditContactScheme GetContactScheme(Scope::Type type); const std::map &LatinToNativeMap(); diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp index ba82c0957..2b001e32e 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp @@ -425,25 +425,42 @@ not_null PanelEditDocument::setupContent( showIfError = true; } }); - const auto shown = [=](const QString &code) { + const auto shown = [=](const Scheme::CountryInfo &info) { using Result = Scheme::AdditionalVisibility; - const auto value = _scheme.additionalShown(code); + const auto value = _scheme.additionalShown(info); return (value == Result::Shown) || (value == Result::OnlyIfError && showIfError); }; - auto title = row->value( - ) | rpl::filter( + auto langValue = row->value( + ) | rpl::map( + _scheme.preferredLanguage + ) | rpl::flatten_latest(); + + auto title = rpl::duplicate(langValue) | rpl::filter( shown - ) | rpl::map([=](const QString &code) { - return _scheme.additionalHeader(code); + ) | rpl::map([=](const Scheme::CountryInfo &info) { + return _scheme.additionalHeader(info); }); - added->add( + const auto headerLabel = added->add( object_ptr( added, - std::move(title), + rpl::duplicate(title), st::passportFormHeader), st::passportNativeNameHeaderPadding); + std::move( + title + ) | rpl::start_with_next([=] { + const auto &padding = st::passportNativeNameHeaderPadding; + const auto available = added->width() + - padding.left() + - padding.right(); + headerLabel->resizeToNaturalWidth(available); + headerLabel->moveToLeft( + padding.left(), + padding.top(), + available); + }, headerLabel->lifetime()); enumerateRows([&]( int i, @@ -454,11 +471,10 @@ not_null PanelEditDocument::setupContent( } }); - auto description = row->value( - ) | rpl::filter( + auto description = rpl::duplicate(langValue) | rpl::filter( shown - ) | rpl::map([=](const QString &code) { - return _scheme.additionalDescription(code); + ) | rpl::map([=](const Scheme::CountryInfo &info) { + return _scheme.additionalDescription(info); }); added->add( object_ptr( @@ -470,11 +486,10 @@ not_null PanelEditDocument::setupContent( st::passportFormLabelPadding), st::passportNativeNameAboutMargin); - wrap->toggleOn(row->value() | rpl::map(shown)); + wrap->toggleOn(rpl::duplicate(langValue) | rpl::map(shown)); wrap->finishAnimating(); - row->value( - ) | rpl::map( + std::move(langValue) | rpl::map( shown ) | rpl::start_with_next([=](bool visible) { _additionalShown = visible; diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.h b/Telegram/SourceFiles/passport/passport_panel_edit_document.h index aa13908ce..0ab72f0aa 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.h +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.h @@ -39,6 +39,11 @@ class EditScans; enum class FileType; struct ScanListData; +struct EditDocumentCountry { + QString countryCode; + QString languageCode; +}; + struct EditDocumentScheme { enum class ValueClass { Fields, @@ -50,6 +55,7 @@ struct EditDocumentScheme { OnlyIfError, Shown, }; + using CountryInfo = EditDocumentCountry; struct Row { using Validator = Fn(const QString &value)>; using Formatter = Fn; @@ -69,9 +75,10 @@ struct EditDocumentScheme { QString scansHeader; QString additionalDependencyKey; - Fn additionalShown; - Fn additionalHeader; - Fn additionalDescription; + Fn additionalShown; + Fn additionalHeader; + Fn additionalDescription; + Fn(const QString &)> preferredLanguage; };