Merge branch 'feat-additional-compensation' of https://github.com/jpirnay/crosspoint-reader into mybuild

This commit is contained in:
jpirnay
2026-03-29 18:35:36 +02:00
parent 4740145a88
commit 0bdfc6e816
27 changed files with 1514 additions and 85 deletions
+30
View File
@@ -138,6 +138,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
@@ -204,6 +225,15 @@ class CrossPointSettings {
uint8_t imageRendering = IMAGES_DISPLAY;
// Enable synthetic TOC fallback for malformed/sparse TOC books (1 = enabled, 0 = disabled)
uint8_t syntheticTocFallback = 1;
// Show clock in the reader status bar
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;
// Use clock and keep the LP timer running during deep sleep (GPIO13 HIGH)
// so time can be accurately restored on wake. Increases sleep current by ~3-4 mA.
uint8_t useClock = 0;
~CrossPointSettings() = default;
+11
View File
@@ -84,6 +84,15 @@ inline const std::vector<SettingInfo>& getSettingsList() {
"sleepTimeout", StrId::STR_CAT_SYSTEM),
SettingInfo::Toggle(StrId::STR_SHOW_HIDDEN_FILES, &CrossPointSettings::showHiddenFiles, "showHiddenFiles",
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_USE_CLOCK, &CrossPointSettings::useClock, "useClock", StrId::STR_CAT_SYSTEM),
// --- KOReader Sync (web-only, uses KOReaderCredentialStore) ---
SettingInfo::DynamicString(
@@ -140,6 +149,8 @@ inline const std::vector<SettingInfo>& getSettingsList() {
StrId::STR_CUSTOMISE_STATUS_BAR),
SettingInfo::Toggle(StrId::STR_BATTERY, &CrossPointSettings::statusBarBattery, "statusBarBattery",
StrId::STR_CUSTOMISE_STATUS_BAR),
SettingInfo::Toggle(StrId::STR_CLOCK, &CrossPointSettings::statusBarClock, "statusBarClock",
StrId::STR_CUSTOMISE_STATUS_BAR),
};
return list;
}
+14
View File
@@ -1,5 +1,7 @@
#include "ActivityManager.h"
#include <Arduino.h>
#include <HalClock.h>
#include <HalPowerManager.h>
#include "boot_sleep/BootActivity.h"
@@ -56,6 +58,18 @@ void ActivityManager::loop() {
currentActivity->loop();
}
if (SETTINGS.useClock && HalClock::isSynced()) {
time_t now = HalClock::now();
if (now > 0) {
static time_t lastMinute = -1;
time_t minute = now / 60;
if (minute != lastMinute) {
lastMinute = minute;
requestUpdate();
}
}
}
while (pendingAction != PendingAction::None) {
if (pendingAction == PendingAction::Pop) {
RenderLock lock;
@@ -2,6 +2,7 @@
#include <Epub.h>
#include <GfxRenderer.h>
#include <HalClock.h>
#include <I18n.h>
#include <Logging.h>
#include <OpdsStream.h>
@@ -39,8 +40,7 @@ void OpdsBookBrowserActivity::onEnter() {
void OpdsBookBrowserActivity::onExit() {
Activity::onExit();
// Turn off WiFi when exiting
WiFi.mode(WIFI_OFF);
HalClock::wifiOff();
entries.clear();
navigationHistory.clear();
@@ -3,6 +3,7 @@
#include <DNSServer.h>
#include <ESPmDNS.h>
#include <GfxRenderer.h>
#include <HalClock.h>
#include <I18n.h>
#include <WiFi.h>
#include <esp_task_wdt.h>
@@ -86,15 +87,12 @@ void CrossPointWebServerActivity::onExit() {
if (isApMode) {
LOG_DBG("WEBACT", "Stopping WiFi AP...");
WiFi.softAPdisconnect(true);
delay(30);
WiFi.mode(WIFI_OFF);
delay(30);
} else {
LOG_DBG("WEBACT", "Disconnecting WiFi (graceful)...");
WiFi.disconnect(false); // false = don't erase credentials, send disconnect frame
HalClock::wifiOff();
}
delay(30); // Allow disconnect frame to be sent
LOG_DBG("WEBACT", "Setting WiFi mode OFF...");
WiFi.mode(WIFI_OFF);
delay(30); // Allow WiFi hardware to power down
LOG_DBG("WEBACT", "Free heap at onExit end: %d bytes", ESP.getFreeHeap());
}
+5 -42
View File
@@ -1,10 +1,10 @@
#include "KOReaderSyncActivity.h"
#include <GfxRenderer.h>
#include <HalClock.h>
#include <I18n.h>
#include <Logging.h>
#include <WiFi.h>
#include <esp_sntp.h>
#include "KOReaderCredentialStore.h"
#include "KOReaderDocumentId.h"
@@ -13,43 +13,6 @@
#include "components/UITheme.h"
#include "fontIds.h"
namespace {
void syncTimeWithNTP() {
// Stop SNTP if already running (can't reconfigure while running)
if (esp_sntp_enabled()) {
esp_sntp_stop();
}
// Configure SNTP
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
esp_sntp_setservername(0, "pool.ntp.org");
esp_sntp_init();
// Wait for time to sync (with timeout)
int retry = 0;
const int maxRetries = 50; // 5 seconds max
while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED && retry < maxRetries) {
vTaskDelay(100 / portTICK_PERIOD_MS);
retry++;
}
if (retry < maxRetries) {
LOG_DBG("KOSync", "NTP time synced");
} else {
LOG_DBG("KOSync", "NTP sync timeout, using fallback");
}
}
void wifiOff() {
if (esp_sntp_enabled()) {
esp_sntp_stop();
}
WiFi.disconnect(false);
delay(100);
WiFi.mode(WIFI_OFF);
delay(100);
}
} // namespace
void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) {
if (!success) {
LOG_DBG("KOSync", "WiFi connection failed, exiting");
@@ -70,7 +33,7 @@ void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) {
requestUpdate(true);
// Sync time with NTP before making API requests
syncTimeWithNTP();
HalClock::syncNtp();
{
RenderLock lock(*this);
@@ -187,7 +150,7 @@ void KOReaderSyncActivity::performUpload() {
const auto result = KOReaderSyncClient::updateProgress(progress);
if (result != KOReaderSyncClient::OK) {
wifiOff();
HalClock::wifiOff(true);
{
RenderLock lock(*this);
state = SYNC_FAILED;
@@ -197,7 +160,7 @@ void KOReaderSyncActivity::performUpload() {
return;
}
wifiOff();
HalClock::wifiOff(true);
{
RenderLock lock(*this);
state = UPLOAD_COMPLETE;
@@ -232,7 +195,7 @@ void KOReaderSyncActivity::onEnter() {
void KOReaderSyncActivity::onExit() {
Activity::onExit();
wifiOff();
HalClock::wifiOff(true);
}
void KOReaderSyncActivity::closeCancelled() {
@@ -0,0 +1,129 @@
#include "ClockSettingsActivity.h"
#include <GfxRenderer.h>
#include <HalClock.h>
#include <I18n.h>
#include "CrossPointSettings.h"
#include "DetectTimezoneActivity.h"
#include "MappedInputManager.h"
#include "SyncTimeActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
namespace {
constexpr int MENU_ITEMS = 5;
const StrId menuNames[MENU_ITEMS] = {StrId::STR_USE_CLOCK, StrId::STR_CLOCK_FORMAT, StrId::STR_TIMEZONE,
StrId::STR_SYNC_TIME, StrId::STR_DETECT_TIMEZONE};
const StrId timeZoneNames[CrossPointSettings::TIMEZONE_COUNT] = {
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};
} // namespace
void ClockSettingsActivity::onEnter() {
Activity::onEnter();
selectedIndex = 0;
requestUpdate();
}
void ClockSettingsActivity::onExit() { Activity::onExit(); }
void ClockSettingsActivity::loop() {
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
finish();
return;
}
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
handleSelection();
requestUpdate();
return;
}
buttonNavigator.onNextRelease([this] {
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, MENU_ITEMS);
requestUpdate();
});
buttonNavigator.onPreviousRelease([this] {
selectedIndex = ButtonNavigator::previousIndex(selectedIndex, MENU_ITEMS);
requestUpdate();
});
buttonNavigator.onNextContinuous([this] {
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, MENU_ITEMS);
requestUpdate();
});
buttonNavigator.onPreviousContinuous([this] {
selectedIndex = ButtonNavigator::previousIndex(selectedIndex, MENU_ITEMS);
requestUpdate();
});
}
void ClockSettingsActivity::handleSelection() {
if (selectedIndex == 0) {
SETTINGS.useClock = (SETTINGS.useClock + 1) % 2;
if (!SETTINGS.useClock) {
SETTINGS.statusBarClock = 0;
}
SETTINGS.saveToFile();
} else if (selectedIndex == 1) {
SETTINGS.clockFormat12h = (SETTINGS.clockFormat12h + 1) % 2;
SETTINGS.saveToFile();
} else if (selectedIndex == 2) {
SETTINGS.timeZone = (SETTINGS.timeZone + 1) % CrossPointSettings::TIMEZONE_COUNT;
HalClock::applyTimezone(SETTINGS.timeZone);
SETTINGS.saveToFile();
} else if (selectedIndex == 3) {
auto resultHandler = [](const ActivityResult&) { SETTINGS.saveToFile(); };
startActivityForResult(std::make_unique<SyncTimeActivity>(renderer, mappedInput), resultHandler);
} else if (selectedIndex == 4) {
auto resultHandler = [](const ActivityResult&) { SETTINGS.saveToFile(); };
startActivityForResult(std::make_unique<DetectTimezoneActivity>(renderer, mappedInput), resultHandler);
}
}
void ClockSettingsActivity::render(RenderLock&&) {
renderer.clearScreen();
const auto& metrics = UITheme::getInstance().getMetrics();
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_CLOCK_SETTINGS));
GUI.drawSubHeader(renderer, Rect{0, metrics.topPadding + metrics.headerHeight, pageWidth, metrics.tabBarHeight},
tr(STR_CLOCK_SETTINGS_WARNING));
const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.verticalSpacing;
const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing * 2;
GUI.drawList(
renderer, Rect{0, contentTop, pageWidth, contentHeight}, MENU_ITEMS, selectedIndex,
[](int index) { return std::string(I18N.get(menuNames[index])); }, nullptr, nullptr,
[](int index) {
if (index == 0) {
return std::string(SETTINGS.useClock ? tr(STR_STATE_ON) : tr(STR_STATE_OFF));
}
if (index == 1) {
return std::string(SETTINGS.clockFormat12h ? tr(STR_12H) : tr(STR_24H));
}
if (index == 2) {
const auto tzIndex = static_cast<size_t>(SETTINGS.timeZone);
if (tzIndex < (sizeof(timeZoneNames) / sizeof(timeZoneNames[0]))) {
return std::string(I18N.get(timeZoneNames[tzIndex]));
}
return std::string(tr(STR_TZ_UTC));
}
return std::string("");
},
true);
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();
}
@@ -0,0 +1,20 @@
#pragma once
#include <I18n.h>
#include "activities/Activity.h"
#include "util/ButtonNavigator.h"
class ClockSettingsActivity final : public Activity {
ButtonNavigator buttonNavigator;
int selectedIndex = 0;
void handleSelection();
public:
explicit ClockSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
: Activity("ClockSettings", renderer, mappedInput) {}
void onEnter() override;
void onExit() override;
void loop() override;
void render(RenderLock&&) override;
};
@@ -0,0 +1,308 @@
#include "DetectTimezoneActivity.h"
#include <ArduinoJson.h>
#include <GfxRenderer.h>
#include <HalClock.h>
#include <I18n.h>
#include <Logging.h>
#include <WiFi.h>
#include <string>
#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 == "Europe/Moscow") {
outSetting = TZ::TZ_MSK;
return true;
}
if (tz.rfind("Europe/", 0) == 0) {
outSetting = TZ::TZ_CET;
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("https://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<bool>();
} else if (!doc["dstActive"].isNull()) {
outDstKnown = true;
outDstActive = doc["dstActive"].as<bool>();
} else if (!doc["dst"].isNull()) {
outDstKnown = true;
outDstActive = doc["dst"].as<bool>();
} 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;
}
} // namespace
void DetectTimezoneActivity::onEnter() {
Activity::onEnter();
if (WiFi.status() == WL_CONNECTED) {
onWifiSelectionComplete(true);
return;
}
startActivityForResult(std::make_unique<WifiSelectionActivity>(renderer, mappedInput),
[this](const ActivityResult& result) {
if (result.isCancelled) {
onWifiSelectionCancelled();
return;
}
onWifiSelectionComplete(true);
});
}
void DetectTimezoneActivity::onExit() {
Activity::onExit();
HalClock::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;
}
HalClock::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 == CONNECTING) {
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, tr(STR_CONNECTING), true, EpdFontFamily::BOLD);
renderer.displayBuffer();
return;
}
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();
}
}
}
@@ -0,0 +1,28 @@
#pragma once
#include <string>
#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;
};
@@ -1,6 +1,7 @@
#include "KOReaderAuthActivity.h"
#include <GfxRenderer.h>
#include <HalClock.h>
#include <I18n.h>
#include <WiFi.h>
@@ -93,11 +94,7 @@ void KOReaderAuthActivity::onEnter() {
void KOReaderAuthActivity::onExit() {
Activity::onExit();
// Turn off wifi
WiFi.disconnect(false);
delay(100);
WiFi.mode(WIFI_OFF);
delay(100);
HalClock::wifiOff();
}
void KOReaderAuthActivity::render(RenderLock&&) {
@@ -1,6 +1,7 @@
#include "OtaUpdateActivity.h"
#include <GfxRenderer.h>
#include <HalClock.h>
#include <I18n.h>
#include <WiFi.h>
@@ -66,11 +67,7 @@ void OtaUpdateActivity::onEnter() {
void OtaUpdateActivity::onExit() {
Activity::onExit();
// Turn off wifi
WiFi.disconnect(false); // false = don't erase credentials, send disconnect frame
delay(100); // Allow disconnect frame to be sent
WiFi.mode(WIFI_OFF);
delay(100); // Allow WiFi hardware to fully power down
HalClock::wifiOff();
}
void OtaUpdateActivity::render(RenderLock&&) {
@@ -1,18 +1,22 @@
#include "SettingsActivity.h"
#include <GfxRenderer.h>
#include <HalClock.h>
#include <Logging.h>
#include "ButtonRemapActivity.h"
#include "CalibreSettingsActivity.h"
#include "ClearCacheActivity.h"
#include "ClockSettingsActivity.h"
#include "CrossPointSettings.h"
#include "DetectTimezoneActivity.h"
#include "KOReaderSettingsActivity.h"
#include "LanguageSelectActivity.h"
#include "MappedInputManager.h"
#include "OtaUpdateActivity.h"
#include "SettingsList.h"
#include "StatusBarSettingsActivity.h"
#include "SyncTimeActivity.h"
#include "SystemInformationActivity.h"
#include "activities/network/WifiSelectionActivity.h"
#include "components/UITheme.h"
@@ -32,6 +36,11 @@ void SettingsActivity::onEnter() {
for (const auto& setting : getSettingsList()) {
if (setting.category == StrId::STR_NONE_OPT) continue;
if (setting.category == StrId::STR_CAT_SYSTEM &&
(setting.nameId == StrId::STR_USE_CLOCK || setting.nameId == StrId::STR_CLOCK_FORMAT ||
setting.nameId == StrId::STR_TIMEZONE)) {
continue;
}
if (setting.category == StrId::STR_CAT_DISPLAY) {
displaySettings.push_back(setting);
} else if (setting.category == StrId::STR_CAT_READER) {
@@ -47,6 +56,7 @@ void SettingsActivity::onEnter() {
// Append device-only ACTION items
controlsSettings.insert(controlsSettings.begin(),
SettingInfo::Action(StrId::STR_REMAP_FRONT_BUTTONS, SettingAction::RemapFrontButtons));
systemSettings.push_back(SettingInfo::Action(StrId::STR_CLOCK_SETTINGS, SettingAction::ClockSettings));
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));
@@ -176,6 +186,9 @@ void SettingsActivity::toggleCurrentSetting() {
case SettingAction::CustomiseStatusBar:
startActivityForResult(std::make_unique<StatusBarSettingsActivity>(renderer, mappedInput), resultHandler);
break;
case SettingAction::ClockSettings:
startActivityForResult(std::make_unique<ClockSettingsActivity>(renderer, mappedInput), resultHandler);
break;
case SettingAction::KOReaderSync:
startActivityForResult(std::make_unique<KOReaderSettingsActivity>(renderer, mappedInput), resultHandler);
break;
@@ -197,6 +210,12 @@ void SettingsActivity::toggleCurrentSetting() {
case SettingAction::SystemInfo:
startActivityForResult(std::make_unique<SystemInformationActivity>(renderer, mappedInput), resultHandler);
break;
case SettingAction::SyncTime:
startActivityForResult(std::make_unique<SyncTimeActivity>(renderer, mappedInput), resultHandler);
break;
case SettingAction::DetectTimezone:
startActivityForResult(std::make_unique<DetectTimezoneActivity>(renderer, mappedInput), resultHandler);
break;
case SettingAction::None:
// Do nothing
break;
@@ -15,6 +15,7 @@ enum class SettingAction {
None,
RemapFrontButtons,
CustomiseStatusBar,
ClockSettings,
KOReaderSync,
OPDSBrowser,
Network,
@@ -22,6 +23,8 @@ enum class SettingAction {
CheckForUpdates,
Language,
SystemInfo,
DetectTimezone,
SyncTime,
};
struct SettingInfo {
@@ -11,13 +11,14 @@
#include "fontIds.h"
namespace {
constexpr int MENU_ITEMS = 6;
constexpr int MENU_ITEMS = 7;
const StrId menuNames[MENU_ITEMS] = {StrId::STR_CHAPTER_PAGE_COUNT,
StrId::STR_BOOK_PROGRESS_PERCENTAGE,
StrId::STR_PROGRESS_BAR,
StrId::STR_PROGRESS_BAR_THICKNESS,
StrId::STR_TITLE,
StrId::STR_BATTERY};
StrId::STR_BATTERY,
StrId::STR_CLOCK};
constexpr int PROGRESS_BAR_ITEMS = 3;
const StrId progressBarNames[PROGRESS_BAR_ITEMS] = {StrId::STR_BOOK, StrId::STR_CHAPTER, StrId::STR_HIDE};
@@ -36,7 +37,10 @@ const int verticalPreviewTextPadding = 40;
void StatusBarSettingsActivity::onEnter() {
Activity::onEnter();
selectedIndex = 0;
const int menuCount = SETTINGS.useClock ? MENU_ITEMS : MENU_ITEMS - 1;
if (selectedIndex >= menuCount) {
selectedIndex = 0;
}
// Clamp statusBarProgressBar and statusBarTitle in case of corrupt/migrated data
if (SETTINGS.statusBarProgressBar >= PROGRESS_BAR_ITEMS) {
@@ -70,22 +74,26 @@ void StatusBarSettingsActivity::loop() {
// Handle navigation
buttonNavigator.onNextRelease([this] {
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, MENU_ITEMS);
const int menuCount = SETTINGS.useClock ? MENU_ITEMS : MENU_ITEMS - 1;
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, menuCount);
requestUpdate();
});
buttonNavigator.onPreviousRelease([this] {
selectedIndex = ButtonNavigator::previousIndex(selectedIndex, MENU_ITEMS);
const int menuCount = SETTINGS.useClock ? MENU_ITEMS : MENU_ITEMS - 1;
selectedIndex = ButtonNavigator::previousIndex(selectedIndex, menuCount);
requestUpdate();
});
buttonNavigator.onNextContinuous([this] {
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, MENU_ITEMS);
const int menuCount = SETTINGS.useClock ? MENU_ITEMS : MENU_ITEMS - 1;
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, menuCount);
requestUpdate();
});
buttonNavigator.onPreviousContinuous([this] {
selectedIndex = ButtonNavigator::previousIndex(selectedIndex, MENU_ITEMS);
const int menuCount = SETTINGS.useClock ? MENU_ITEMS : MENU_ITEMS - 1;
selectedIndex = ButtonNavigator::previousIndex(selectedIndex, menuCount);
requestUpdate();
});
}
@@ -110,6 +118,9 @@ void StatusBarSettingsActivity::handleSelection() {
} else if (selectedIndex == 5) {
// Show Battery
SETTINGS.statusBarBattery = (SETTINGS.statusBarBattery + 1) % 2;
} else if (selectedIndex == 6 && SETTINGS.useClock) {
// Show Clock
SETTINGS.statusBarClock = (SETTINGS.statusBarClock + 1) % 2;
}
SETTINGS.saveToFile();
}
@@ -124,9 +135,9 @@ void StatusBarSettingsActivity::render(RenderLock&&) {
tr(STR_CUSTOMISE_STATUS_BAR));
const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing;
const int contentHeight = contentRect.height - contentTop - metrics.verticalSpacing * 2;
const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing * 2;
GUI.drawList(
renderer, Rect{contentRect.x, contentTop, contentRect.width, contentHeight}, static_cast<int>(MENU_ITEMS),
renderer, Rect{0, contentTop, pageWidth, contentHeight}, static_cast<int>(MENU_ITEMS),
static_cast<int>(selectedIndex), [](int index) { return std::string(I18N.get(menuNames[index])); }, nullptr,
nullptr,
[this](int index) {
@@ -143,6 +154,8 @@ void StatusBarSettingsActivity::render(RenderLock&&) {
return I18N.get(titleNames[SETTINGS.statusBarTitle]);
} else if (index == 5) {
return SETTINGS.statusBarBattery ? tr(STR_SHOW) : tr(STR_HIDE);
} else if (index == 6) {
return SETTINGS.statusBarClock ? tr(STR_SHOW) : tr(STR_HIDE);
} else {
return tr(STR_HIDE);
}
@@ -0,0 +1,197 @@
#include "SyncTimeActivity.h"
#include <GfxRenderer.h>
#include <HalClock.h>
#include <I18n.h>
#include <Logging.h>
#include <WiFi.h>
#include <cstdlib>
#include "CrossPointSettings.h"
#include "MappedInputManager.h"
#include "activities/network/WifiSelectionActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
static void formatDuration(char* buf, size_t bufSize, int32_t totalSeconds) {
const char* sign = totalSeconds < 0 ? "-" : "+";
int32_t abs = totalSeconds < 0 ? -totalSeconds : totalSeconds;
int32_t days = abs / 86400;
int32_t hours = (abs % 86400) / 3600;
int32_t mins = (abs % 3600) / 60;
int32_t secs = abs % 60;
if (days > 0) {
snprintf(buf, bufSize, "%s%ldd %ldh %ldm", sign, (long)days, (long)hours, (long)mins);
} else if (hours > 0) {
snprintf(buf, bufSize, "%s%ldh %ldm %lds", sign, (long)hours, (long)mins, (long)secs);
} else if (mins > 0) {
snprintf(buf, bufSize, "%s%ldm %lds", sign, (long)mins, (long)secs);
} else {
snprintf(buf, bufSize, "%s%lds", sign, (long)secs);
}
}
static void formatElapsed(char* buf, size_t bufSize, int32_t totalSeconds) {
int32_t days = totalSeconds / 86400;
int32_t hours = (totalSeconds % 86400) / 3600;
int32_t mins = (totalSeconds % 3600) / 60;
if (days > 0) {
snprintf(buf, bufSize, "%ldd %ldh ago", (long)days, (long)hours);
} else if (hours > 0) {
snprintf(buf, bufSize, "%ldh %ldm ago", (long)hours, (long)mins);
} else {
snprintf(buf, bufSize, "%ldm ago", (long)mins);
}
}
void SyncTimeActivity::onEnter() {
Activity::onEnter();
if (WiFi.status() == WL_CONNECTED) {
onWifiSelectionComplete(true);
return;
}
startActivityForResult(std::make_unique<WifiSelectionActivity>(renderer, mappedInput),
[this](const ActivityResult& result) {
if (result.isCancelled) {
onWifiSelectionCancelled();
return;
}
onWifiSelectionComplete(true);
});
}
void SyncTimeActivity::onExit() {
Activity::onExit();
HalClock::wifiOff(true);
}
void SyncTimeActivity::onWifiSelectionComplete(bool success) {
if (!success) {
state = FAILED;
requestUpdate();
return;
}
{
RenderLock lock(*this);
state = SYNCING;
}
requestUpdateAndWait();
performSync();
}
void SyncTimeActivity::onWifiSelectionCancelled() { finish(); }
void SyncTimeActivity::performSync() {
hadTimeBeforeSync = HalClock::isSynced();
preSyncTime = hadTimeBeforeSync ? time(nullptr) : 0;
prevSyncTime = HalClock::lastSyncTime();
bool ok = HalClock::syncNtp();
if (ok && hadTimeBeforeSync) {
driftSeconds = (int32_t)(time(nullptr) - preSyncTime);
}
HalClock::wifiOff(true);
state = ok ? SUCCESS : FAILED;
requestUpdate();
}
void SyncTimeActivity::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_SYNC_TIME));
if (state == SYNCING) {
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, tr(STR_SYNCING_CLOCK), true, EpdFontFamily::BOLD);
renderer.displayBuffer();
return;
}
if (state == SUCCESS) {
int y = pageHeight / 2 - 40;
renderer.drawCenteredText(UI_10_FONT_ID, y, tr(STR_TIME_SYNCED), true, EpdFontFamily::BOLD);
time_t now = HalClock::now();
struct tm timeinfo;
localtime_r(&now, &timeinfo);
char timePart[16];
HalClock::formatTime(timePart, sizeof(timePart), !SETTINGS.clockFormat12h);
char timeStr[32];
snprintf(timeStr, sizeof(timeStr), "%s %04d-%02d-%02d", timePart, timeinfo.tm_year + 1900, timeinfo.tm_mon + 1,
timeinfo.tm_mday);
y += 30;
renderer.drawCenteredText(UI_10_FONT_ID, y, timeStr);
int32_t elapsedSinceSync = -1;
if (prevSyncTime > 0) {
elapsedSinceSync = (int32_t)(now - prevSyncTime);
}
char driftStr[80];
if (hadTimeBeforeSync) {
char driftFmt[24];
formatDuration(driftFmt, sizeof(driftFmt), driftSeconds);
if (elapsedSinceSync > 0) {
double hours = (double)elapsedSinceSync / 3600.0;
double rate = (double)driftSeconds / hours;
char rateFmt[16];
snprintf(rateFmt, sizeof(rateFmt), "%+.2f", rate);
char driftWithRate[48];
snprintf(driftWithRate, sizeof(driftWithRate), "%s (%s s/hr)", driftFmt, rateFmt);
snprintf(driftStr, sizeof(driftStr), tr(STR_CLOCK_DRIFT), driftWithRate);
} else {
snprintf(driftStr, sizeof(driftStr), tr(STR_CLOCK_DRIFT), driftFmt);
}
} else {
snprintf(driftStr, sizeof(driftStr), tr(STR_CLOCK_DRIFT), "N/A");
}
y += 30;
renderer.drawCenteredText(UI_10_FONT_ID, y, driftStr);
if (elapsedSinceSync > 0) {
char elapsedFmt[24];
formatElapsed(elapsedFmt, sizeof(elapsedFmt), elapsedSinceSync);
char lastSyncStr[48];
snprintf(lastSyncStr, sizeof(lastSyncStr), tr(STR_LAST_NTP_SYNC), elapsedFmt);
y += 25;
renderer.drawCenteredText(UI_10_FONT_ID, y, lastSyncStr);
}
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_TIME_SYNC_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;
}
}
void SyncTimeActivity::loop() {
if (state == SUCCESS || state == FAILED) {
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
finish();
}
}
}
@@ -0,0 +1,25 @@
#pragma once
#include "activities/Activity.h"
class SyncTimeActivity final : public Activity {
public:
explicit SyncTimeActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
: Activity("SyncTime", renderer, mappedInput) {}
void onEnter() override;
void onExit() override;
void loop() override;
void render(RenderLock&&) override;
private:
enum State { CONNECTING, SYNCING, SUCCESS, FAILED };
State state = CONNECTING;
time_t preSyncTime = 0;
time_t prevSyncTime = 0;
int32_t driftSeconds = 0;
bool hadTimeBeforeSync = false;
void onWifiSelectionComplete(bool success);
void onWifiSelectionCancelled();
void performSync();
};
+1 -1
View File
@@ -127,7 +127,7 @@ int UITheme::getStatusBarHeight() {
// Add status bar margin
const bool showStatusBar = SETTINGS.statusBarChapterPageCount || SETTINGS.statusBarBookProgressPercentage ||
SETTINGS.statusBarTitle != CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE ||
SETTINGS.statusBarBattery;
SETTINGS.statusBarBattery || (SETTINGS.useClock && SETTINGS.statusBarClock);
const bool showProgressBar =
SETTINGS.statusBarProgressBar != CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS;
return (showStatusBar ? (metrics.statusBarVerticalMargin) : 0) +
+21 -1
View File
@@ -1,6 +1,7 @@
#include "BaseTheme.h"
#include <GfxRenderer.h>
#include <HalClock.h>
#include <HalPowerManager.h>
#include <HalStorage.h>
#include <Logging.h>
@@ -306,6 +307,13 @@ void BaseTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* t
Rect{batteryX, rect.y + 5, BaseMetrics::values.batteryWidth, BaseMetrics::values.batteryHeight},
showBatteryPercentage);
// Draw clock in header
if (SETTINGS.useClock) {
char clockStr[16];
HalClock::formatTime(clockStr, sizeof(clockStr), !SETTINGS.clockFormat12h);
renderer.drawText(SMALL_FONT_ID, rect.x + BaseMetrics::values.contentSidePadding, rect.y + 5, clockStr);
}
if (title) {
int padding = rect.width - batteryX + BaseMetrics::values.batteryWidth;
auto truncatedTitle = renderer.truncatedText(UI_12_FONT_ID, title,
@@ -745,6 +753,17 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c
showBatteryPercentage);
}
// Draw Clock
int clockTextWidth = 0;
if (SETTINGS.useClock && SETTINGS.statusBarClock) {
char clockStr[16];
HalClock::formatTime(clockStr, sizeof(clockStr), !SETTINGS.clockFormat12h);
clockTextWidth = renderer.getTextWidth(SMALL_FONT_ID, clockStr);
const int batterySize = SETTINGS.statusBarBattery ? (showBatteryPercentage ? 50 : 20) : 0;
renderer.drawText(SMALL_FONT_ID, metrics.statusBarHorizontalMargin + orientedMarginLeft + batterySize + 8, textY,
clockStr);
}
// Draw Title
if (!title.empty()) {
textY -= textYOffset;
@@ -754,7 +773,8 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c
renderer.getScreenWidth() - (metrics.statusBarHorizontalMargin * 2) - orientedMarginLeft - orientedMarginRight;
const int batterySize = SETTINGS.statusBarBattery ? (showBatteryPercentage ? 50 : 20) : 0;
const int titleMarginLeft = batterySize + 30;
const int clockSize = clockTextWidth > 0 ? clockTextWidth + 8 : 0;
const int titleMarginLeft = batterySize + clockSize + 30;
const int titleMarginRight = progressTextWidth + 30;
// Attempt to center title on the screen, but if title is too wide then later we will center it within the
+8
View File
@@ -1,6 +1,7 @@
#include "LyraTheme.h"
#include <GfxRenderer.h>
#include <HalClock.h>
#include <HalGPIO.h>
#include <HalPowerManager.h>
#include <HalStorage.h>
@@ -150,6 +151,13 @@ void LyraTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* t
Rect{batteryX, rect.y + 5, LyraMetrics::values.batteryWidth, LyraMetrics::values.batteryHeight},
showBatteryPercentage);
// Draw clock in header
if (SETTINGS.useClock) {
char clockStr[16];
HalClock::formatTime(clockStr, sizeof(clockStr), !SETTINGS.clockFormat12h);
renderer.drawText(SMALL_FONT_ID, rect.x + LyraMetrics::values.contentSidePadding, rect.y + 5, clockStr);
}
int maxTitleWidth =
rect.width - LyraMetrics::values.contentSidePadding * 2 - (subtitle != nullptr ? maxSubtitleWidth : 0);
+6 -1
View File
@@ -3,6 +3,7 @@
#include <FontCacheManager.h>
#include <FontDecompressor.h>
#include <GfxRenderer.h>
#include <HalClock.h>
#include <HalDisplay.h>
#include <HalGPIO.h>
#include <HalPowerManager.h>
@@ -184,6 +185,7 @@ void waitForPowerRelease() {
void enterDeepSleep() {
HalPowerManager::Lock powerLock; // Ensure we are at normal CPU frequency for sleep preparation
APP_STATE.lastSleepFromReader = activityManager.isReaderActivity();
HalClock::saveBeforeSleep(SETTINGS.useClock);
APP_STATE.saveToFile();
activityManager.goToSleep();
@@ -192,7 +194,7 @@ void enterDeepSleep() {
LOG_DBG("MAIN", "Power button press calibration value: %lu ms", t2 - t1);
LOG_DBG("MAIN", "Entering deep sleep");
powerManager.startDeepSleep(gpio);
powerManager.startDeepSleep(gpio, SETTINGS.useClock);
}
void setupDisplayAndFonts() {
@@ -258,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();
@@ -289,6 +292,7 @@ void setup() {
activityManager.goToBoot();
APP_STATE.loadFromFile();
HalClock::restore();
RECENT_BOOKS.loadFromFile();
// Boot to home screen if no book is open, last sleep was not from reader, back button is held, or reader activity
@@ -315,6 +319,7 @@ void loop() {
static unsigned long lastMemPrint = 0;
gpio.update();
HalClock::updatePeriodic();
renderer.setFadingFix(SETTINGS.fadingFix);