mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-07 08:07:40 +03:00
## Summary Adds a new "Long-press Chapter Skip" toggle in Settings to control whether holding the side buttons skips chapters. I kept accidentally triggering chapter skips while reading, which caused me to lose my place in the middle of long chapters. ## Additional Context * Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY **_
225 lines
9.0 KiB
C++
225 lines
9.0 KiB
C++
#include "SettingsActivity.h"
|
|
|
|
#include <GfxRenderer.h>
|
|
#include <HardwareSerial.h>
|
|
|
|
#include <cstring>
|
|
|
|
#include "CalibreSettingsActivity.h"
|
|
#include "CrossPointSettings.h"
|
|
#include "MappedInputManager.h"
|
|
#include "OtaUpdateActivity.h"
|
|
#include "fontIds.h"
|
|
|
|
// Define the static settings list
|
|
namespace {
|
|
constexpr int settingsCount = 20;
|
|
const SettingInfo settingsList[settingsCount] = {
|
|
// Should match with SLEEP_SCREEN_MODE
|
|
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
|
|
SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}),
|
|
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}),
|
|
SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}),
|
|
SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing),
|
|
SettingInfo::Toggle("Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing),
|
|
SettingInfo::Enum("Short Power Button Click", &CrossPointSettings::shortPwrBtn, {"Ignore", "Sleep", "Page Turn"}),
|
|
SettingInfo::Enum("Reading Orientation", &CrossPointSettings::orientation,
|
|
{"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}),
|
|
SettingInfo::Enum("Front Button Layout", &CrossPointSettings::frontButtonLayout,
|
|
{"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght"}),
|
|
SettingInfo::Enum("Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout,
|
|
{"Prev, Next", "Next, Prev"}),
|
|
SettingInfo::Toggle("Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip),
|
|
SettingInfo::Enum("Reader Font Family", &CrossPointSettings::fontFamily,
|
|
{"Bookerly", "Noto Sans", "Open Dyslexic"}),
|
|
SettingInfo::Enum("Reader Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}),
|
|
SettingInfo::Enum("Reader Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}),
|
|
SettingInfo::Value("Reader Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}),
|
|
SettingInfo::Enum("Reader Paragraph Alignment", &CrossPointSettings::paragraphAlignment,
|
|
{"Justify", "Left", "Center", "Right"}),
|
|
SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout,
|
|
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
|
|
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
|
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}),
|
|
SettingInfo::Action("Calibre Settings"),
|
|
SettingInfo::Action("Check for updates")};
|
|
} // namespace
|
|
|
|
void SettingsActivity::taskTrampoline(void* param) {
|
|
auto* self = static_cast<SettingsActivity*>(param);
|
|
self->displayTaskLoop();
|
|
}
|
|
|
|
void SettingsActivity::onEnter() {
|
|
Activity::onEnter();
|
|
renderingMutex = xSemaphoreCreateMutex();
|
|
|
|
// Reset selection to first item
|
|
selectedSettingIndex = 0;
|
|
|
|
// Trigger first update
|
|
updateRequired = true;
|
|
|
|
xTaskCreate(&SettingsActivity::taskTrampoline, "SettingsActivityTask",
|
|
4096, // Stack size
|
|
this, // Parameters
|
|
1, // Priority
|
|
&displayTaskHandle // Task handle
|
|
);
|
|
}
|
|
|
|
void SettingsActivity::onExit() {
|
|
ActivityWithSubactivity::onExit();
|
|
|
|
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
|
if (displayTaskHandle) {
|
|
vTaskDelete(displayTaskHandle);
|
|
displayTaskHandle = nullptr;
|
|
}
|
|
vSemaphoreDelete(renderingMutex);
|
|
renderingMutex = nullptr;
|
|
}
|
|
|
|
void SettingsActivity::loop() {
|
|
if (subActivity) {
|
|
subActivity->loop();
|
|
return;
|
|
}
|
|
|
|
// Handle actions with early return
|
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
|
toggleCurrentSetting();
|
|
updateRequired = true;
|
|
return;
|
|
}
|
|
|
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
|
SETTINGS.saveToFile();
|
|
onGoHome();
|
|
return;
|
|
}
|
|
|
|
// Handle navigation
|
|
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
|
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
|
// Move selection up (with wrap-around)
|
|
selectedSettingIndex = (selectedSettingIndex > 0) ? (selectedSettingIndex - 1) : (settingsCount - 1);
|
|
updateRequired = true;
|
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
|
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
|
// Move selection down (with wrap around)
|
|
selectedSettingIndex = (selectedSettingIndex < settingsCount - 1) ? (selectedSettingIndex + 1) : 0;
|
|
updateRequired = true;
|
|
}
|
|
}
|
|
|
|
void SettingsActivity::toggleCurrentSetting() {
|
|
// Validate index
|
|
if (selectedSettingIndex < 0 || selectedSettingIndex >= settingsCount) {
|
|
return;
|
|
}
|
|
|
|
const auto& setting = settingsList[selectedSettingIndex];
|
|
|
|
if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) {
|
|
// Toggle the boolean value using the member pointer
|
|
const bool currentValue = SETTINGS.*(setting.valuePtr);
|
|
SETTINGS.*(setting.valuePtr) = !currentValue;
|
|
} else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {
|
|
const uint8_t currentValue = SETTINGS.*(setting.valuePtr);
|
|
SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size());
|
|
} else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) {
|
|
// Decreasing would also be nice for large ranges I think but oh well can't have everything
|
|
const int8_t currentValue = SETTINGS.*(setting.valuePtr);
|
|
// Wrap to minValue if exceeding setting value boundary
|
|
if (currentValue + setting.valueRange.step > setting.valueRange.max) {
|
|
SETTINGS.*(setting.valuePtr) = setting.valueRange.min;
|
|
} else {
|
|
SETTINGS.*(setting.valuePtr) = currentValue + setting.valueRange.step;
|
|
}
|
|
} else if (setting.type == SettingType::ACTION) {
|
|
if (strcmp(setting.name, "Calibre Settings") == 0) {
|
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
|
exitActivity();
|
|
enterNewActivity(new CalibreSettingsActivity(renderer, mappedInput, [this] {
|
|
exitActivity();
|
|
updateRequired = true;
|
|
}));
|
|
xSemaphoreGive(renderingMutex);
|
|
} else if (strcmp(setting.name, "Check for updates") == 0) {
|
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
|
exitActivity();
|
|
enterNewActivity(new OtaUpdateActivity(renderer, mappedInput, [this] {
|
|
exitActivity();
|
|
updateRequired = true;
|
|
}));
|
|
xSemaphoreGive(renderingMutex);
|
|
}
|
|
} else {
|
|
// Only toggle if it's a toggle type and has a value pointer
|
|
return;
|
|
}
|
|
|
|
// Save settings when they change
|
|
SETTINGS.saveToFile();
|
|
}
|
|
|
|
void SettingsActivity::displayTaskLoop() {
|
|
while (true) {
|
|
if (updateRequired && !subActivity) {
|
|
updateRequired = false;
|
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
|
render();
|
|
xSemaphoreGive(renderingMutex);
|
|
}
|
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
|
}
|
|
}
|
|
|
|
void SettingsActivity::render() const {
|
|
renderer.clearScreen();
|
|
|
|
const auto pageWidth = renderer.getScreenWidth();
|
|
const auto pageHeight = renderer.getScreenHeight();
|
|
|
|
// Draw header
|
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Settings", true, EpdFontFamily::BOLD);
|
|
|
|
// Draw selection
|
|
renderer.fillRect(0, 60 + selectedSettingIndex * 30 - 2, pageWidth - 1, 30);
|
|
|
|
// Draw all settings
|
|
for (int i = 0; i < settingsCount; i++) {
|
|
const int settingY = 60 + i * 30; // 30 pixels between settings
|
|
|
|
// Draw setting name
|
|
renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name, i != selectedSettingIndex);
|
|
|
|
// Draw value based on setting type
|
|
std::string valueText = "";
|
|
if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) {
|
|
const bool value = SETTINGS.*(settingsList[i].valuePtr);
|
|
valueText = value ? "ON" : "OFF";
|
|
} else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) {
|
|
const uint8_t value = SETTINGS.*(settingsList[i].valuePtr);
|
|
valueText = settingsList[i].enumValues[value];
|
|
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) {
|
|
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr));
|
|
}
|
|
const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
|
|
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), i != selectedSettingIndex);
|
|
}
|
|
|
|
// Draw version text above button hints
|
|
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
|
|
pageHeight - 60, CROSSPOINT_VERSION);
|
|
|
|
// Draw help text
|
|
const auto labels = mappedInput.mapLabels("« Save", "Toggle", "", "");
|
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
|
|
|
// Always use standard refresh for settings screen
|
|
renderer.displayBuffer();
|
|
}
|