Files
crosspoint-reader-mod/src/SettingsList.h
cottongin 2aa13ea2de feat: port upstream OPDS improvements (PRs #1207, #1209)
Port two upstream PRs:

- PR #1207: Replace manual chunked download loop with
  HTTPClient::writeToStream via a FileWriteStream adapter, improving
  reliability for OPDS file downloads including chunked transfers.

- PR #1209: Add support for multiple OPDS servers with a new
  OpdsServerStore (JSON persistence with MAC-based password obfuscation),
  OpdsServerListActivity and OpdsSettingsActivity UIs, per-server
  credentials passed to HttpDownloader, web UI management endpoints,
  and migration from legacy single-server settings.

Made-with: Cursor
2026-02-26 19:14:59 -05:00

195 lines
11 KiB
C++

#pragma once
#include <I18n.h>
#include <vector>
#include "CrossPointSettings.h"
#include "KOReaderCredentialStore.h"
#include "activities/settings/SettingsActivity.h"
// Compile-time table of available font families and their enum values.
// Used by the DynamicEnum getter/setter to map between list indices and stored FONT_FAMILY values.
struct FontFamilyMapping {
const char* name;
uint8_t value;
};
inline constexpr FontFamilyMapping kFontFamilyMappings[] = {
#ifndef OMIT_BOOKERLY
{"Bookerly", CrossPointSettings::BOOKERLY},
#endif
#ifndef OMIT_NOTOSANS
{"Noto Sans", CrossPointSettings::NOTOSANS},
#endif
#ifndef OMIT_OPENDYSLEXIC
{"Open Dyslexic", CrossPointSettings::OPENDYSLEXIC},
#endif
};
inline constexpr size_t kFontFamilyMappingCount = sizeof(kFontFamilyMappings) / sizeof(kFontFamilyMappings[0]);
static_assert(kFontFamilyMappingCount > 0, "At least one font family must be available");
// Shared settings list used by both the device settings UI and the web settings API.
// Each entry has a key (for JSON API) and category (for grouping).
// ACTION-type entries and entries without a key are device-only.
inline std::vector<SettingInfo> getSettingsList() {
// Build font family StrId options from the compile-time mapping table
constexpr StrId kFontFamilyStrIds[] = {
#ifndef OMIT_BOOKERLY
StrId::STR_BOOKERLY,
#endif
#ifndef OMIT_NOTOSANS
StrId::STR_NOTO_SANS,
#endif
#ifndef OMIT_OPENDYSLEXIC
StrId::STR_OPEN_DYSLEXIC,
#endif
};
std::vector<StrId> fontFamilyStrIds(std::begin(kFontFamilyStrIds), std::end(kFontFamilyStrIds));
return {
// --- Display ---
SettingInfo::Enum(StrId::STR_SLEEP_SCREEN, &CrossPointSettings::sleepScreen,
{StrId::STR_DARK, StrId::STR_LIGHT, StrId::STR_CUSTOM, StrId::STR_COVER, StrId::STR_NONE_OPT,
StrId::STR_COVER_CUSTOM},
"sleepScreen", StrId::STR_CAT_DISPLAY),
SettingInfo::Enum(StrId::STR_SLEEP_COVER_MODE, &CrossPointSettings::sleepScreenCoverMode,
{StrId::STR_FIT, StrId::STR_CROP}, "sleepScreenCoverMode", StrId::STR_CAT_DISPLAY),
SettingInfo::Enum(StrId::STR_SLEEP_COVER_FILTER, &CrossPointSettings::sleepScreenCoverFilter,
{StrId::STR_NONE_OPT, StrId::STR_FILTER_CONTRAST, StrId::STR_INVERTED},
"sleepScreenCoverFilter", StrId::STR_CAT_DISPLAY),
SettingInfo::Enum(StrId::STR_LETTERBOX_FILL, &CrossPointSettings::sleepScreenLetterboxFill,
{StrId::STR_DITHERED, StrId::STR_SOLID, StrId::STR_NONE_OPT}, "sleepScreenLetterboxFill",
StrId::STR_CAT_DISPLAY),
SettingInfo::Enum(
StrId::STR_STATUS_BAR, &CrossPointSettings::statusBar,
{StrId::STR_NONE_OPT, StrId::STR_NO_PROGRESS, StrId::STR_STATUS_BAR_FULL_PERCENT,
StrId::STR_STATUS_BAR_FULL_BOOK, StrId::STR_STATUS_BAR_BOOK_ONLY, StrId::STR_STATUS_BAR_FULL_CHAPTER},
"statusBar", StrId::STR_CAT_DISPLAY),
SettingInfo::Enum(StrId::STR_INDEXING_DISPLAY, &CrossPointSettings::indexingDisplay,
{StrId::STR_INDEXING_POPUP, StrId::STR_INDEXING_STATUS_TEXT, StrId::STR_INDEXING_STATUS_ICON},
"indexingDisplay", StrId::STR_CAT_DISPLAY),
SettingInfo::Enum(StrId::STR_HIDE_BATTERY, &CrossPointSettings::hideBatteryPercentage,
{StrId::STR_NEVER, StrId::STR_IN_READER, StrId::STR_ALWAYS}, "hideBatteryPercentage",
StrId::STR_CAT_DISPLAY),
SettingInfo::Enum(
StrId::STR_REFRESH_FREQ, &CrossPointSettings::refreshFrequency,
{StrId::STR_PAGES_1, StrId::STR_PAGES_5, StrId::STR_PAGES_10, StrId::STR_PAGES_15, StrId::STR_PAGES_30},
"refreshFrequency", StrId::STR_CAT_DISPLAY),
SettingInfo::Enum(StrId::STR_UI_THEME, &CrossPointSettings::uiTheme,
{StrId::STR_THEME_CLASSIC, StrId::STR_THEME_LYRA, StrId::STR_THEME_LYRA_EXTENDED}, "uiTheme",
StrId::STR_CAT_DISPLAY),
SettingInfo::Toggle(StrId::STR_SUNLIGHT_FADING_FIX, &CrossPointSettings::fadingFix, "fadingFix",
StrId::STR_CAT_DISPLAY),
// --- Clock ---
SettingInfo::Enum(StrId::STR_CLOCK, &CrossPointSettings::clockFormat,
{StrId::STR_STATE_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),
// --- Reader ---
SettingInfo::DynamicEnum(
StrId::STR_FONT_FAMILY, std::move(fontFamilyStrIds),
[]() -> uint8_t {
for (uint8_t i = 0; i < kFontFamilyMappingCount; i++) {
if (kFontFamilyMappings[i].value == SETTINGS.fontFamily) return i;
}
return 0; // fallback to first available family
},
[](uint8_t idx) {
if (idx < kFontFamilyMappingCount) {
SETTINGS.fontFamily = kFontFamilyMappings[idx].value;
}
},
"fontFamily", StrId::STR_CAT_READER),
SettingInfo::Enum(StrId::STR_FONT_SIZE, &CrossPointSettings::fontSize,
{StrId::STR_SMALL, StrId::STR_MEDIUM, StrId::STR_LARGE, StrId::STR_X_LARGE}, "fontSize",
StrId::STR_CAT_READER),
SettingInfo::Enum(StrId::STR_LINE_SPACING, &CrossPointSettings::lineSpacing,
{StrId::STR_TIGHT, StrId::STR_NORMAL, StrId::STR_WIDE}, "lineSpacing", StrId::STR_CAT_READER),
SettingInfo::Value(StrId::STR_SCREEN_MARGIN, &CrossPointSettings::screenMargin, {5, 40, 5}, "screenMargin",
StrId::STR_CAT_READER),
SettingInfo::Enum(StrId::STR_PARA_ALIGNMENT, &CrossPointSettings::paragraphAlignment,
{StrId::STR_JUSTIFY, StrId::STR_ALIGN_LEFT, StrId::STR_CENTER, StrId::STR_ALIGN_RIGHT,
StrId::STR_BOOK_S_STYLE},
"paragraphAlignment", StrId::STR_CAT_READER),
SettingInfo::Toggle(StrId::STR_EMBEDDED_STYLE, &CrossPointSettings::embeddedStyle, "embeddedStyle",
StrId::STR_CAT_READER),
SettingInfo::Toggle(StrId::STR_HYPHENATION, &CrossPointSettings::hyphenationEnabled, "hyphenationEnabled",
StrId::STR_CAT_READER),
SettingInfo::Enum(StrId::STR_ORIENTATION, &CrossPointSettings::orientation,
{StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED, StrId::STR_LANDSCAPE_CCW},
"orientation", StrId::STR_CAT_READER),
SettingInfo::DynamicEnum(
StrId::STR_PREFERRED_PORTRAIT, {StrId::STR_PORTRAIT, StrId::STR_INVERTED},
[] { return static_cast<uint8_t>(SETTINGS.preferredPortrait == CrossPointSettings::INVERTED ? 1 : 0); },
[](uint8_t idx) {
SETTINGS.preferredPortrait = (idx == 1) ? CrossPointSettings::INVERTED : CrossPointSettings::PORTRAIT;
},
"preferredPortrait", StrId::STR_CAT_READER),
SettingInfo::DynamicEnum(
StrId::STR_PREFERRED_LANDSCAPE, {StrId::STR_LANDSCAPE_CW, StrId::STR_LANDSCAPE_CCW},
[] { return static_cast<uint8_t>(SETTINGS.preferredLandscape == CrossPointSettings::LANDSCAPE_CCW ? 1 : 0); },
[](uint8_t idx) {
SETTINGS.preferredLandscape =
(idx == 1) ? CrossPointSettings::LANDSCAPE_CCW : CrossPointSettings::LANDSCAPE_CW;
},
"preferredLandscape", StrId::STR_CAT_READER),
SettingInfo::Toggle(StrId::STR_EXTRA_SPACING, &CrossPointSettings::extraParagraphSpacing, "extraParagraphSpacing",
StrId::STR_CAT_READER),
SettingInfo::Toggle(StrId::STR_TEXT_AA, &CrossPointSettings::textAntiAliasing, "textAntiAliasing",
StrId::STR_CAT_READER),
// --- Controls ---
SettingInfo::Enum(StrId::STR_SIDE_BTN_LAYOUT, &CrossPointSettings::sideButtonLayout,
{StrId::STR_PREV_NEXT, StrId::STR_NEXT_PREV}, "sideButtonLayout", StrId::STR_CAT_CONTROLS),
SettingInfo::Toggle(StrId::STR_LONG_PRESS_SKIP, &CrossPointSettings::longPressChapterSkip, "longPressChapterSkip",
StrId::STR_CAT_CONTROLS),
SettingInfo::Enum(StrId::STR_SHORT_PWR_BTN, &CrossPointSettings::shortPwrBtn,
{StrId::STR_IGNORE, StrId::STR_SLEEP, StrId::STR_PAGE_TURN}, "shortPwrBtn",
StrId::STR_CAT_CONTROLS),
// --- System ---
SettingInfo::Enum(StrId::STR_TIME_TO_SLEEP, &CrossPointSettings::sleepTimeout,
{StrId::STR_MIN_1, StrId::STR_MIN_5, StrId::STR_MIN_10, StrId::STR_MIN_15, StrId::STR_MIN_30},
"sleepTimeout", StrId::STR_CAT_SYSTEM),
// --- KOReader Sync (web-only, uses KOReaderCredentialStore) ---
SettingInfo::DynamicString(
StrId::STR_KOREADER_USERNAME, [] { return KOREADER_STORE.getUsername(); },
[](const std::string& v) {
KOREADER_STORE.setCredentials(v, KOREADER_STORE.getPassword());
KOREADER_STORE.saveToFile();
},
"koUsername", StrId::STR_KOREADER_SYNC),
SettingInfo::DynamicString(
StrId::STR_KOREADER_PASSWORD, [] { return KOREADER_STORE.getPassword(); },
[](const std::string& v) {
KOREADER_STORE.setCredentials(KOREADER_STORE.getUsername(), v);
KOREADER_STORE.saveToFile();
},
"koPassword", StrId::STR_KOREADER_SYNC),
SettingInfo::DynamicString(
StrId::STR_SYNC_SERVER_URL, [] { return KOREADER_STORE.getServerUrl(); },
[](const std::string& v) {
KOREADER_STORE.setServerUrl(v);
KOREADER_STORE.saveToFile();
},
"koServerUrl", StrId::STR_KOREADER_SYNC),
SettingInfo::DynamicEnum(
StrId::STR_DOCUMENT_MATCHING, {StrId::STR_FILENAME, StrId::STR_BINARY},
[] { return static_cast<uint8_t>(KOREADER_STORE.getMatchMethod()); },
[](uint8_t v) {
KOREADER_STORE.setMatchMethod(static_cast<DocumentMatchMethod>(v));
KOREADER_STORE.saveToFile();
},
"koMatchMethod", StrId::STR_KOREADER_SYNC),
};
}