From 6e8254b81537773173302601721e185306122bff Mon Sep 17 00:00:00 2001 From: jpirnay Date: Thu, 26 Mar 2026 18:46:28 +0100 Subject: [PATCH] Adding timezone support --- lib/I18n/translations/english.yaml | 24 ++ lib/hal/HalClock.cpp | 33 ++ lib/hal/HalClock.h | 3 + src/CrossPointSettings.h | 23 ++ src/SettingsList.h | 6 + src/activities/ActivityManager.cpp | 13 + .../settings/DetectTimezoneActivity.cpp | 315 ++++++++++++++++++ .../settings/DetectTimezoneActivity.h | 28 ++ src/activities/settings/SettingsActivity.cpp | 10 + src/activities/settings/SettingsActivity.h | 1 + src/main.cpp | 1 + 11 files changed, 457 insertions(+) create mode 100644 src/activities/settings/DetectTimezoneActivity.cpp create mode 100644 src/activities/settings/DetectTimezoneActivity.h diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 7a7e4b81..9047748c 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -85,12 +85,36 @@ STR_SHOW_HIDDEN_FILES: "Show Hidden Files" STR_KEEP_CLOCK_ALIVE: "Keep Clock Alive" STR_CLOCK: "Clock" STR_CLOCK_FORMAT: "Clock Format" +STR_TIMEZONE: "Timezone" STR_24H: "24h" STR_12H: "12h" +STR_TZ_UTC: "UTC (GMT/BST)" +STR_TZ_CET: "Central Europe (CET/CEST)" +STR_TZ_EET: "Eastern Europe (EET/EEST)" +STR_TZ_EST: "US Eastern (EST/EDT)" +STR_TZ_CST: "US Central (CST/CDT)" +STR_TZ_MST: "US Mountain (MST/MDT)" +STR_TZ_PST: "US Pacific (PST/PDT)" +STR_TZ_AEST: "Australia Eastern (AEST/AEDT)" +STR_TZ_NZST: "New Zealand (NZST/NZDT)" +STR_TZ_MSK: "Russia (MSK)" +STR_TZ_UTC_MINUS3: "South America (UTC-3)" +STR_TZ_UTC_PLUS4: "Gulf (UTC+4)" +STR_TZ_IST: "India (UTC+5:30)" +STR_TZ_UTC_PLUS7: "SE Asia (UTC+7)" +STR_TZ_UTC_PLUS8: "China/SE Asia (UTC+8)" +STR_TZ_UTC_PLUS9: "Japan/Korea (UTC+9)" STR_SYNC_TIME: "Sync Time" +STR_DETECT_TIMEZONE: "Detect Timezone" STR_SYNCING_CLOCK: "Syncing clock..." +STR_DETECTING_TIMEZONE: "Detecting timezone..." STR_TIME_SYNCED: "Time synced" STR_TIME_SYNC_FAILED: "Time sync failed" +STR_TIMEZONE_DETECTED: "Timezone detected" +STR_TIMEZONE_DETECT_FAILED: "Timezone detect failed" +STR_DST_ACTIVE: "DST: active" +STR_DST_INACTIVE: "DST: inactive" +STR_DST_UNKNOWN: "DST: unknown" STR_REFRESH_FREQ: "Refresh Frequency" STR_KOREADER_SYNC: "KOReader Sync" STR_CHECK_UPDATES: "Check for updates" diff --git a/lib/hal/HalClock.cpp b/lib/hal/HalClock.cpp index 7edad342..4d32dcbb 100644 --- a/lib/hal/HalClock.cpp +++ b/lib/hal/HalClock.cpp @@ -6,6 +6,9 @@ #include #include #include +#include + +#include // ---- RTC-memory state (survives deep sleep, not cold boot) ---------------- @@ -19,6 +22,29 @@ RTC_NOINIT_ATTR static uint64_t rtcLpTimeUs; // esp_clk_rtc_time() at capture static bool clockApproximate = true; +struct TimeZoneEntry { + const char* tz; +}; + +static constexpr TimeZoneEntry TIMEZONES[] = { + {"GMT0BST,M3.5.0/1,M10.5.0/2"}, + {"CET-1CEST,M3.5.0/2,M10.5.0/3"}, + {"EET-2EEST,M3.5.0/3,M10.5.0/4"}, + {"MSK-3"}, + {"UTC-4"}, + {"UTC-5:30"}, + {"UTC-7"}, + {"UTC-8"}, + {"UTC-9"}, + {"AEST-10AEDT,M10.1.0/2,M4.1.0/3"}, + {"NZST-12NZDT,M9.5.0/2,M4.1.0/3"}, + {"UTC+3"}, + {"EST5EDT,M3.2.0/2,M11.1.0/2"}, + {"CST6CDT,M3.2.0/2,M11.1.0/2"}, + {"MST7MDT,M3.2.0/2,M11.1.0/2"}, + {"PST8PDT,M3.2.0/2,M11.1.0/2"}, +}; + // ---- NVS helpers ---------------------------------------------------------- static constexpr char NVS_NAMESPACE[] = "halclock"; @@ -65,6 +91,13 @@ static void capture(bool lpValid) { namespace HalClock { +void applyTimezone(uint8_t timeZoneSetting) { + const size_t index = timeZoneSetting < (sizeof(TIMEZONES) / sizeof(TIMEZONES[0])) ? timeZoneSetting : 0; + setenv("TZ", TIMEZONES[index].tz, 1); + tzset(); + LOG_DBG("CLK", "Timezone applied: %s", TIMEZONES[index].tz); +} + bool syncNtp() { if (esp_sntp_enabled()) { esp_sntp_stop(); diff --git a/lib/hal/HalClock.h b/lib/hal/HalClock.h index e5dd5d7a..05cc3101 100644 --- a/lib/hal/HalClock.h +++ b/lib/hal/HalClock.h @@ -30,6 +30,9 @@ namespace HalClock { /// Returns true if the sync succeeded. bool syncNtp(); +/// Apply timezone/DST rules via the POSIX TZ string for the given setting. +void applyTimezone(uint8_t timeZoneSetting); + /// Call just before deep sleep. Snapshots the current system time to RTC /// memory and NVS so it can be restored on wake / cold boot. Pass true when /// the LP timer is kept alive during sleep. diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 65858128..269a866d 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -137,6 +137,27 @@ class CrossPointSettings { // Image rendering in EPUB reader enum IMAGE_RENDERING { IMAGES_DISPLAY = 0, IMAGES_PLACEHOLDER = 1, IMAGES_SUPPRESS = 2, IMAGE_RENDERING_COUNT }; + // Timezone options (POSIX TZ rules for DST support) + enum TIMEZONE { + TZ_UTC = 0, + TZ_CET = 1, + TZ_EET = 2, + TZ_MSK = 3, + TZ_UTC_PLUS4 = 4, + TZ_IST = 5, + TZ_UTC_PLUS7 = 6, + TZ_UTC_PLUS8 = 7, + TZ_UTC_PLUS9 = 8, + TZ_AEST = 9, + TZ_NZST = 10, + TZ_UTC_MINUS3 = 11, + TZ_EST = 12, + TZ_CST = 13, + TZ_MST = 14, + TZ_PST = 15, + TIMEZONE_COUNT + }; + // Sleep screen settings uint8_t sleepScreen = DARK; // Sleep screen cover mode settings @@ -203,6 +224,8 @@ class CrossPointSettings { uint8_t statusBarClock = 0; // Clock format: 0 = 24h (14:00), 1 = 12h (2:00pm) uint8_t clockFormat12h = 0; + // Timezone selection (applies POSIX TZ rules for DST) + uint8_t timeZone = TZ_UTC; // Keep the LP timer running during deep sleep (GPIO13 HIGH) so the clock // can be accurately restored on wake. Increases sleep current by ~3-4 mA. uint8_t keepClockAlive = 0; diff --git a/src/SettingsList.h b/src/SettingsList.h index 8f6d3af0..d01119d1 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -82,6 +82,12 @@ inline const std::vector& getSettingsList() { StrId::STR_CAT_SYSTEM), SettingInfo::Enum(StrId::STR_CLOCK_FORMAT, &CrossPointSettings::clockFormat12h, {StrId::STR_24H, StrId::STR_12H}, "clockFormat12h", StrId::STR_CAT_SYSTEM), + SettingInfo::Enum(StrId::STR_TIMEZONE, &CrossPointSettings::timeZone, + {StrId::STR_TZ_UTC, StrId::STR_TZ_CET, StrId::STR_TZ_EET, StrId::STR_TZ_MSK, + StrId::STR_TZ_UTC_PLUS4, StrId::STR_TZ_IST, StrId::STR_TZ_UTC_PLUS7, StrId::STR_TZ_UTC_PLUS8, + StrId::STR_TZ_UTC_PLUS9, StrId::STR_TZ_AEST, StrId::STR_TZ_NZST, StrId::STR_TZ_UTC_MINUS3, + StrId::STR_TZ_EST, StrId::STR_TZ_CST, StrId::STR_TZ_MST, StrId::STR_TZ_PST}, + "timeZone", StrId::STR_CAT_SYSTEM), SettingInfo::Toggle(StrId::STR_KEEP_CLOCK_ALIVE, &CrossPointSettings::keepClockAlive, "keepClockAlive", StrId::STR_CAT_SYSTEM), diff --git a/src/activities/ActivityManager.cpp b/src/activities/ActivityManager.cpp index 3cf115b1..e1a01cea 100644 --- a/src/activities/ActivityManager.cpp +++ b/src/activities/ActivityManager.cpp @@ -1,5 +1,6 @@ #include "ActivityManager.h" +#include #include #include "boot_sleep/BootActivity.h" @@ -56,6 +57,18 @@ void ActivityManager::loop() { currentActivity->loop(); } + if (SETTINGS.statusBarClock && HalClock::isSynced()) { + static time_t lastClockMinute = 0; + time_t now = HalClock::now(); + if (now > 0) { + time_t minute = now / 60; + if (minute != lastClockMinute) { + lastClockMinute = minute; + requestUpdate(); + } + } + } + while (pendingAction != PendingAction::None) { if (pendingAction == PendingAction::Pop) { RenderLock lock; diff --git a/src/activities/settings/DetectTimezoneActivity.cpp b/src/activities/settings/DetectTimezoneActivity.cpp new file mode 100644 index 00000000..686c2545 --- /dev/null +++ b/src/activities/settings/DetectTimezoneActivity.cpp @@ -0,0 +1,315 @@ +#include "DetectTimezoneActivity.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "CrossPointSettings.h" +#include "MappedInputManager.h" +#include "activities/network/WifiSelectionActivity.h" +#include "components/UITheme.h" +#include "fontIds.h" +#include "network/HttpDownloader.h" + +namespace { +bool mapIanaTimezone(const std::string& tz, uint8_t& outSetting) { + using TZ = CrossPointSettings::TIMEZONE; + if (tz == "UTC" || tz == "Etc/UTC") { + outSetting = TZ::TZ_UTC; + return true; + } + if (tz == "Europe/London" || tz == "Europe/Guernsey" || tz == "Europe/Isle_of_Man" || + tz == "Europe/Jersey") { + outSetting = TZ::TZ_UTC; + return true; + } + if (tz == "Europe/Athens" || tz == "Europe/Bucharest" || tz == "Europe/Helsinki" || tz == "Europe/Kiev" || + tz == "Europe/Vilnius" || tz == "Europe/Riga" || tz == "Europe/Tallinn") { + outSetting = TZ::TZ_EET; + return true; + } + if (tz.rfind("Europe/", 0) == 0) { + outSetting = TZ::TZ_CET; + return true; + } + if (tz == "Europe/Moscow") { + outSetting = TZ::TZ_MSK; + return true; + } + if (tz == "America/New_York" || tz == "America/Toronto") { + outSetting = TZ::TZ_EST; + return true; + } + if (tz == "America/Chicago") { + outSetting = TZ::TZ_CST; + return true; + } + if (tz == "America/Denver") { + outSetting = TZ::TZ_MST; + return true; + } + if (tz == "America/Los_Angeles" || tz == "America/Vancouver") { + outSetting = TZ::TZ_PST; + return true; + } + if (tz == "America/Sao_Paulo" || tz == "America/Argentina/Buenos_Aires" || tz == "America/Montevideo") { + outSetting = TZ::TZ_UTC_MINUS3; + return true; + } + if (tz == "Asia/Dubai" || tz == "Asia/Muscat") { + outSetting = TZ::TZ_UTC_PLUS4; + return true; + } + if (tz == "Asia/Kolkata") { + outSetting = TZ::TZ_IST; + return true; + } + if (tz == "Asia/Bangkok" || tz == "Asia/Ho_Chi_Minh" || tz == "Asia/Jakarta" || tz == "Asia/Phnom_Penh" || + tz == "Asia/Vientiane") { + outSetting = TZ::TZ_UTC_PLUS7; + return true; + } + if (tz == "Asia/Shanghai" || tz == "Asia/Hong_Kong" || tz == "Asia/Singapore" || tz == "Asia/Taipei" || + tz == "Asia/Kuala_Lumpur" || tz == "Asia/Manila") { + outSetting = TZ::TZ_UTC_PLUS8; + return true; + } + if (tz == "Asia/Tokyo" || tz == "Asia/Seoul") { + outSetting = TZ::TZ_UTC_PLUS9; + return true; + } + if (tz == "Australia/Sydney" || tz == "Australia/Melbourne" || tz == "Australia/Hobart") { + outSetting = TZ::TZ_AEST; + return true; + } + if (tz == "Pacific/Auckland") { + outSetting = TZ::TZ_NZST; + return true; + } + return false; +} + +bool fetchTimezonePayload(const char* url, std::string& payload) { + // Give DNS a moment after WiFi connect. + delay(500); + for (int attempt = 0; attempt < 2; ++attempt) { + if (HttpDownloader::fetchUrl(url, payload)) { + return true; + } + delay(300); + } + return false; +} + +bool fetchPublicIp(std::string& ip) { + std::string payload; + if (!fetchTimezonePayload("https://api.ipify.org", payload)) { + return false; + } + + // Payload is plain text: IP address + ip = payload; + // Trim any whitespace + while (!ip.empty() && (ip.back() == '\n' || ip.back() == '\r' || ip.back() == ' ' || ip.back() == '\t')) { + ip.pop_back(); + } + if (!ip.empty()) { + LOG_DBG("CLK", "Public IP: %s", ip.c_str()); + return true; + } + return false; +} + +bool detectTimezoneSetting(uint8_t& outSetting, std::string& outIana, bool& outDstKnown, bool& outDstActive) { + std::string payload; + std::string publicIp; + if (fetchPublicIp(publicIp)) { + std::string timeUrl = std::string("https://timeapi.io/api/Time/current/ip?ipAddress=") + publicIp; + LOG_DBG("CLK", "Timezone detect via TimeAPI: %s", timeUrl.c_str()); + if (fetchTimezonePayload(timeUrl.c_str(), payload)) { + // Continue to parse TimeAPI payload below. + } + } + + if (payload.empty() && !fetchTimezonePayload("http://ip-api.com/json/?fields=timezone,dst", payload)) { + LOG_ERR("CLK", "Timezone detect failed: fetch error"); + return false; + } + + JsonDocument doc; + const auto err = deserializeJson(doc, payload); + if (err) { + LOG_ERR("CLK", "Timezone detect failed: %s", err.c_str()); + return false; + } + + const char* tz = doc["timezone"] | ""; + if (!tz || tz[0] == '\0') { + tz = doc["timeZone"] | ""; + } + if (!tz || tz[0] == '\0') { + tz = doc["time_zone"] | ""; + } + if (!tz || tz[0] == '\0') { + LOG_ERR("CLK", "Timezone detect failed: missing timezone (payload: %s)", payload.c_str()); + return false; + } + + outIana = tz; + if (!doc["dst_active"].isNull()) { + outDstKnown = true; + outDstActive = doc["dst_active"].as(); + } else if (!doc["dstActive"].isNull()) { + outDstKnown = true; + outDstActive = doc["dstActive"].as(); + } else if (!doc["dst"].isNull()) { + outDstKnown = true; + outDstActive = doc["dst"].as(); + } else { + outDstKnown = false; + outDstActive = false; + } + + if (!mapIanaTimezone(tz, outSetting)) { + LOG_ERR("CLK", "Timezone detect unsupported: %s", tz); + return false; + } + + if (outDstKnown) { + LOG_DBG("CLK", "Timezone detected: %s (dst=%d)", tz, outDstActive ? 1 : 0); + } else { + LOG_DBG("CLK", "Timezone detected: %s (dst=unknown)", tz); + } + return true; +} + +void wifiOff() { + if (esp_sntp_enabled()) { + esp_sntp_stop(); + } + WiFi.disconnect(false); + delay(100); + WiFi.mode(WIFI_OFF); + delay(100); +} +} // namespace + +void DetectTimezoneActivity::onEnter() { + Activity::onEnter(); + + if (WiFi.status() == WL_CONNECTED) { + onWifiSelectionComplete(true); + return; + } + + startActivityForResult(std::make_unique(renderer, mappedInput), + [this](const ActivityResult& result) { + if (result.isCancelled) { + onWifiSelectionCancelled(); + return; + } + onWifiSelectionComplete(true); + }); +} + +void DetectTimezoneActivity::onExit() { + Activity::onExit(); + wifiOff(); +} + +void DetectTimezoneActivity::onWifiSelectionComplete(bool success) { + if (!success) { + state = FAILED; + requestUpdate(); + return; + } + + { + RenderLock lock(*this); + state = DETECTING; + } + requestUpdateAndWait(); + + performDetect(); +} + +void DetectTimezoneActivity::onWifiSelectionCancelled() { finish(); } + +void DetectTimezoneActivity::performDetect() { + uint8_t detected = SETTINGS.timeZone; + detectedTimezone.clear(); + dstKnown = false; + dstActive = false; + if (detectTimezoneSetting(detected, detectedTimezone, dstKnown, dstActive)) { + SETTINGS.timeZone = detected; + HalClock::applyTimezone(SETTINGS.timeZone); + SETTINGS.saveToFile(); + state = SUCCESS; + } else { + state = FAILED; + } + + wifiOff(); + requestUpdate(); +} + +void DetectTimezoneActivity::render(RenderLock&&) { + const auto& metrics = UITheme::getInstance().getMetrics(); + const auto pageWidth = renderer.getScreenWidth(); + const auto pageHeight = renderer.getScreenHeight(); + + renderer.clearScreen(); + GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_DETECT_TIMEZONE)); + + if (state == DETECTING) { + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, tr(STR_DETECTING_TIMEZONE), true, EpdFontFamily::BOLD); + renderer.displayBuffer(); + return; + } + + if (state == SUCCESS) { + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, tr(STR_TIMEZONE_DETECTED), true, + EpdFontFamily::BOLD); + + if (!detectedTimezone.empty()) { + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 5, detectedTimezone.c_str()); + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 25, dstStatusLabel()); + } + + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); + GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + renderer.displayBuffer(); + return; + } + + if (state == FAILED) { + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, tr(STR_TIMEZONE_DETECT_FAILED), true, + EpdFontFamily::BOLD); + + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); + GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + renderer.displayBuffer(); + return; + } +} + +const char* DetectTimezoneActivity::dstStatusLabel() const { + if (!dstKnown) { + return tr(STR_DST_UNKNOWN); + } + return dstActive ? tr(STR_DST_ACTIVE) : tr(STR_DST_INACTIVE); +} + +void DetectTimezoneActivity::loop() { + if (state == SUCCESS || state == FAILED) { + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { + finish(); + } + } +} diff --git a/src/activities/settings/DetectTimezoneActivity.h b/src/activities/settings/DetectTimezoneActivity.h new file mode 100644 index 00000000..103a0736 --- /dev/null +++ b/src/activities/settings/DetectTimezoneActivity.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "activities/Activity.h" + +class DetectTimezoneActivity final : public Activity { + public: + explicit DetectTimezoneActivity(GfxRenderer& renderer, MappedInputManager& mappedInput) + : Activity("DetectTimezone", renderer, mappedInput) {} + + void onEnter() override; + void onExit() override; + void loop() override; + void render(RenderLock&&) override; + + private: + enum State { CONNECTING, DETECTING, SUCCESS, FAILED }; + State state = CONNECTING; + std::string detectedTimezone; + bool dstKnown = false; + bool dstActive = false; + + void onWifiSelectionComplete(bool success); + void onWifiSelectionCancelled(); + void performDetect(); + const char* dstStatusLabel() const; +}; diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 455d56f5..783c5e50 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -1,12 +1,14 @@ #include "SettingsActivity.h" #include +#include #include #include "ButtonRemapActivity.h" #include "CalibreSettingsActivity.h" #include "ClearCacheActivity.h" #include "CrossPointSettings.h" +#include "DetectTimezoneActivity.h" #include "KOReaderSettingsActivity.h" #include "LanguageSelectActivity.h" #include "MappedInputManager.h" @@ -48,6 +50,7 @@ void SettingsActivity::onEnter() { controlsSettings.insert(controlsSettings.begin(), SettingInfo::Action(StrId::STR_REMAP_FRONT_BUTTONS, SettingAction::RemapFrontButtons)); systemSettings.push_back(SettingInfo::Action(StrId::STR_SYNC_TIME, SettingAction::SyncTime)); + systemSettings.push_back(SettingInfo::Action(StrId::STR_DETECT_TIMEZONE, SettingAction::DetectTimezone)); systemSettings.push_back(SettingInfo::Action(StrId::STR_WIFI_NETWORKS, SettingAction::Network)); systemSettings.push_back(SettingInfo::Action(StrId::STR_KOREADER_SYNC, SettingAction::KOReaderSync)); systemSettings.push_back(SettingInfo::Action(StrId::STR_OPDS_BROWSER, SettingAction::OPDSBrowser)); @@ -197,6 +200,9 @@ void SettingsActivity::toggleCurrentSetting() { case SettingAction::SyncTime: startActivityForResult(std::make_unique(renderer, mappedInput), resultHandler); break; + case SettingAction::DetectTimezone: + startActivityForResult(std::make_unique(renderer, mappedInput), resultHandler); + break; case SettingAction::None: // Do nothing break; @@ -206,6 +212,10 @@ void SettingsActivity::toggleCurrentSetting() { return; } + if (setting.nameId == StrId::STR_TIMEZONE) { + HalClock::applyTimezone(SETTINGS.timeZone); + } + SETTINGS.saveToFile(); } diff --git a/src/activities/settings/SettingsActivity.h b/src/activities/settings/SettingsActivity.h index 06baeae8..28116197 100644 --- a/src/activities/settings/SettingsActivity.h +++ b/src/activities/settings/SettingsActivity.h @@ -21,6 +21,7 @@ enum class SettingAction { ClearCache, CheckForUpdates, Language, + DetectTimezone, SyncTime, }; diff --git a/src/main.cpp b/src/main.cpp index fa02523a..fa24aa8e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -260,6 +260,7 @@ void setup() { HalSystem::clearPanic(); // TODO: move this to an activity when we have one to display the panic info SETTINGS.loadFromFile(); + HalClock::applyTimezone(SETTINGS.timeZone); I18N.loadSettings(); KOREADER_STORE.loadFromFile(); UITheme::getInstance().reload();