mod: Phase 2a - add mod settings, I18n strings, and main.cpp integration

CrossPointSettings: Add mod-specific enums and fields:
- Clock: CLOCK_FORMAT, CLOCK_SIZE, TIMEZONE, clockFormat, clockSize,
  timezone, timezoneOffsetHours, autoNtpSync
- Sleep: SLEEP_SCREEN_LETTERBOX_FILL, sleepScreenLetterboxFill
- Reader: preferredPortrait, preferredLandscape
- Indexing: INDEXING_DISPLAY, indexingDisplay
- getTimezonePosixStr() for POSIX TZ string generation

main.cpp: Integrate mod initialization:
- OPDS store loading, boot NTP sync, timezone application
- Clock refresh loop (re-render on minute change)
- RTC time logging on boot

SettingsList.h: Add clock, timezone, and letterbox fill settings
JsonSettingsIO.cpp: Handle int8_t timezoneOffsetHours separately
I18n: Add ~80 mod string keys (english.yaml + regenerated I18nKeys.h)

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-07 15:14:35 -05:00
parent dfbc931c14
commit 66a754dabd
6 changed files with 232 additions and 0 deletions

View File

@@ -341,3 +341,81 @@ STR_LINK: "[link]"
STR_SCREENSHOT_BUTTON: "Take screenshot" STR_SCREENSHOT_BUTTON: "Take screenshot"
STR_AUTO_TURN_ENABLED: "Auto Turn Enabled: " STR_AUTO_TURN_ENABLED: "Auto Turn Enabled: "
STR_AUTO_TURN_PAGES_PER_MIN: "Auto Turn (Pages Per Minute)" 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"

View File

@@ -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";
}
}

View File

@@ -137,6 +137,41 @@ class CrossPointSettings {
// Image rendering in EPUB reader // Image rendering in EPUB reader
enum IMAGE_RENDERING { IMAGES_DISPLAY = 0, IMAGES_PLACEHOLDER = 1, IMAGES_SUPPRESS = 2, IMAGE_RENDERING_COUNT }; 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 // Sleep screen settings
uint8_t sleepScreen = DARK; uint8_t sleepScreen = DARK;
// Sleep screen cover mode settings // Sleep screen cover mode settings
@@ -200,6 +235,26 @@ class CrossPointSettings {
// Image rendering mode in EPUB reader // Image rendering mode in EPUB reader
uint8_t imageRendering = IMAGES_DISPLAY; 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; ~CrossPointSettings() = default;
// Get singleton instance // Get singleton instance
@@ -225,6 +280,7 @@ class CrossPointSettings {
float getReaderLineCompression() const; float getReaderLineCompression() const;
unsigned long getSleepTimeoutMs() const; unsigned long getSleepTimeoutMs() const;
int getRefreshFrequency() const; int getRefreshFrequency() const;
const char* getTimezonePosixStr() const;
}; };
// Helper macro to access settings // Helper macro to access settings

View File

@@ -121,6 +121,9 @@ bool JsonSettingsIO::saveSettings(const CrossPointSettings& s, const char* path)
doc["frontButtonLeft"] = s.frontButtonLeft; doc["frontButtonLeft"] = s.frontButtonLeft;
doc["frontButtonRight"] = s.frontButtonRight; doc["frontButtonRight"] = s.frontButtonRight;
// Mod: timezone offset is int8_t, not uint8_t — handled separately
doc["timezoneOffsetHours"] = s.timezoneOffsetHours;
String json; String json;
serializeJson(doc, json); serializeJson(doc, json);
return Storage.writeFile(path, 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); clamp(doc["frontButtonRight"] | (uint8_t)S::FRONT_HW_RIGHT, S::FRONT_BUTTON_HARDWARE_COUNT, S::FRONT_HW_RIGHT);
CrossPointSettings::validateFrontButtonMapping(s); 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"); LOG_DBG("CPS", "Settings loaded from file");
return true; return true;

View File

@@ -118,6 +118,25 @@ inline const std::vector<SettingInfo>& getSettingsList() {
SettingInfo::String(StrId::STR_PASSWORD, SETTINGS.opdsPassword, sizeof(SETTINGS.opdsPassword), "opdsPassword", SettingInfo::String(StrId::STR_PASSWORD, SETTINGS.opdsPassword, sizeof(SETTINGS.opdsPassword), "opdsPassword",
StrId::STR_OPDS_BROWSER) StrId::STR_OPDS_BROWSER)
.withObfuscated(), .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) --- // --- Status Bar Settings (web-only, uses StatusBarSettingsActivity) ---
SettingInfo::Toggle(StrId::STR_CHAPTER_PAGE_COUNT, &CrossPointSettings::statusBarChapterPageCount, SettingInfo::Toggle(StrId::STR_CHAPTER_PAGE_COUNT, &CrossPointSettings::statusBarChapterPageCount,
"statusBarChapterPageCount", StrId::STR_CUSTOMISE_STATUS_BAR), "statusBarChapterPageCount", StrId::STR_CUSTOMISE_STATUS_BAR),

View File

@@ -12,17 +12,21 @@
#include <SPI.h> #include <SPI.h>
#include <builtinFonts/all.h> #include <builtinFonts/all.h>
#include <cstdlib>
#include <cstring> #include <cstring>
#include <ctime>
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "CrossPointState.h" #include "CrossPointState.h"
#include "KOReaderCredentialStore.h" #include "KOReaderCredentialStore.h"
#include "MappedInputManager.h" #include "MappedInputManager.h"
#include "OpdsServerStore.h"
#include "RecentBooksStore.h" #include "RecentBooksStore.h"
#include "activities/Activity.h" #include "activities/Activity.h"
#include "activities/ActivityManager.h" #include "activities/ActivityManager.h"
#include "components/UITheme.h" #include "components/UITheme.h"
#include "fontIds.h" #include "fontIds.h"
#include "util/BootNtpSync.h"
#include "util/ButtonNavigator.h" #include "util/ButtonNavigator.h"
#include "util/ScreenshotUtil.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 HalSystem::clearPanic(); // TODO: move this to an activity when we have one to display the panic info
SETTINGS.loadFromFile(); SETTINGS.loadFromFile();
// Apply saved timezone setting on boot
setenv("TZ", SETTINGS.getTimezonePosixStr(), 1);
tzset();
I18N.loadSettings(); I18N.loadSettings();
KOREADER_STORE.loadFromFile(); KOREADER_STORE.loadFromFile();
OPDS_STORE.loadFromFile();
BootNtpSync::start();
UITheme::getInstance().reload(); UITheme::getInstance().reload();
ButtonNavigator::setMappedInputManager(mappedInputManager); ButtonNavigator::setMappedInputManager(mappedInputManager);
@@ -281,6 +292,18 @@ void setup() {
// First serial output only here to avoid timing inconsistencies for power button press duration verification // First serial output only here to avoid timing inconsistencies for power button press duration verification
LOG_DBG("MAIN", "Starting CrossPoint version " CROSSPOINT_VERSION); 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(); setupDisplayAndFonts();
activityManager.goToBoot(); activityManager.goToBoot();
@@ -376,6 +399,28 @@ void loop() {
return; 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(); const unsigned long activityStartTime = millis();
activityManager.loop(); activityManager.loop();
const unsigned long activityDuration = millis() - activityStartTime; const unsigned long activityDuration = millis() - activityStartTime;