diff --git a/defines.hpp b/defines.hpp index 3a682fc5df..081f461a8b 100644 --- a/defines.hpp +++ b/defines.hpp @@ -78,7 +78,7 @@ #define WORLD_COASTS_FILE_NAME "WorldCoasts" #define SETTINGS_FILE_NAME "settings.ini" -#define MARKETING_SETTINGS_FILE_NAME "marketing_settings.ini" +#define PRODUCTS_SETTINGS_FILE_NAME "products_settings.json" #define SEARCH_CATEGORIES_FILE_NAME "categories.txt" #define SEARCH_CUISINE_CATEGORIES_FILE_NAME "categories_cuisines.txt" diff --git a/iphone/Maps/Core/Settings/MWMSettings.mm b/iphone/Maps/Core/Settings/MWMSettings.mm index 28bc2b835c..4b9710ae9e 100644 --- a/iphone/Maps/Core/Settings/MWMSettings.mm +++ b/iphone/Maps/Core/Settings/MWMSettings.mm @@ -149,7 +149,7 @@ NSString * const kUDFileLoggingEnabledKey = @"FileLoggingEnabledKey"; + (NSString *)donateUrl { std::string url; - return settings::Get("DonateUrl", url) ? @(url.c_str()) : nil; + return settings::Get(settings::kDonateUrl, url) ? @(url.c_str()) : nil; } + (BOOL)isNY diff --git a/map/framework.cpp b/map/framework.cpp index dc68173bbb..782c7ce216 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -102,6 +102,14 @@ std::string_view constexpr kTranslitMode = "TransliterationMode"; std::string_view constexpr kPreferredGraphicsAPI = "PreferredGraphicsAPI"; std::string_view constexpr kShowDebugInfo = "DebugInfo"; std::string_view constexpr kScreenViewport = "ScreenClipRect"; +std::string_view constexpr kPlacePageProductsPopupCloseTime = "PlacePageProductsPopupCloseTime"; +std::string_view constexpr kPlacePageProductsPopupCloseReason = "PlacePageProductsPopupCloseReason"; +std::string_view constexpr kPlacePageSelectedProduct = "PlacePageSelectedProduct"; + +std::string_view constexpr kProductsPopupCloseReasonCloseStr = "close"; +std::string_view constexpr kProductsPopupCloseReasonRemindLaterStr = "remind_later"; +std::string_view constexpr kProductsPopupCloseReasonAlreadyDonatedStr = "already_donated"; +std::string_view constexpr kProductsPopupCloseReasonSelectProductStr = "select_product"; auto constexpr kLargeFontsScaleFactor = 1.6; size_t constexpr kMaxTrafficCacheSizeBytes = 64 /* Mb */ * 1024 * 1024; @@ -3313,3 +3321,97 @@ void Framework::OnPowerSchemeChanged(power_management::Scheme const actualScheme if (actualScheme == power_management::Scheme::EconomyMaximum && GetTrafficManager().IsEnabled()) GetTrafficManager().SetEnabled(false); } + +bool Framework::ShouldShowProducts() const +{ + auto const connectionStatus = Platform::ConnectionStatus(); + if (connectionStatus == Platform::EConnectionType::CONNECTION_NONE) + return false; + + std::string donateUrl; + if (!settings::Get(settings::kDonateUrl, donateUrl)) // donation is disabled + return false; + + if (!m_usageStats.IsLoyalUser()) + return false; + + if (!storage::IsPointCoveredByDownloadedMaps(GetCurrentPlacePageInfo().GetMercator(), m_storage, *m_infoGetter)) + return false; + + uint64_t popupCloseTime; + std::string productCloseReason; + if (!settings::Get(kPlacePageProductsPopupCloseTime, popupCloseTime) || + !settings::Get(kPlacePageProductsPopupCloseReason, productCloseReason)) + return true; // The popup was never closed. + + auto const now = base::SecondsSinceEpoch(); + auto const timeout = GetTimeoutForReason(FromString(productCloseReason)); + bool const timeoutExpired = popupCloseTime + timeout < now; + if (timeoutExpired) + return true; + + return false; +} + +std::optional Framework::GetProductsConfiguration() const +{ + if (!ShouldShowProducts()) + return nullopt; + return products::GetProductsConfiguration(); +} + +void Framework::DidCloseProductsPopup(ProductsPopupCloseReason reason) const +{ + settings::Set(kPlacePageProductsPopupCloseTime, base::SecondsSinceEpoch()); + settings::Set(kPlacePageProductsPopupCloseReason, std::string(ToString(reason))); +} + +void Framework::DidSelectProduct(products::ProductsConfig::Product const & product) const +{ + settings::Set(kPlacePageSelectedProduct, product.GetTitle()); +} + +uint32_t Framework::GetTimeoutForReason(ProductsPopupCloseReason reason) const +{ + #ifdef DEBUG + uint32_t constexpr kPopupCloseTimeout = 10; + uint32_t constexpr kProductSelectTimeout = 20; + uint32_t constexpr kRemindMeLaterTimeout = 5; + #else + uint32_t constexpr kPopupCloseTimeout = 60 * 60 * 24 * 30; // 30 days + uint32_t constexpr kProductSelectTimeout = 60 * 60 * 24 * 180; // 180 days + uint32_t constexpr kRemindMeLaterTimeout = 60 * 60 * 24 * 3; // 3 days + #endif + switch (reason) + { + case ProductsPopupCloseReason::Close: return kPopupCloseTimeout; + case ProductsPopupCloseReason::RemindLater: return kRemindMeLaterTimeout; + case ProductsPopupCloseReason::AlreadyDonated: return kProductSelectTimeout; + case ProductsPopupCloseReason::SelectProduct: return kProductSelectTimeout; + } + ASSERT(false, ("Unknown reason")); + return kPopupCloseTimeout; +} + +std::string_view Framework::ToString(ProductsPopupCloseReason reason) const +{ + switch (reason) + { + case ProductsPopupCloseReason::Close: return kProductsPopupCloseReasonCloseStr; + case ProductsPopupCloseReason::RemindLater: return kProductsPopupCloseReasonRemindLaterStr; + case ProductsPopupCloseReason::AlreadyDonated: return kProductsPopupCloseReasonAlreadyDonatedStr; + case ProductsPopupCloseReason::SelectProduct: return kProductsPopupCloseReasonSelectProductStr; + } + ASSERT(false, ("Unknown reason")); + return kProductsPopupCloseReasonCloseStr; +} + +Framework::ProductsPopupCloseReason Framework::FromString(std::string const & str) const +{ + if (str == kProductsPopupCloseReasonCloseStr) return ProductsPopupCloseReason::Close; + if (str == kProductsPopupCloseReasonRemindLaterStr) return ProductsPopupCloseReason::RemindLater; + if (str == kProductsPopupCloseReasonAlreadyDonatedStr) return ProductsPopupCloseReason::AlreadyDonated; + if (str == kProductsPopupCloseReasonSelectProductStr) return ProductsPopupCloseReason::SelectProduct; + ASSERT(false, ("Incorrect reason string:", str)); + return ProductsPopupCloseReason::Close; +} diff --git a/map/framework.hpp b/map/framework.hpp index e99fbbb855..4276bab57b 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -47,6 +47,7 @@ #include "platform/location.hpp" #include "platform/platform.hpp" #include "platform/distance.hpp" +#include "platform/products.hpp" #include "routing/router.hpp" @@ -755,4 +756,24 @@ public: // PowerManager::Subscriber override. void OnPowerFacilityChanged(power_management::Facility const facility, bool enabled) override; void OnPowerSchemeChanged(power_management::Scheme const actualScheme) override; + +public: + std::optional GetProductsConfiguration() const; + + enum class ProductsPopupCloseReason + { + Close, + SelectProduct, + AlreadyDonated, + RemindLater + }; + + void DidCloseProductsPopup(ProductsPopupCloseReason reason) const; + void DidSelectProduct(products::ProductsConfig::Product const & product) const; + +private: + bool ShouldShowProducts() const; + uint32_t GetTimeoutForReason(ProductsPopupCloseReason reason) const; + std::string_view ToString(ProductsPopupCloseReason reason) const; + ProductsPopupCloseReason FromString(std::string const & str) const; }; diff --git a/platform/CMakeLists.txt b/platform/CMakeLists.txt index fbad0d52c0..0e4f40becd 100644 --- a/platform/CMakeLists.txt +++ b/platform/CMakeLists.txt @@ -53,6 +53,8 @@ set(SRC secure_storage.hpp servers_list.cpp servers_list.hpp + products.cpp + products.hpp settings.cpp settings.hpp socket.hpp diff --git a/platform/platform_tests/CMakeLists.txt b/platform/platform_tests/CMakeLists.txt index d2be24a22f..b3cb32c5f8 100644 --- a/platform/platform_tests/CMakeLists.txt +++ b/platform/platform_tests/CMakeLists.txt @@ -14,6 +14,8 @@ set(SRC location_test.cpp measurement_tests.cpp platform_test.cpp + meta_config_tests.cpp + products_tests.cpp utm_mgrs_utils_tests.cpp ) diff --git a/platform/platform_tests/meta_config_tests.cpp b/platform/platform_tests/meta_config_tests.cpp new file mode 100644 index 0000000000..3597a33476 --- /dev/null +++ b/platform/platform_tests/meta_config_tests.cpp @@ -0,0 +1,110 @@ +#include "testing/testing.hpp" + +#include "platform/servers_list.hpp" + +#include "platform/products.hpp" + +#include "cppjansson/cppjansson.hpp" + +using namespace downloader; + +UNIT_TEST(MetaConfig_JSONParser_OldFormat) +{ + std::string oldFormatJson = R"(["http://url1", "http://url2", "http://url3"])"; + auto result = ParseMetaConfig(oldFormatJson); + TEST(result.has_value(), ()); + TEST_EQUAL(result->m_serversList.size(), 3, ()); + TEST_EQUAL(result->m_serversList[0], "http://url1", ()); + TEST_EQUAL(result->m_serversList[1], "http://url2", ()); + TEST_EQUAL(result->m_serversList[2], "http://url3", ()); + TEST(result->m_settings.empty(), ()); + TEST(result->m_productsConfig.empty(), ()); +} + +UNIT_TEST(MetaConfig_JSONParser_InvalidJSON) +{ + std::string invalidJson = R"({"servers": ["http://url1", "http://url2")"; + auto result = ParseMetaConfig(invalidJson); + TEST(!result.has_value(), ()); +} + +UNIT_TEST(MetaConfig_JSONParser_EmptyServersList) +{ + std::string emptyServersJson = R"({"servers": []})"; + auto result = ParseMetaConfig(emptyServersJson); + TEST(!result.has_value(), ()); +} + +UNIT_TEST(MetaConfig_JSONParser_NewFormatWithoutProducts) +{ + std::string newFormatJson = R"({ + "servers": ["http://url1", "http://url2"], + "settings": { + "key1": "value1", + "key2": "value2" + } + })"; + auto result = ParseMetaConfig(newFormatJson); + TEST(result.has_value(), ()); + TEST_EQUAL(result->m_serversList.size(), 2, ()); + TEST_EQUAL(result->m_serversList[0], "http://url1", ()); + TEST_EQUAL(result->m_serversList[1], "http://url2", ()); + TEST_EQUAL(result->m_settings.size(), 2, ()); + TEST_EQUAL(result->m_settings["key1"], "value1", ()); + TEST_EQUAL(result->m_settings["key2"], "value2", ()); + TEST(result->m_productsConfig.empty(), ()); +} + +UNIT_TEST(MetaConfig_JSONParser_NewFormatWithProducts) +{ + std::string newFormatJson = R"({ + "servers": ["http://url1", "http://url2"], + "settings": { + "key1": "value1", + "key2": "value2" + }, + "productsConfig": { + "placePagePrompt": "prompt1", + "aboutScreenPrompt": "prompt2", + "products": [ + { + "title": "Product 1", + "link": "http://product1" + }, + { + "title": "Product 2", + "link": "http://product2" + } + ] + } + })"; + + auto result = ParseMetaConfig(newFormatJson); + TEST(result.has_value(), ()); + TEST_EQUAL(result->m_serversList.size(), 2, ()); + TEST_EQUAL(result->m_serversList[0], "http://url1", ()); + TEST_EQUAL(result->m_serversList[1], "http://url2", ()); + TEST_EQUAL(result->m_settings.size(), 2, ()); + TEST_EQUAL(result->m_settings["key1"], "value1", ()); + TEST_EQUAL(result->m_settings["key2"], "value2", ()); + + TEST(!result->m_productsConfig.empty(), ()); + auto const productsConfigResult = products::ProductsConfig::Parse(result->m_productsConfig); + TEST(productsConfigResult.has_value(), ()); + auto const productsConfig = productsConfigResult.value(); + TEST_EQUAL(productsConfig.GetPlacePagePrompt(), "prompt1", ()); + TEST(productsConfig.HasProducts(), ()); + auto const products = productsConfig.GetProducts(); + TEST_EQUAL(products.size(), 2, ()); +} + +UNIT_TEST(MetaConfig_JSONParser_MissingServersKey) +{ + std::string missingServersJson = R"({ + "settings": { + "key1": "value1" + } + })"; + auto result = ParseMetaConfig(missingServersJson); + TEST(!result.has_value(), ("JSON shouldn't be parsed without 'servers' key")); +} diff --git a/platform/platform_tests/products_tests.cpp b/platform/platform_tests/products_tests.cpp new file mode 100644 index 0000000000..25382932e8 --- /dev/null +++ b/platform/platform_tests/products_tests.cpp @@ -0,0 +1,107 @@ +#include "testing/testing.hpp" + +#include "platform/products.hpp" + +#include "cppjansson/cppjansson.hpp" + +using namespace products; + +UNIT_TEST(ProductsConfig_ValidConfig) +{ + std::string jsonStr = R"({ + "placePagePrompt": "prompt1", + "products": [ + { + "title": "Product 1", + "link": "http://product1" + }, + { + "title": "Product 2", + "link": "http://product2" + } + ] + })"; + + auto const result = ProductsConfig::Parse(jsonStr); + TEST(result.has_value(), ()); + auto const productsConfig = result.value(); + TEST_EQUAL(productsConfig.GetPlacePagePrompt(), "prompt1", ()); + + auto const products = productsConfig.GetProducts(); + TEST_EQUAL(products.size(), 2, ()); + TEST_EQUAL(products[0].GetTitle(), "Product 1", ()); + TEST_EQUAL(products[0].GetLink(), "http://product1", ()); + TEST_EQUAL(products[1].GetTitle(), "Product 2", ()); + TEST_EQUAL(products[1].GetLink(), "http://product2", ()); +} + +UNIT_TEST(ProductsConfig_EmptyPrompts) +{ + std::string jsonStr = R"({ + "aboutScreenPrompt": "", + "products": [ + { + "title": "Product 1", + "link": "http://product1" + }, + { + "title": "Product 2", + "link": "http://product2" + } + ] + })"; + + auto const result = ProductsConfig::Parse(jsonStr); + TEST(result.has_value(), ()); + auto const productsConfig = result.value(); + TEST_EQUAL(productsConfig.GetPlacePagePrompt(), "", ()); + TEST_EQUAL(productsConfig.GetProducts().size(), 2, ()); +} + +UNIT_TEST(ProductsConfig_InvalidProduct) +{ + std::string jsonStr = R"({ + "placePagePrompt": "prompt1", + "products": [ + { + "title": "Product 1" + }, + { + "title": "Product 2", + "link": "http://product2" + } + ] + })"; + + auto const result = ProductsConfig::Parse(jsonStr); + TEST(result.has_value(), ()); + auto const productsConfig = result.value(); + TEST_EQUAL(productsConfig.GetPlacePagePrompt(), "prompt1", ()); + + auto const products = productsConfig.GetProducts(); + TEST_EQUAL(products.size(), 1, ()); + TEST_EQUAL(products[0].GetTitle(), "Product 2", ()); + TEST_EQUAL(products[0].GetLink(), "http://product2", ()); +} + +UNIT_TEST(ProductsConfig_EmptyProducts) +{ + std::string jsonStr = R"({ + "placePagePrompt": "prompt1", + "products": [] + })"; + + auto const result = ProductsConfig::Parse(jsonStr); + TEST(!result.has_value(), ()); +} + +UNIT_TEST(ProductsConfig_MissedProductsField) +{ + std::string jsonStr = R"({ + "placePagePrompt": "prompt1" + })"; + + auto const result = ProductsConfig::Parse(jsonStr); + TEST(!result.has_value(), ()); +} + diff --git a/platform/products.cpp b/platform/products.cpp new file mode 100644 index 0000000000..7c651cbd2f --- /dev/null +++ b/platform/products.cpp @@ -0,0 +1,118 @@ +#include "platform/products.hpp" +#include "platform/platform.hpp" + +#include "base/logging.hpp" +#include "base/assert.hpp" +#include "base/string_utils.hpp" + +#include "defines.hpp" + +#include "coding/file_writer.hpp" + +#include "cppjansson/cppjansson.hpp" + +namespace products { + +char const kPlacePagePrompt[] = "placePagePrompt"; +char const kProducts[] = "products"; +char const kProductTitle[] = "title"; +char const kProductLink[] = "link"; + +std::string GetProductsFilePath() +{ + return GetPlatform().SettingsPathForFile(PRODUCTS_SETTINGS_FILE_NAME); +} + +ProductsSettings::ProductsSettings() +{ + std::lock_guard guard(m_mutex); + auto const path = GetProductsFilePath(); + if (Platform::IsFileExistsByFullPath(path)) + { + try + { + std::string outValue; + auto dataReader = GetPlatform().GetReader(path); + dataReader->ReadAsString(outValue); + m_productsConfig = ProductsConfig::Parse(outValue); + } + catch (std::exception const & ex) + { + LOG(LERROR, ("Error reading ProductsConfig file.", ex.what())); + } + } + LOG(LWARNING, ("ProductsConfig file not found.")); +} + +ProductsSettings & ProductsSettings::Instance() +{ + static ProductsSettings instance; + return instance; +} + +std::optional ProductsSettings::Get() +{ + std::lock_guard guard(m_mutex); + return m_productsConfig; +} + +void ProductsSettings::Update(std::string const & jsonStr) +{ + std::lock_guard guard(m_mutex); + if (jsonStr.empty()) + FileWriter::DeleteFileX(GetProductsFilePath()); + else + { + try + { + FileWriter file(GetProductsFilePath()); + file.Write(jsonStr.data(), jsonStr.size()); + m_productsConfig = ProductsConfig::Parse(jsonStr); + } + catch (std::exception const & ex) + { + LOG(LERROR, ("Error writing ProductsConfig file.", ex.what())); + } + } +} + +std::optional ProductsConfig::Parse(std::string const & jsonStr) +{ + const base::Json root(jsonStr.c_str()); + auto const json = root.get(); + auto const productsObj = json_object_get(json, kProducts); + if (!json_is_object(json) || !productsObj || !json_is_array(productsObj)) + { + LOG(LWARNING, ("Failed to parse ProductsConfig:", jsonStr)); + return std::nullopt; + } + + ProductsConfig config; + auto const placePagePrompt = json_object_get(json, kPlacePagePrompt); + if (placePagePrompt && json_is_string(placePagePrompt)) + config.m_placePagePrompt = json_string_value(placePagePrompt); + + for (size_t i = 0; i < json_array_size(productsObj); ++i) + { + json_t * product = json_array_get(productsObj, i); + if (!product || !json_is_object(product)) + { + LOG(LWARNING, ("Failed to parse Product:", jsonStr)); + continue; + } + json_t * title = json_object_get(product, kProductTitle); + json_t * link = json_object_get(product, kProductLink); + if (title && link && json_is_string(title) && json_is_string(link)) + config.m_products.push_back({json_string_value(title), json_string_value(link)}); + else + LOG(LWARNING, ("Failed to parse Product:", jsonStr)); + } + if (config.m_products.empty()) + { + LOG(LWARNING, ("Products list is empty")); + return std::nullopt; + } + return config; +} + +} // namespace products diff --git a/platform/products.hpp b/platform/products.hpp new file mode 100644 index 0000000000..f7829f25d9 --- /dev/null +++ b/platform/products.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include + +namespace products { + +struct ProductsConfig +{ + struct Product + { + private: + std::string m_title; + std::string m_link; + + public: + Product(std::string const & title, std::string const & link) + : m_title(title), m_link(link) + {} + + std::string const & GetTitle() const { return m_title; } + std::string const & GetLink() const { return m_link; } + }; + +private: + std::string m_placePagePrompt; + std::vector m_products; + +public: + std::string const GetPlacePagePrompt() const { return m_placePagePrompt; } + std::vector const & GetProducts() const { return m_products; } + bool HasProducts() const { return !m_products.empty(); } + + static std::optional Parse(std::string const & jsonStr); +}; + +class ProductsSettings +{ +private: + ProductsSettings(); + + std::optional m_productsConfig; + mutable std::mutex m_mutex; + +public: + static ProductsSettings & Instance(); + + void Update(std::string const & jsonStr); + std::optional Get(); +}; + +inline void Update(std::string const & jsonStr) +{ + ProductsSettings::Instance().Update(jsonStr); +} + +inline std::optional GetProductsConfiguration() +{ + return ProductsSettings::Instance().Get(); +} + +} // namespace products diff --git a/platform/servers_list.cpp b/platform/servers_list.cpp index 03bf6a2037..d71ba5275e 100644 --- a/platform/servers_list.cpp +++ b/platform/servers_list.cpp @@ -10,8 +10,13 @@ namespace downloader { + std::optional ParseMetaConfig(std::string const & jsonStr) { + char const kSettings[] = "settings"; + char const kServers[] = "servers"; + char const kProductsConfig[] = "productsConfig"; + MetaConfig outMetaConfig; try { @@ -28,7 +33,7 @@ std::optional ParseMetaConfig(std::string const & jsonStr) // } // } - json_t * settings = json_object_get(root.get(), "settings"); + json_t * settings = json_object_get(root.get(), kSettings); const char * key; const json_t * value; json_object_foreach(settings, key, value) @@ -38,7 +43,13 @@ std::optional ParseMetaConfig(std::string const & jsonStr) outMetaConfig.m_settings[key] = valueStr; } - servers = json_object_get(root.get(), "servers"); + servers = json_object_get(root.get(), kServers); + + auto const productsConfig = json_object_get(root.get(), kProductsConfig); + if (productsConfig) + outMetaConfig.m_productsConfig = json_dumps(productsConfig, JSON_ENCODE_ANY); + else + LOG(LINFO, ("No ProductsConfig in meta configuration")); } else { diff --git a/platform/servers_list.hpp b/platform/servers_list.hpp index c6f605c4df..7887c02e72 100644 --- a/platform/servers_list.hpp +++ b/platform/servers_list.hpp @@ -14,6 +14,7 @@ struct MetaConfig ServersList m_serversList; using SettingsMap = std::map; SettingsMap m_settings; + std::string m_productsConfig; }; std::optional ParseMetaConfig(std::string const & jsonStr); diff --git a/platform/settings.cpp b/platform/settings.cpp index 802fd64afe..371b49bcba 100644 --- a/platform/settings.cpp +++ b/platform/settings.cpp @@ -23,6 +23,7 @@ using namespace std; std::string_view kMeasurementUnits = "Units"; std::string_view kMapLanguageCode = "MapLanguageCode"; std::string_view kDeveloperMode = "DeveloperMode"; +std::string_view kDonateUrl = "DonateUrl"; StringStorage::StringStorage() : StringStorageBase(GetPlatform().SettingsPathForFile(SETTINGS_FILE_NAME)) {} @@ -425,18 +426,16 @@ void UsageStats::EnterBackground() m_ss.SetValue(m_sessions, ToString(m_sessionsCount)); } -} // namespace settings - -/* -namespace marketing +bool UsageStats::IsLoyalUser() const { -Settings::Settings() : platform::StringStorageBase(GetPlatform().SettingsPathForFile(MARKETING_SETTINGS_FILE_NAME)) {} - -// static -Settings & Settings::Instance() -{ - static Settings instance; - return instance; + #ifdef DEBUG + uint32_t constexpr kMinTotalForegroundTimeout = 30; + uint32_t constexpr kMinSessionsCount = 3; + #else + uint32_t constexpr kMinTotalForegroundTimeout = 60 * 30; // 30 min + uint32_t constexpr kMinSessionsCount = 5; + #endif + return m_sessionsCount >= kMinSessionsCount && m_totalForegroundTime >= kMinTotalForegroundTimeout; } -} // namespace marketing -*/ + +} // namespace settings diff --git a/platform/settings.hpp b/platform/settings.hpp index 18aa97edcd..9a0e0457ac 100644 --- a/platform/settings.hpp +++ b/platform/settings.hpp @@ -12,6 +12,7 @@ namespace settings extern std::string_view kMeasurementUnits; extern std::string_view kDeveloperMode; extern std::string_view kMapLanguageCode; +extern std::string_view kDonateUrl; template bool FromString(std::string const & str, T & outValue); @@ -76,32 +77,8 @@ public: void EnterForeground(); void EnterBackground(); + + bool IsLoyalUser() const; }; } // namespace settings - -/* -namespace marketing -{ -class Settings : public platform::StringStorageBase -{ -public: - template - static void Set(std::string const & key, Value const & value) - { - Instance().SetValue(key, settings::ToString(value)); - } - - template - [[nodiscard]] static bool Get(std::string const & key, Value & outValue) - { - std::string strVal; - return Instance().GetValue(key, strVal) && settings::FromString(strVal, outValue); - } - -private: - static Settings & Instance(); - Settings(); -}; -} // namespace marketing -*/ diff --git a/storage/map_files_downloader.cpp b/storage/map_files_downloader.cpp index 9c29fdd11a..9dd98843e4 100644 --- a/storage/map_files_downloader.cpp +++ b/storage/map_files_downloader.cpp @@ -7,6 +7,8 @@ #include "platform/platform.hpp" #include "platform/servers_list.hpp" #include "platform/settings.hpp" +#include "platform/products.hpp" +#include "platform/locale.hpp" #include "coding/url.hpp" @@ -49,7 +51,7 @@ void MapFilesDownloader::RunMetaConfigAsync(std::function && callback) { m_serversList = metaConfig.m_serversList; settings::Update(metaConfig.m_settings); - + products::Update(metaConfig.m_productsConfig); callback(); // Reset flag to invoke servers list downloading next time if current request has failed. @@ -149,6 +151,12 @@ std::vector MapFilesDownloader::MakeUrlList(std::string const & rel return urls; } +std::string GetAcceptLanguage() +{ + auto const locale = platform::GetCurrentLocale(); + return locale.m_language + "-" + locale.m_country; +} + // static MetaConfig MapFilesDownloader::LoadMetaConfig() { @@ -160,7 +168,8 @@ MetaConfig MapFilesDownloader::LoadMetaConfig() { platform::HttpClient request(metaServerUrl); request.SetRawHeader("X-OM-DataVersion", std::to_string(m_dataVersion)); - request.SetRawHeader("X-OM-AppVersion", GetPlatform().Version()); + request.SetRawHeader("X-OM-AppVersion", pl.Version()); + request.SetRawHeader("Accept-Language", GetAcceptLanguage()); request.SetTimeout(10.0); // timeout in seconds request.RunHttpRequest(httpResult); } diff --git a/xcode/platform/platform.xcodeproj/project.pbxproj b/xcode/platform/platform.xcodeproj/project.pbxproj index 1b1fa72e9b..d59e90274d 100644 --- a/xcode/platform/platform.xcodeproj/project.pbxproj +++ b/xcode/platform/platform.xcodeproj/project.pbxproj @@ -114,6 +114,10 @@ D5B191CF2386C7E4009CD0D6 /* http_uploader_background.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D5B191CE2386C7E4009CD0D6 /* http_uploader_background.hpp */; }; EB60B4DC204C130300E4953B /* network_policy_ios.mm in Sources */ = {isa = PBXBuildFile; fileRef = EB60B4DB204C130300E4953B /* network_policy_ios.mm */; }; EB60B4DE204C175700E4953B /* network_policy_ios.h in Headers */ = {isa = PBXBuildFile; fileRef = EB60B4DD204C175700E4953B /* network_policy_ios.h */; }; + ED49D7402CEE438E004AF27E /* products.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED49D73F2CEE438E004AF27E /* products.cpp */; }; + ED49D7412CEE438E004AF27E /* products.hpp in Headers */ = {isa = PBXBuildFile; fileRef = ED49D73E2CEE438E004AF27E /* products.hpp */; }; + ED49D7442CEE43A4004AF27E /* meta_config_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED49D7422CEE43A4004AF27E /* meta_config_tests.cpp */; }; + ED49D7452CEE43A4004AF27E /* products_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED49D7432CEE43A4004AF27E /* products_tests.cpp */; }; ED965B252CD8F72E0049E39E /* duration.hpp in Headers */ = {isa = PBXBuildFile; fileRef = ED965B242CD8F72A0049E39E /* duration.hpp */; }; ED965B272CD8F7810049E39E /* duration.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED965B262CD8F77D0049E39E /* duration.cpp */; }; ED965B472CDA52DB0049E39E /* duration_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED965B462CDA4EC00049E39E /* duration_tests.cpp */; }; @@ -256,6 +260,10 @@ D5B191CE2386C7E4009CD0D6 /* http_uploader_background.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = http_uploader_background.hpp; sourceTree = ""; }; EB60B4DB204C130300E4953B /* network_policy_ios.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = network_policy_ios.mm; sourceTree = ""; }; EB60B4DD204C175700E4953B /* network_policy_ios.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = network_policy_ios.h; sourceTree = ""; }; + ED49D73E2CEE438E004AF27E /* products.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = products.hpp; sourceTree = ""; }; + ED49D73F2CEE438E004AF27E /* products.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = products.cpp; sourceTree = ""; }; + ED49D7422CEE43A4004AF27E /* meta_config_tests.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = meta_config_tests.cpp; sourceTree = ""; }; + ED49D7432CEE43A4004AF27E /* products_tests.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = products_tests.cpp; sourceTree = ""; }; ED965B242CD8F72A0049E39E /* duration.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = duration.hpp; sourceTree = ""; }; ED965B262CD8F77D0049E39E /* duration.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = duration.cpp; sourceTree = ""; }; ED965B462CDA4EC00049E39E /* duration_tests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = duration_tests.cpp; sourceTree = ""; }; @@ -317,6 +325,8 @@ 675340DE1C58C405002CF0D9 /* platform_tests */ = { isa = PBXGroup; children = ( + ED49D7422CEE43A4004AF27E /* meta_config_tests.cpp */, + ED49D7432CEE43A4004AF27E /* products_tests.cpp */, ED965B462CDA4EC00049E39E /* duration_tests.cpp */, 168EFCC12A30EB7400F71EE8 /* distance_tests.cpp */, 675341001C58C4C9002CF0D9 /* apk_test.cpp */, @@ -466,6 +476,8 @@ 675343A71A3F5D5A00A0A8C3 /* servers_list.hpp */, 675343A81A3F5D5A00A0A8C3 /* settings.cpp */, 675343A91A3F5D5A00A0A8C3 /* settings.hpp */, + ED49D73E2CEE438E004AF27E /* products.hpp */, + ED49D73F2CEE438E004AF27E /* products.cpp */, 34C624BB1DABCCD100510300 /* socket_apple.mm */, 34C624BC1DABCCD100510300 /* socket.hpp */, F6DF73561EC9EAE700D8BA0B /* string_storage_base.cpp */, @@ -544,6 +556,7 @@ 675343CD1A3F5D5A00A0A8C3 /* platform.hpp in Headers */, 6741250F1B4C00CC00A3E828 /* local_country_file.hpp in Headers */, 3D15ACE2214A707900F725D5 /* localization.hpp in Headers */, + ED49D7412CEE438E004AF27E /* products.hpp in Headers */, D5B191CF2386C7E4009CD0D6 /* http_uploader_background.hpp in Headers */, 1669C8422A30DCD200530AD1 /* distance.hpp in Headers */, 3DE8B98F1DEC3115000E6083 /* network_policy.hpp in Headers */, @@ -699,6 +712,7 @@ ED965B272CD8F7810049E39E /* duration.cpp in Sources */, EB60B4DC204C130300E4953B /* network_policy_ios.mm in Sources */, 6741250E1B4C00CC00A3E828 /* local_country_file.cpp in Sources */, + ED49D7402CEE438E004AF27E /* products.cpp in Sources */, 670E8C761BB318AB00094197 /* platform_ios.mm in Sources */, 67A2526A1BB40E100063F8A8 /* platform_qt.cpp in Sources */, 56EB1EDE1C6B6E6C0022D831 /* mwm_traits.cpp in Sources */, @@ -737,6 +751,8 @@ buildActionMask = 2147483647; files = ( 6783389E1C6DE59200FD6263 /* platform_test.cpp in Sources */, + ED49D7442CEE43A4004AF27E /* meta_config_tests.cpp in Sources */, + ED49D7452CEE43A4004AF27E /* products_tests.cpp in Sources */, 678338961C6DE59200FD6263 /* apk_test.cpp in Sources */, 678338991C6DE59200FD6263 /* jansson_test.cpp in Sources */, ED965B482CDA575B0049E39E /* duration.cpp in Sources */,