Xteink-X4-crosspoint-reader/src/CrossPointSettings.cpp
dpoulter c3ce6e11da Merge master into feature/sleep-bmp-selection
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-01 08:57:37 +02:00

316 lines
10 KiB
C++

#include "CrossPointSettings.h"
#include <HardwareSerial.h>
#include <SDCardManager.h>
#include <Serialization.h>
#include <cstring>
#include "fontIds.h"
// Initialize the static instance
CrossPointSettings CrossPointSettings::instance;
void readAndValidate(FsFile& file, uint8_t& member, const uint8_t maxValue) {
uint8_t tempValue;
serialization::readPod(file, tempValue);
if (tempValue < maxValue) {
member = tempValue;
}
}
namespace {
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
// Increment this when adding new persisted settings fields
constexpr uint8_t SETTINGS_COUNT = 24;
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
} // namespace
bool CrossPointSettings::saveToFile() const {
// Make sure the directory exists
SdMan.mkdir("/.crosspoint");
FsFile outputFile;
if (!SdMan.openFileForWrite("CPS", SETTINGS_FILE, outputFile)) {
return false;
}
serialization::writePod(outputFile, SETTINGS_FILE_VERSION);
serialization::writePod(outputFile, SETTINGS_COUNT);
serialization::writePod(outputFile, sleepScreen);
serialization::writePod(outputFile, extraParagraphSpacing);
serialization::writePod(outputFile, shortPwrBtn);
serialization::writePod(outputFile, statusBar);
serialization::writePod(outputFile, orientation);
serialization::writePod(outputFile, frontButtonLayout);
serialization::writePod(outputFile, sideButtonLayout);
serialization::writePod(outputFile, fontFamily);
serialization::writePod(outputFile, fontSize);
serialization::writePod(outputFile, lineSpacing);
serialization::writePod(outputFile, paragraphAlignment);
serialization::writePod(outputFile, sleepTimeout);
serialization::writePod(outputFile, refreshFrequency);
serialization::writePod(outputFile, screenMargin);
serialization::writePod(outputFile, sleepScreenCoverMode);
serialization::writeString(outputFile, std::string(opdsServerUrl));
serialization::writePod(outputFile, textAntiAliasing);
serialization::writePod(outputFile, hideBatteryPercentage);
serialization::writePod(outputFile, longPressChapterSkip);
serialization::writePod(outputFile, hyphenationEnabled);
serialization::writeString(outputFile, std::string(selectedSleepBmp));
serialization::writeString(outputFile, std::string(opdsUsername));
serialization::writeString(outputFile, std::string(opdsPassword));
serialization::writePod(outputFile, sleepScreenCoverFilter);
outputFile.close();
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
return true;
}
bool CrossPointSettings::loadFromFile() {
FsFile inputFile;
if (!SdMan.openFileForRead("CPS", SETTINGS_FILE, inputFile)) {
return false;
}
uint8_t version;
serialization::readPod(inputFile, version);
if (version != SETTINGS_FILE_VERSION) {
Serial.printf("[%lu] [CPS] Deserialization failed: Unknown version %u\n", millis(), version);
inputFile.close();
return false;
}
uint8_t fileSettingsCount = 0;
serialization::readPod(inputFile, fileSettingsCount);
// load settings that exist (support older files with fewer fields)
uint8_t settingsRead = 0;
do {
readAndValidate(inputFile, sleepScreen, SLEEP_SCREEN_MODE_COUNT);
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, extraParagraphSpacing);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, shortPwrBtn, SHORT_PWRBTN_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, statusBar, STATUS_BAR_MODE_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, orientation, ORIENTATION_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, frontButtonLayout, FRONT_BUTTON_LAYOUT_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, sideButtonLayout, SIDE_BUTTON_LAYOUT_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, fontFamily, FONT_FAMILY_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, fontSize, FONT_SIZE_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, lineSpacing, LINE_COMPRESSION_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, paragraphAlignment, PARAGRAPH_ALIGNMENT_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, sleepTimeout, SLEEP_TIMEOUT_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, refreshFrequency, REFRESH_FREQUENCY_COUNT);
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, screenMargin);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, sleepScreenCoverMode, SLEEP_SCREEN_COVER_MODE_COUNT);
if (++settingsRead >= fileSettingsCount) break;
{
std::string urlStr;
serialization::readString(inputFile, urlStr);
strncpy(opdsServerUrl, urlStr.c_str(), sizeof(opdsServerUrl) - 1);
opdsServerUrl[sizeof(opdsServerUrl) - 1] = '\0';
}
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, textAntiAliasing);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, hideBatteryPercentage, HIDE_BATTERY_PERCENTAGE_COUNT);
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, longPressChapterSkip);
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, hyphenationEnabled);
if (++settingsRead >= fileSettingsCount) break;
{
std::string bmpStr;
serialization::readString(inputFile, bmpStr);
strncpy(selectedSleepBmp, bmpStr.c_str(), sizeof(selectedSleepBmp) - 1);
selectedSleepBmp[sizeof(selectedSleepBmp) - 1] = '\0';
}
if (++settingsRead >= fileSettingsCount) break;
{
std::string usernameStr;
serialization::readString(inputFile, usernameStr);
strncpy(opdsUsername, usernameStr.c_str(), sizeof(opdsUsername) - 1);
opdsUsername[sizeof(opdsUsername) - 1] = '\0';
}
if (++settingsRead >= fileSettingsCount) break;
{
std::string passwordStr;
serialization::readString(inputFile, passwordStr);
strncpy(opdsPassword, passwordStr.c_str(), sizeof(opdsPassword) - 1);
opdsPassword[sizeof(opdsPassword) - 1] = '\0';
}
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, sleepScreenCoverFilter, SLEEP_SCREEN_COVER_FILTER_COUNT);
if (++settingsRead >= fileSettingsCount) break;
} while (false);
inputFile.close();
Serial.printf("[%lu] [CPS] Settings loaded from file\n", millis());
return true;
}
float CrossPointSettings::getReaderLineCompression() const {
switch (fontFamily) {
case BOOKERLY:
default:
switch (lineSpacing) {
case TIGHT:
return 0.95f;
case NORMAL:
default:
return 1.0f;
case WIDE:
return 1.1f;
}
case NOTOSANS:
switch (lineSpacing) {
case TIGHT:
return 0.90f;
case NORMAL:
default:
return 0.95f;
case WIDE:
return 1.0f;
}
case OPENDYSLEXIC:
switch (lineSpacing) {
case TIGHT:
return 0.90f;
case NORMAL:
default:
return 0.95f;
case WIDE:
return 1.0f;
}
}
}
unsigned long CrossPointSettings::getSleepTimeoutMs() const {
switch (sleepTimeout) {
case SLEEP_1_MIN:
return 1UL * 60 * 1000;
case SLEEP_5_MIN:
return 5UL * 60 * 1000;
case SLEEP_10_MIN:
default:
return 10UL * 60 * 1000;
case SLEEP_15_MIN:
return 15UL * 60 * 1000;
case SLEEP_30_MIN:
return 30UL * 60 * 1000;
}
}
int CrossPointSettings::getRefreshFrequency() const {
switch (refreshFrequency) {
case REFRESH_1:
return 1;
case REFRESH_5:
return 5;
case REFRESH_10:
return 10;
case REFRESH_15:
default:
return 15;
case REFRESH_30:
return 30;
}
}
int CrossPointSettings::getReaderFontId() const {
switch (fontFamily) {
case BOOKERLY:
default:
switch (fontSize) {
case SMALL:
return BOOKERLY_12_FONT_ID;
case MEDIUM:
default:
return BOOKERLY_14_FONT_ID;
case LARGE:
return BOOKERLY_16_FONT_ID;
case EXTRA_LARGE:
return BOOKERLY_18_FONT_ID;
}
case NOTOSANS:
switch (fontSize) {
case SMALL:
return NOTOSANS_12_FONT_ID;
case MEDIUM:
default:
return NOTOSANS_14_FONT_ID;
case LARGE:
return NOTOSANS_16_FONT_ID;
case EXTRA_LARGE:
return NOTOSANS_18_FONT_ID;
}
case OPENDYSLEXIC:
switch (fontSize) {
case SMALL:
return OPENDYSLEXIC_8_FONT_ID;
case MEDIUM:
default:
return OPENDYSLEXIC_10_FONT_ID;
case LARGE:
return OPENDYSLEXIC_12_FONT_ID;
case EXTRA_LARGE:
return OPENDYSLEXIC_14_FONT_ID;
}
}
}
const char* CrossPointSettings::getRefreshFrequencyString(uint8_t value) {
static const char* options[] = {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"};
static constexpr size_t count = sizeof(options) / sizeof(options[0]);
if (value < count) {
return options[value];
}
return options[REFRESH_15]; // Default
}
size_t CrossPointSettings::getRefreshFrequencyCount() {
static const char* options[] = {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"};
return sizeof(options) / sizeof(options[0]);
}
const char* CrossPointSettings::getSleepScreenString(uint8_t value) {
static const char* options[] = {"Dark", "Light", "Custom", "Cover", "None"};
static constexpr size_t count = sizeof(options) / sizeof(options[0]);
if (value < count) {
return options[value];
}
return options[DARK]; // Default
}
size_t CrossPointSettings::getSleepScreenCount() {
static const char* options[] = {"Dark", "Light", "Custom", "Cover", "None"};
return sizeof(options) / sizeof(options[0]);
}
const char* CrossPointSettings::getSleepTimeoutString(uint8_t value) {
static const char* options[] = {"1 min", "5 min", "10 min", "15 min", "30 min"};
static constexpr size_t count = sizeof(options) / sizeof(options[0]);
if (value < count) {
return options[value];
}
return options[SLEEP_10_MIN]; // Default
}
size_t CrossPointSettings::getSleepTimeoutCount() {
static const char* options[] = {"1 min", "5 min", "10 min", "15 min", "30 min"};
return sizeof(options) / sizeof(options[0]);
}