mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-05 15:17:37 +03:00
## Summary When uploading or downloading an updated ebook from SD/WebUI/OPDS with same the filename the `.crosspoint` cache is not cleared. This can lead to issues with the Table of Contents and hangs when switching between chapters. I encountered this issue in two places: - When I need to do further ePub cleaning using Calibre after I load an ePub and find that some of its formatting should be cleaned up. When I reprocess the same book and want to place it back in the same location I need a way to invalidate the cache. - When syncing RSS feed generated epubs. I generate news ePubs with filenames like `news-outlet.epub` and so every day when I fetch new news the crosspoint cache needs to be cleared to load that file. This change offers the following features: - On web uploads, if the file already exists, the cache for that file is cleared - On OPDS downloads, if the file already exists, the cache for that file is cleared - There's now an action for `Clear Cache` in the Settings page which can clear the cache for all books Addresses https://github.com/crosspoint-reader/crosspoint-reader/issues/281 --- ### 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 --------- Co-authored-by: Dave Allie <dave@daveallie.com>
204 lines
7.7 KiB
C++
204 lines
7.7 KiB
C++
#include "SettingsActivity.h"
|
|
|
|
#include <GfxRenderer.h>
|
|
#include <HardwareSerial.h>
|
|
|
|
#include "CategorySettingsActivity.h"
|
|
#include "CrossPointSettings.h"
|
|
#include "MappedInputManager.h"
|
|
#include "fontIds.h"
|
|
|
|
const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"};
|
|
|
|
namespace {
|
|
constexpr int displaySettingsCount = 5;
|
|
const SettingInfo displaySettings[displaySettingsCount] = {
|
|
// 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::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
|
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"})};
|
|
|
|
constexpr int readerSettingsCount = 9;
|
|
const SettingInfo readerSettings[readerSettingsCount] = {
|
|
SettingInfo::Enum("Font Family", &CrossPointSettings::fontFamily, {"Bookerly", "Noto Sans", "Open Dyslexic"}),
|
|
SettingInfo::Enum("Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}),
|
|
SettingInfo::Enum("Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}),
|
|
SettingInfo::Value("Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}),
|
|
SettingInfo::Enum("Paragraph Alignment", &CrossPointSettings::paragraphAlignment,
|
|
{"Justify", "Left", "Center", "Right"}),
|
|
SettingInfo::Toggle("Hyphenation", &CrossPointSettings::hyphenationEnabled),
|
|
SettingInfo::Enum("Reading Orientation", &CrossPointSettings::orientation,
|
|
{"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}),
|
|
SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing),
|
|
SettingInfo::Toggle("Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing)};
|
|
|
|
constexpr int controlsSettingsCount = 4;
|
|
const SettingInfo controlsSettings[controlsSettingsCount] = {
|
|
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("Short Power Button Click", &CrossPointSettings::shortPwrBtn, {"Ignore", "Sleep", "Page Turn"})};
|
|
|
|
constexpr int systemSettingsCount = 5;
|
|
const SettingInfo systemSettings[systemSettingsCount] = {
|
|
SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout,
|
|
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
|
|
SettingInfo::Action("KOReader Sync"), SettingInfo::Action("Calibre Settings"), SettingInfo::Action("Clear Cache"),
|
|
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 category
|
|
selectedCategoryIndex = 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 category selection
|
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
|
enterCategory(selectedCategoryIndex);
|
|
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)
|
|
selectedCategoryIndex = (selectedCategoryIndex > 0) ? (selectedCategoryIndex - 1) : (categoryCount - 1);
|
|
updateRequired = true;
|
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
|
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
|
// Move selection down (with wrap around)
|
|
selectedCategoryIndex = (selectedCategoryIndex < categoryCount - 1) ? (selectedCategoryIndex + 1) : 0;
|
|
updateRequired = true;
|
|
}
|
|
}
|
|
|
|
void SettingsActivity::enterCategory(int categoryIndex) {
|
|
if (categoryIndex < 0 || categoryIndex >= categoryCount) {
|
|
return;
|
|
}
|
|
|
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
|
exitActivity();
|
|
|
|
const SettingInfo* settingsList = nullptr;
|
|
int settingsCount = 0;
|
|
|
|
switch (categoryIndex) {
|
|
case 0: // Display
|
|
settingsList = displaySettings;
|
|
settingsCount = displaySettingsCount;
|
|
break;
|
|
case 1: // Reader
|
|
settingsList = readerSettings;
|
|
settingsCount = readerSettingsCount;
|
|
break;
|
|
case 2: // Controls
|
|
settingsList = controlsSettings;
|
|
settingsCount = controlsSettingsCount;
|
|
break;
|
|
case 3: // System
|
|
settingsList = systemSettings;
|
|
settingsCount = systemSettingsCount;
|
|
break;
|
|
}
|
|
|
|
enterNewActivity(new CategorySettingsActivity(renderer, mappedInput, categoryNames[categoryIndex], settingsList,
|
|
settingsCount, [this] {
|
|
exitActivity();
|
|
updateRequired = true;
|
|
}));
|
|
xSemaphoreGive(renderingMutex);
|
|
}
|
|
|
|
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 + selectedCategoryIndex * 30 - 2, pageWidth - 1, 30);
|
|
|
|
// Draw all categories
|
|
for (int i = 0; i < categoryCount; i++) {
|
|
const int categoryY = 60 + i * 30; // 30 pixels between categories
|
|
|
|
// Draw category name
|
|
renderer.drawText(UI_10_FONT_ID, 20, categoryY, categoryNames[i], i != selectedCategoryIndex);
|
|
}
|
|
|
|
// 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("« Back", "Select", "", "");
|
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
|
|
|
// Always use standard refresh for settings screen
|
|
renderer.displayBuffer();
|
|
}
|