diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 38192cc9..8c4ba26d 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -341,3 +341,81 @@ STR_LINK: "[link]" STR_SCREENSHOT_BUTTON: "Take screenshot" STR_AUTO_TURN_ENABLED: "Auto Turn Enabled: " STR_AUTO_TURN_PAGES_PER_MIN: "Auto Turn (Pages Per Minute)" + +STR_CAT_CLOCK: "Clock" +STR_CLOCK: "Clock" +STR_CLOCK_AMPM: "AM/PM" +STR_CLOCK_24H: "24 Hour" +STR_SET_TIME: "Set Time" +STR_CLOCK_SIZE: "Clock Size" +STR_CLOCK_SIZE_SMALL: "Small" +STR_CLOCK_SIZE_MEDIUM: "Medium" +STR_CLOCK_SIZE_LARGE: "Large" +STR_TIMEZONE: "Timezone" +STR_TZ_UTC: "UTC" +STR_TZ_EASTERN: "Eastern" +STR_TZ_CENTRAL: "Central" +STR_TZ_MOUNTAIN: "Mountain" +STR_TZ_PACIFIC: "Pacific" +STR_TZ_ALASKA: "Alaska" +STR_TZ_HAWAII: "Hawaii" +STR_TZ_CUSTOM: "Custom" +STR_SET_UTC_OFFSET: "Set UTC Offset" +STR_SYNC_CLOCK: "Sync Clock" +STR_TIME_SYNCED: "Time synced!" +STR_AUTO_NTP_SYNC: "Auto Sync on Boot" +STR_LETTERBOX_FILL: "Letterbox Fill" +STR_DITHERED: "Dithered" +STR_SOLID: "Solid" +STR_ADD_BOOKMARK: "Add Bookmark" +STR_REMOVE_BOOKMARK: "Remove Bookmark" +STR_LOOKUP_WORD: "Lookup Word" +STR_LOOKUP_HISTORY: "Lookup Word History" +STR_GO_TO_BOOKMARK: "Go to Bookmark" +STR_CLOSE_BOOK: "Close Book" +STR_DELETE_DICT_CACHE: "Delete Dictionary Cache" +STR_DEFAULT_OPTION: "Default" +STR_BOOKMARK_ADDED: "Bookmark added" +STR_BOOKMARK_REMOVED: "Bookmark removed" +STR_DICT_CACHE_DELETED: "Dictionary cache deleted" +STR_NO_CACHE_TO_DELETE: "No cache to delete" +STR_TABLE_OF_CONTENTS: "Table of Contents" +STR_TOGGLE_ORIENTATION: "Toggle Portrait/Landscape" +STR_TOGGLE_FONT_SIZE: "Toggle Font Size" +STR_OVERRIDE_LETTERBOX_FILL: "Override Letterbox Fill" +STR_PREFERRED_PORTRAIT: "Preferred Portrait" +STR_PREFERRED_LANDSCAPE: "Preferred Landscape" +STR_CHOOSE_SOMETHING: "Choose something to read" +STR_INDEXING_DISPLAY: "Indexing Display" +STR_INDEXING_POPUP: "Popup" +STR_INDEXING_STATUS_TEXT: "Status Bar Text" +STR_INDEXING_STATUS_ICON: "Status Bar Icon" +STR_MANAGE_BOOK: "Manage Book" +STR_ARCHIVE_BOOK: "Archive Book" +STR_UNARCHIVE_BOOK: "Unarchive Book" +STR_DELETE_BOOK: "Delete Book" +STR_DELETE_CACHE_ONLY: "Delete Cache Only" +STR_REINDEX_BOOK: "Reindex Book" +STR_BROWSE_ARCHIVE: "Browse Archive" +STR_BOOK_ARCHIVED: "Book archived" +STR_BOOK_UNARCHIVED: "Book unarchived" +STR_BOOK_DELETED: "Book deleted" +STR_CACHE_DELETED: "Cache deleted" +STR_BOOK_REINDEXED: "Book reindexed" +STR_ACTION_FAILED: "Action failed" +STR_BACK_TO_BEGINNING: "Back to Beginning" +STR_CLOSE_MENU: "Close Menu" +STR_ADD_SERVER: "Add Server" +STR_SERVER_NAME: "Server Name" +STR_NO_SERVERS: "No OPDS servers configured" +STR_DELETE_SERVER: "Delete Server" +STR_DELETE_CONFIRM: "Delete this server?" +STR_OPDS_SERVERS: "OPDS Servers" +STR_SAVE_HERE: "Save Here" +STR_SELECT_FOLDER: "Select Folder" +STR_DOWNLOAD_PATH: "Download Path" +STR_POSITION: "Position" +STR_DOWNLOAD_COMPLETE: "Download Complete!" +STR_OPEN_BOOK: "Open Book" +STR_BACK_TO_LISTING: "Back to Listing" +STR_AFTER_DOWNLOAD: "After Download" diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index aca87bdc..b41682fa 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -334,3 +334,29 @@ int CrossPointSettings::getReaderFontId() const { } } } + +const char* CrossPointSettings::getTimezonePosixStr() const { + switch (timezone) { + case TZ_EASTERN: + return "EST5EDT,M3.2.0,M11.1.0"; + case TZ_CENTRAL: + return "CST6CDT,M3.2.0,M11.1.0"; + case TZ_MOUNTAIN: + return "MST7MDT,M3.2.0,M11.1.0"; + case TZ_PACIFIC: + return "PST8PDT,M3.2.0,M11.1.0"; + case TZ_ALASKA: + return "AKST9AKDT,M3.2.0,M11.1.0"; + case TZ_HAWAII: + return "HST10"; + case TZ_CUSTOM: { + static char buf[16]; + int posixOffset = -timezoneOffsetHours; + snprintf(buf, sizeof(buf), "UTC%d", posixOffset); + return buf; + } + case TZ_UTC: + default: + return "UTC0"; + } +} diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 9a0e298b..f5a1a53b 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -137,6 +137,41 @@ class CrossPointSettings { // Image rendering in EPUB reader enum IMAGE_RENDERING { IMAGES_DISPLAY = 0, IMAGES_PLACEHOLDER = 1, IMAGES_SUPPRESS = 2, IMAGE_RENDERING_COUNT }; + // Sleep screen letterbox fill mode + enum SLEEP_SCREEN_LETTERBOX_FILL { + LETTERBOX_DITHERED = 0, + LETTERBOX_SOLID = 1, + LETTERBOX_NONE = 2, + SLEEP_SCREEN_LETTERBOX_FILL_COUNT + }; + + // Home screen clock format + enum CLOCK_FORMAT { CLOCK_OFF = 0, CLOCK_AMPM = 1, CLOCK_24H = 2, CLOCK_FORMAT_COUNT }; + + // Clock size + enum CLOCK_SIZE { CLOCK_SIZE_SMALL = 0, CLOCK_SIZE_MEDIUM = 1, CLOCK_SIZE_LARGE = 2, CLOCK_SIZE_COUNT }; + + // Timezone presets + enum TIMEZONE { + TZ_UTC = 0, + TZ_EASTERN = 1, + TZ_CENTRAL = 2, + TZ_MOUNTAIN = 3, + TZ_PACIFIC = 4, + TZ_ALASKA = 5, + TZ_HAWAII = 6, + TZ_CUSTOM = 7, + TZ_COUNT + }; + + // Indexing feedback display mode + enum INDEXING_DISPLAY { + INDEXING_POPUP = 0, + INDEXING_STATUS_TEXT = 1, + INDEXING_STATUS_ICON = 2, + INDEXING_DISPLAY_COUNT + }; + // Sleep screen settings uint8_t sleepScreen = DARK; // Sleep screen cover mode settings @@ -200,6 +235,26 @@ class CrossPointSettings { // Image rendering mode in EPUB reader uint8_t imageRendering = IMAGES_DISPLAY; + // --- Mod-specific settings --- + + // Sleep screen letterbox fill mode (Dithered / Solid / None) + uint8_t sleepScreenLetterboxFill = LETTERBOX_DITHERED; + // Indexing feedback display mode + uint8_t indexingDisplay = INDEXING_POPUP; + // Preferred orientations for the portrait/landscape toggle + uint8_t preferredPortrait = PORTRAIT; + uint8_t preferredLandscape = LANDSCAPE_CW; + // Clock display format (OFF by default) + uint8_t clockFormat = CLOCK_OFF; + // Clock display size + uint8_t clockSize = CLOCK_SIZE_SMALL; + // Timezone setting + uint8_t timezone = TZ_UTC; + // Custom timezone offset in hours from UTC (-12 to +14) + int8_t timezoneOffsetHours = 0; + // Automatically sync time via NTP on boot + uint8_t autoNtpSync = 0; + ~CrossPointSettings() = default; // Get singleton instance @@ -225,6 +280,7 @@ class CrossPointSettings { float getReaderLineCompression() const; unsigned long getSleepTimeoutMs() const; int getRefreshFrequency() const; + const char* getTimezonePosixStr() const; }; // Helper macro to access settings diff --git a/src/JsonSettingsIO.cpp b/src/JsonSettingsIO.cpp index 4a563142..7ff70a40 100644 --- a/src/JsonSettingsIO.cpp +++ b/src/JsonSettingsIO.cpp @@ -121,6 +121,9 @@ bool JsonSettingsIO::saveSettings(const CrossPointSettings& s, const char* path) doc["frontButtonLeft"] = s.frontButtonLeft; doc["frontButtonRight"] = s.frontButtonRight; + // Mod: timezone offset is int8_t, not uint8_t — handled separately + doc["timezoneOffsetHours"] = s.timezoneOffsetHours; + String json; serializeJson(doc, json); return Storage.writeFile(path, json); @@ -200,6 +203,11 @@ bool JsonSettingsIO::loadSettings(CrossPointSettings& s, const char* json, bool* clamp(doc["frontButtonRight"] | (uint8_t)S::FRONT_HW_RIGHT, S::FRONT_BUTTON_HARDWARE_COUNT, S::FRONT_HW_RIGHT); CrossPointSettings::validateFrontButtonMapping(s); + // Mod: timezone offset is int8_t, not uint8_t + s.timezoneOffsetHours = doc["timezoneOffsetHours"] | (int8_t)0; + if (s.timezoneOffsetHours < -12) s.timezoneOffsetHours = -12; + if (s.timezoneOffsetHours > 14) s.timezoneOffsetHours = 14; + LOG_DBG("CPS", "Settings loaded from file"); return true; diff --git a/src/SettingsList.h b/src/SettingsList.h index f69c9e93..79a8eb82 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -118,6 +118,25 @@ inline const std::vector& getSettingsList() { SettingInfo::String(StrId::STR_PASSWORD, SETTINGS.opdsPassword, sizeof(SETTINGS.opdsPassword), "opdsPassword", StrId::STR_OPDS_BROWSER) .withObfuscated(), + + // --- Mod: Clock --- + SettingInfo::Enum(StrId::STR_CLOCK, &CrossPointSettings::clockFormat, + {StrId::STR_OFF, StrId::STR_CLOCK_AMPM, StrId::STR_CLOCK_24H}, "clockFormat", + StrId::STR_CAT_CLOCK), + SettingInfo::Enum(StrId::STR_CLOCK_SIZE, &CrossPointSettings::clockSize, + {StrId::STR_CLOCK_SIZE_SMALL, StrId::STR_CLOCK_SIZE_MEDIUM, StrId::STR_CLOCK_SIZE_LARGE}, + "clockSize", StrId::STR_CAT_CLOCK), + SettingInfo::Enum(StrId::STR_TIMEZONE, &CrossPointSettings::timezone, + {StrId::STR_TZ_UTC, StrId::STR_TZ_EASTERN, StrId::STR_TZ_CENTRAL, StrId::STR_TZ_MOUNTAIN, + StrId::STR_TZ_PACIFIC, StrId::STR_TZ_ALASKA, StrId::STR_TZ_HAWAII, StrId::STR_TZ_CUSTOM}, + "timezone", StrId::STR_CAT_CLOCK), + SettingInfo::Toggle(StrId::STR_AUTO_NTP_SYNC, &CrossPointSettings::autoNtpSync, "autoNtpSync", + StrId::STR_CAT_CLOCK), + + // --- Mod: Sleep Screen --- + SettingInfo::Enum(StrId::STR_LETTERBOX_FILL, &CrossPointSettings::sleepScreenLetterboxFill, + {StrId::STR_DITHERED, StrId::STR_SOLID, StrId::STR_NONE_OPT}, "sleepScreenLetterboxFill", + StrId::STR_CAT_DISPLAY), // --- Status Bar Settings (web-only, uses StatusBarSettingsActivity) --- SettingInfo::Toggle(StrId::STR_CHAPTER_PAGE_COUNT, &CrossPointSettings::statusBarChapterPageCount, "statusBarChapterPageCount", StrId::STR_CUSTOMISE_STATUS_BAR), diff --git a/src/main.cpp b/src/main.cpp index d629346f..5e51b1ae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,17 +12,21 @@ #include #include +#include #include +#include #include "CrossPointSettings.h" #include "CrossPointState.h" #include "KOReaderCredentialStore.h" #include "MappedInputManager.h" +#include "OpdsServerStore.h" #include "RecentBooksStore.h" #include "activities/Activity.h" #include "activities/ActivityManager.h" #include "components/UITheme.h" #include "fontIds.h" +#include "util/BootNtpSync.h" #include "util/ButtonNavigator.h" #include "util/ScreenshotUtil.h" @@ -255,8 +259,15 @@ void setup() { HalSystem::clearPanic(); // TODO: move this to an activity when we have one to display the panic info SETTINGS.loadFromFile(); + + // Apply saved timezone setting on boot + setenv("TZ", SETTINGS.getTimezonePosixStr(), 1); + tzset(); + I18N.loadSettings(); KOREADER_STORE.loadFromFile(); + OPDS_STORE.loadFromFile(); + BootNtpSync::start(); UITheme::getInstance().reload(); ButtonNavigator::setMappedInputManager(mappedInputManager); @@ -281,6 +292,18 @@ void setup() { // First serial output only here to avoid timing inconsistencies for power button press duration verification LOG_DBG("MAIN", "Starting CrossPoint version " CROSSPOINT_VERSION); + // Log RTC time on boot for debugging + { + time_t now = time(nullptr); + struct tm* t = localtime(&now); + if (t != nullptr && t->tm_year > 100) { + LOG_DBG("MAIN", "RTC time: %04d-%02d-%02d %02d:%02d:%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + } else { + LOG_DBG("MAIN", "RTC time not set (epoch)"); + } + } + setupDisplayAndFonts(); activityManager.goToBoot(); @@ -376,6 +399,28 @@ void loop() { return; } + // Refresh screen when the displayed minute changes (clock in header) + if (SETTINGS.clockFormat != CrossPointSettings::CLOCK_OFF) { + static int lastRenderedMinute = -1; + static bool sawInvalidTime = false; + time_t now = time(nullptr); + struct tm* t = localtime(&now); + if (t != nullptr && t->tm_year > 100) { + const int currentMinute = t->tm_hour * 60 + t->tm_min; + if (lastRenderedMinute < 0) { + lastRenderedMinute = currentMinute; + if (sawInvalidTime) { + activityManager.requestUpdate(); + } + } else if (currentMinute != lastRenderedMinute) { + activityManager.requestUpdate(); + lastRenderedMinute = currentMinute; + } + } else { + sawInvalidTime = true; + } + } + const unsigned long activityStartTime = millis(); activityManager.loop(); const unsigned long activityDuration = millis() - activityStartTime;