From 85a9204ad9f2a4803b92d496127e0f26843e3b98 Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Mon, 19 Jan 2026 22:03:17 +0100 Subject: [PATCH 1/8] Adding Ebook menu, go home, clear cache options. The chapter selection is moved to the menu on pos 1. --- src/activities/reader/EpubReaderActivity.cpp | 109 ++++++++++++++---- .../EpubReaderChapterSelectionActivity.cpp | 9 ++ .../reader/EpubReaderMenuActivity.cpp | 100 ++++++++++++++++ .../reader/EpubReaderMenuActivity.h | 50 ++++++++ 4 files changed, 248 insertions(+), 20 deletions(-) create mode 100644 src/activities/reader/EpubReaderMenuActivity.cpp create mode 100644 src/activities/reader/EpubReaderMenuActivity.h diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 89be3bc7..8516f401 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -8,6 +8,7 @@ #include "CrossPointSettings.h" #include "CrossPointState.h" #include "EpubReaderChapterSelectionActivity.h" +#include "EpubReaderMenuActivity.h" #include "MappedInputManager.h" #include "RecentBooksStore.h" #include "ScreenComponents.h" @@ -130,30 +131,98 @@ void EpubReaderActivity::loop() { const int currentPage = section ? section->currentPage : 0; const int totalPages = section ? section->pageCount : 0; exitActivity(); - enterNewActivity(new EpubReaderChapterSelectionActivity( - this->renderer, this->mappedInput, epub, epub->getPath(), currentSpineIndex, currentPage, totalPages, - [this] { + enterNewActivity(new EpubReaderMenuActivity( + this->renderer, this->mappedInput, + [this]() { // On Back exitActivity(); updateRequired = true; }, - [this](const int newSpineIndex) { - if (currentSpineIndex != newSpineIndex) { - currentSpineIndex = newSpineIndex; - nextPageNumber = 0; - section.reset(); + [this](EpubReaderMenuActivity::MenuAction action) { // On Select + switch (action) { + case EpubReaderMenuActivity::MenuAction::SELECT_CHAPTER: { + // Calculate values BEFORE we start destroying things + const int currentP = section ? section->currentPage : 0; + const int totalP = section ? section->pageCount : 0; + const int spineIdx = currentSpineIndex; + const std::string path = epub->getPath(); + + xSemaphoreTake(renderingMutex, portMAX_DELAY); + + // 1. Close the menu + exitActivity(); + + // 2. Open the Chapter Selector + enterNewActivity(new EpubReaderChapterSelectionActivity( + this->renderer, this->mappedInput, epub, path, spineIdx, currentP, totalP, + [this] { + exitActivity(); + updateRequired = true; + }, + [this](const int newSpineIndex) { + if (currentSpineIndex != newSpineIndex) { + currentSpineIndex = newSpineIndex; + nextPageNumber = 0; + section.reset(); + } + exitActivity(); + updateRequired = true; + }, + [this](const int newSpineIndex, const int newPage) { + if (currentSpineIndex != newSpineIndex || (section && section->currentPage != newPage)) { + currentSpineIndex = newSpineIndex; + nextPageNumber = newPage; + section.reset(); + } + exitActivity(); + updateRequired = true; + })); + + xSemaphoreGive(renderingMutex); + break; + break; + } + case EpubReaderMenuActivity::MenuAction::GO_HOME: { + // 2. Trigger the reader's "Go Home" callback + if (onGoHome) { + onGoHome(); + } + + break; + } + case EpubReaderMenuActivity::MenuAction::DELETE_CACHE: { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + section.reset(); + if (epub) { + // 2. BACKUP: Read current progress + // We use the current variables that track our position + uint16_t backupSpine = currentSpineIndex; + uint16_t backupPage = nextPageNumber; + + // 3. WIPE: Clear the cache directory + epub->clearCache(); + + // 4. RESTORE: Re-setup the directory and rewrite the progress file + epub->setupCacheDir(); + + FsFile f; + if (SdMan.openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) { + uint8_t data[4]; + data[0] = backupSpine & 0xFF; + data[1] = (backupSpine >> 8) & 0xFF; + data[2] = backupPage & 0xFF; + data[3] = (backupPage >> 8) & 0xFF; + f.write(data, 4); + f.close(); + Serial.println("[ERS] Progress restored after cache clear"); + } + } + exitActivity(); + updateRequired = true; + xSemaphoreGive(renderingMutex); + if (onGoHome) onGoHome(); + break; + } } - exitActivity(); - updateRequired = true; - }, - [this](const int newSpineIndex, const int newPage) { - // Handle sync position - if (currentSpineIndex != newSpineIndex || (section && section->currentPage != newPage)) { - currentSpineIndex = newSpineIndex; - nextPageNumber = newPage; - section.reset(); - } - exitActivity(); - updateRequired = true; })); xSemaphoreGive(renderingMutex); } diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp index 1b35e143..43d792e4 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp @@ -188,6 +188,11 @@ void EpubReaderChapterSelectionActivity::renderScreen() { const auto pageStartIndex = selectorIndex / pageItems * pageItems; renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30); +<<<<<<< HEAD +======= + // for (int itemIndex = pageStartIndex; itemIndex < totalItems && itemIndex < pageStartIndex + pageItems; itemIndex++) + // { +>>>>>>> e6dd49d (Adding Ebook menu, go home, clear cache options.) for (int i = 0; i < pageItems; i++) { int itemIndex = pageStartIndex + i; if (itemIndex >= totalItems) break; @@ -204,6 +209,10 @@ void EpubReaderChapterSelectionActivity::renderScreen() { const std::string chapterName = renderer.truncatedText(UI_10_FONT_ID, item.title.c_str(), pageWidth - 40 - indentSize); +<<<<<<< HEAD +======= + // FIX: Use displayY here instead of recalculating based on tocIndex +>>>>>>> e6dd49d (Adding Ebook menu, go home, clear cache options.) renderer.drawText(UI_10_FONT_ID, indentSize, displayY, chapterName.c_str(), !isSelected); } } diff --git a/src/activities/reader/EpubReaderMenuActivity.cpp b/src/activities/reader/EpubReaderMenuActivity.cpp new file mode 100644 index 00000000..cac6f4aa --- /dev/null +++ b/src/activities/reader/EpubReaderMenuActivity.cpp @@ -0,0 +1,100 @@ +#include "EpubReaderMenuActivity.h" +#include +#include "fontIds.h" + +void EpubReaderMenuActivity::onEnter() { + ActivityWithSubactivity::onEnter(); + renderingMutex = xSemaphoreCreateMutex(); + updateRequired = true; + + xTaskCreate(&EpubReaderMenuActivity::taskTrampoline, "EpubMenuTask", 4096, this, 1, &displayTaskHandle); +} + +void EpubReaderMenuActivity::onExit() { + ActivityWithSubactivity::onExit(); + xSemaphoreTake(renderingMutex, portMAX_DELAY); + if (displayTaskHandle) { + vTaskDelete(displayTaskHandle); + displayTaskHandle = nullptr; + } + vSemaphoreDelete(renderingMutex); + renderingMutex = nullptr; +} + +void EpubReaderMenuActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); + self->displayTaskLoop(); +} + +void EpubReaderMenuActivity::displayTaskLoop() { + while (true) { + if (updateRequired && !subActivity) { + updateRequired = false; + xSemaphoreTake(renderingMutex, portMAX_DELAY); + renderScreen(); + xSemaphoreGive(renderingMutex); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +void EpubReaderMenuActivity::loop() { + if (subActivity) { + subActivity->loop(); + return; + } + + // Use local variables for items we need to check after potential deletion + if (mappedInput.wasReleased(MappedInputManager::Button::Up) || + mappedInput.wasReleased(MappedInputManager::Button::Left) ) { + selectedIndex = (selectedIndex + menuItems.size() - 1) % menuItems.size(); + updateRequired = true; + } else if (mappedInput.wasReleased(MappedInputManager::Button::Down) || + mappedInput.wasReleased(MappedInputManager::Button::Right)) { + selectedIndex = (selectedIndex + 1) % menuItems.size(); + updateRequired = true; + } else if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + // 1. Capture the callback and action locally + auto actionCallback = onAction; + auto selectedAction = menuItems[selectedIndex].action; + + // 2. Execute the callback + actionCallback(selectedAction); + + // 3. CRITICAL: Return immediately. 'this' is likely deleted now. + return; + } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { + onBack(); + return; // Also return here just in case + } +} + +void EpubReaderMenuActivity::renderScreen() { + renderer.clearScreen(); + const auto pageWidth = renderer.getScreenWidth(); + + // Title + renderer.drawCenteredText(UI_12_FONT_ID, 20, "Reader Menu", true, EpdFontFamily::BOLD); + // renderer.fillRect(0, 60 + (selectedIndex % menuItems.size()) * 30 - 2, pageWidth - 1, 30); + + // Menu Items + constexpr int startY = 80; + constexpr int lineHeight = 40; + + for (size_t i = 0; i < menuItems.size(); ++i) { + const int displayY = startY + (i * lineHeight); + const bool isSelected = (static_cast(i) == selectedIndex); + + if (isSelected) { + renderer.fillRect(10, displayY - 5, pageWidth - 20, lineHeight, true); + } + + renderer.drawText(UI_12_FONT_ID, 30, displayY + 5, menuItems[i].label.c_str(), !isSelected); + } + + // Footer / Hints + const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down"); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + + renderer.displayBuffer(); +} diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h new file mode 100644 index 00000000..79ed4de5 --- /dev/null +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -0,0 +1,50 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#include "MappedInputManager.h" +#include "../ActivityWithSubactivity.h" + +class EpubReaderMenuActivity final : public ActivityWithSubactivity { + public: + enum class MenuAction { SELECT_CHAPTER, GO_HOME, DELETE_CACHE }; + + explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + const std::function& onBack, + const std::function& onAction) + : ActivityWithSubactivity("EpubReaderMenu", renderer, mappedInput), + onBack(onBack), + onAction(onAction) {} + + void onEnter() override; + void onExit() override; + void loop() override; + + private: + struct MenuItem { + MenuAction action; + std::string label; + }; + + const std::vector menuItems = { + {MenuAction::SELECT_CHAPTER, "Select Chapter"}, + {MenuAction::GO_HOME, "Go Home"}, + {MenuAction::DELETE_CACHE, "Delete Book Cache"}}; + + int selectedIndex = 0; + bool updateRequired = false; + TaskHandle_t displayTaskHandle = nullptr; + SemaphoreHandle_t renderingMutex = nullptr; + + const std::function onBack; + const std::function onAction; + + static void taskTrampoline(void* param); + [[noreturn]] void displayTaskLoop(); + void renderScreen(); +}; From de17b5da81033014c47cc26d6ec27e8aea82ffe7 Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Mon, 19 Jan 2026 22:46:45 +0100 Subject: [PATCH 2/8] Refactor saving progress to dedicated function. --- src/activities/reader/EpubReaderActivity.cpp | 48 +++++++++++--------- src/activities/reader/EpubReaderActivity.h | 1 + 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 8516f401..35f8b16e 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -191,31 +191,21 @@ void EpubReaderActivity::loop() { } case EpubReaderMenuActivity::MenuAction::DELETE_CACHE: { xSemaphoreTake(renderingMutex, portMAX_DELAY); - section.reset(); if (epub) { - // 2. BACKUP: Read current progress - // We use the current variables that track our position - uint16_t backupSpine = currentSpineIndex; - uint16_t backupPage = nextPageNumber; + // 2. BACKUP: Read current progress + // We use the current variables that track our position + uint16_t backupSpine = currentSpineIndex; + uint16_t backupPage = section->currentPage; - // 3. WIPE: Clear the cache directory - epub->clearCache(); + section.reset(); + // 3. WIPE: Clear the cache directory + epub->clearCache(); - // 4. RESTORE: Re-setup the directory and rewrite the progress file - epub->setupCacheDir(); + // 4. RESTORE: Re-setup the directory and rewrite the progress file + epub->setupCacheDir(); - FsFile f; - if (SdMan.openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) { - uint8_t data[4]; - data[0] = backupSpine & 0xFF; - data[1] = (backupSpine >> 8) & 0xFF; - data[2] = backupPage & 0xFF; - data[3] = (backupPage >> 8) & 0xFF; - f.write(data, 4); - f.close(); - Serial.println("[ERS] Progress restored after cache clear"); - } - } + saveProgress(backupSpine, backupPage); + } exitActivity(); updateRequired = true; xSemaphoreGive(renderingMutex); @@ -476,9 +466,13 @@ void EpubReaderActivity::renderScreen() { renderContents(std::move(p), orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft); Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start); } + saveProgress(currentSpineIndex, section->currentPage); +} +void EpubReaderActivity::saveProgress(int spineIndex, int page) { FsFile f; if (SdMan.openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) { +<<<<<<< HEAD uint8_t data[6]; data[0] = currentSpineIndex & 0xFF; data[1] = (currentSpineIndex >> 8) & 0xFF; @@ -487,10 +481,20 @@ void EpubReaderActivity::renderScreen() { data[4] = section->pageCount & 0xFF; data[5] = (section->pageCount >> 8) & 0xFF; f.write(data, 6); +======= + uint8_t data[4]; + data[0] = spineIndex & 0xFF; + data[1] = (spineIndex >> 8) & 0xFF; + data[2] = page & 0xFF; + data[3] = (page >> 8) & 0xFF; + f.write(data, 4); +>>>>>>> 95b7f80 (Refactor saving progress to dedicated function.) f.close(); + Serial.printf("[ERS] Progress saved: Chapter %d, Page %d\n", spineIndex, page); + } else { + Serial.printf("[ERS] Could not save progress!\n"); } } - void EpubReaderActivity::renderContents(std::unique_ptr page, const int orientedMarginTop, const int orientedMarginRight, const int orientedMarginBottom, const int orientedMarginLeft) { diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index ab4aff2d..2561be1f 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -27,6 +27,7 @@ class EpubReaderActivity final : public ActivityWithSubactivity { void renderContents(std::unique_ptr page, int orientedMarginTop, int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft); void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const; + void saveProgress(int spineIndex, int page); public: explicit EpubReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr epub, From f20cb1036579bbbcbe09812e747aecd24adb5de7 Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Tue, 20 Jan 2026 08:04:30 +0100 Subject: [PATCH 3/8] Formatting fixes --- .../reader/EpubReaderMenuActivity.cpp | 8 ++++--- .../reader/EpubReaderMenuActivity.h | 23 ++++++++----------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/activities/reader/EpubReaderMenuActivity.cpp b/src/activities/reader/EpubReaderMenuActivity.cpp index cac6f4aa..2503575c 100644 --- a/src/activities/reader/EpubReaderMenuActivity.cpp +++ b/src/activities/reader/EpubReaderMenuActivity.cpp @@ -1,5 +1,7 @@ #include "EpubReaderMenuActivity.h" + #include + #include "fontIds.h" void EpubReaderMenuActivity::onEnter() { @@ -46,11 +48,11 @@ void EpubReaderMenuActivity::loop() { // Use local variables for items we need to check after potential deletion if (mappedInput.wasReleased(MappedInputManager::Button::Up) || - mappedInput.wasReleased(MappedInputManager::Button::Left) ) { + mappedInput.wasReleased(MappedInputManager::Button::Left)) { selectedIndex = (selectedIndex + menuItems.size() - 1) % menuItems.size(); updateRequired = true; } else if (mappedInput.wasReleased(MappedInputManager::Button::Down) || - mappedInput.wasReleased(MappedInputManager::Button::Right)) { + mappedInput.wasReleased(MappedInputManager::Button::Right)) { selectedIndex = (selectedIndex + 1) % menuItems.size(); updateRequired = true; } else if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { @@ -65,7 +67,7 @@ void EpubReaderMenuActivity::loop() { return; } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { onBack(); - return; // Also return here just in case + return; // Also return here just in case } } diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h index 79ed4de5..f9ca7673 100644 --- a/src/activities/reader/EpubReaderMenuActivity.h +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -3,23 +3,21 @@ #include #include #include -#include -#include -#include -#include "MappedInputManager.h" +#include +#include +#include + #include "../ActivityWithSubactivity.h" +#include "MappedInputManager.h" class EpubReaderMenuActivity final : public ActivityWithSubactivity { public: enum class MenuAction { SELECT_CHAPTER, GO_HOME, DELETE_CACHE }; explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, - const std::function& onBack, - const std::function& onAction) - : ActivityWithSubactivity("EpubReaderMenu", renderer, mappedInput), - onBack(onBack), - onAction(onAction) {} + const std::function& onBack, const std::function& onAction) + : ActivityWithSubactivity("EpubReaderMenu", renderer, mappedInput), onBack(onBack), onAction(onAction) {} void onEnter() override; void onExit() override; @@ -31,10 +29,9 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity { std::string label; }; - const std::vector menuItems = { - {MenuAction::SELECT_CHAPTER, "Select Chapter"}, - {MenuAction::GO_HOME, "Go Home"}, - {MenuAction::DELETE_CACHE, "Delete Book Cache"}}; + const std::vector menuItems = {{MenuAction::SELECT_CHAPTER, "Select Chapter"}, + {MenuAction::GO_HOME, "Go Home"}, + {MenuAction::DELETE_CACHE, "Delete Book Cache"}}; int selectedIndex = 0; bool updateRequired = false; From 6e24b509ab8eea37dc019df98f633f1fca5bda06 Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Tue, 27 Jan 2026 15:48:36 +0100 Subject: [PATCH 4/8] Fixed merge f'up. --- src/activities/reader/EpubReaderActivity.cpp | 9 --------- .../reader/EpubReaderChapterSelectionActivity.cpp | 9 --------- 2 files changed, 18 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 35f8b16e..c3a23c5c 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -472,7 +472,6 @@ void EpubReaderActivity::renderScreen() { void EpubReaderActivity::saveProgress(int spineIndex, int page) { FsFile f; if (SdMan.openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) { -<<<<<<< HEAD uint8_t data[6]; data[0] = currentSpineIndex & 0xFF; data[1] = (currentSpineIndex >> 8) & 0xFF; @@ -481,14 +480,6 @@ void EpubReaderActivity::saveProgress(int spineIndex, int page) { data[4] = section->pageCount & 0xFF; data[5] = (section->pageCount >> 8) & 0xFF; f.write(data, 6); -======= - uint8_t data[4]; - data[0] = spineIndex & 0xFF; - data[1] = (spineIndex >> 8) & 0xFF; - data[2] = page & 0xFF; - data[3] = (page >> 8) & 0xFF; - f.write(data, 4); ->>>>>>> 95b7f80 (Refactor saving progress to dedicated function.) f.close(); Serial.printf("[ERS] Progress saved: Chapter %d, Page %d\n", spineIndex, page); } else { diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp index 43d792e4..1b35e143 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp @@ -188,11 +188,6 @@ void EpubReaderChapterSelectionActivity::renderScreen() { const auto pageStartIndex = selectorIndex / pageItems * pageItems; renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30); -<<<<<<< HEAD -======= - // for (int itemIndex = pageStartIndex; itemIndex < totalItems && itemIndex < pageStartIndex + pageItems; itemIndex++) - // { ->>>>>>> e6dd49d (Adding Ebook menu, go home, clear cache options.) for (int i = 0; i < pageItems; i++) { int itemIndex = pageStartIndex + i; if (itemIndex >= totalItems) break; @@ -209,10 +204,6 @@ void EpubReaderChapterSelectionActivity::renderScreen() { const std::string chapterName = renderer.truncatedText(UI_10_FONT_ID, item.title.c_str(), pageWidth - 40 - indentSize); -<<<<<<< HEAD -======= - // FIX: Use displayY here instead of recalculating based on tocIndex ->>>>>>> e6dd49d (Adding Ebook menu, go home, clear cache options.) renderer.drawText(UI_10_FONT_ID, indentSize, displayY, chapterName.c_str(), !isSelected); } } From 1cbe081a597557015223f8d6f292f5c5c36e52d7 Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Tue, 27 Jan 2026 16:13:59 +0100 Subject: [PATCH 5/8] Moved callbacks into private functions. --- src/activities/reader/EpubReaderActivity.cpp | 167 ++++++++++--------- src/activities/reader/EpubReaderActivity.h | 3 + 2 files changed, 87 insertions(+), 83 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index c3a23c5c..2981dd1f 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -8,7 +8,6 @@ #include "CrossPointSettings.h" #include "CrossPointState.h" #include "EpubReaderChapterSelectionActivity.h" -#include "EpubReaderMenuActivity.h" #include "MappedInputManager.h" #include "RecentBooksStore.h" #include "ScreenComponents.h" @@ -132,88 +131,8 @@ void EpubReaderActivity::loop() { const int totalPages = section ? section->pageCount : 0; exitActivity(); enterNewActivity(new EpubReaderMenuActivity( - this->renderer, this->mappedInput, - [this]() { // On Back - exitActivity(); - updateRequired = true; - }, - [this](EpubReaderMenuActivity::MenuAction action) { // On Select - switch (action) { - case EpubReaderMenuActivity::MenuAction::SELECT_CHAPTER: { - // Calculate values BEFORE we start destroying things - const int currentP = section ? section->currentPage : 0; - const int totalP = section ? section->pageCount : 0; - const int spineIdx = currentSpineIndex; - const std::string path = epub->getPath(); - - xSemaphoreTake(renderingMutex, portMAX_DELAY); - - // 1. Close the menu - exitActivity(); - - // 2. Open the Chapter Selector - enterNewActivity(new EpubReaderChapterSelectionActivity( - this->renderer, this->mappedInput, epub, path, spineIdx, currentP, totalP, - [this] { - exitActivity(); - updateRequired = true; - }, - [this](const int newSpineIndex) { - if (currentSpineIndex != newSpineIndex) { - currentSpineIndex = newSpineIndex; - nextPageNumber = 0; - section.reset(); - } - exitActivity(); - updateRequired = true; - }, - [this](const int newSpineIndex, const int newPage) { - if (currentSpineIndex != newSpineIndex || (section && section->currentPage != newPage)) { - currentSpineIndex = newSpineIndex; - nextPageNumber = newPage; - section.reset(); - } - exitActivity(); - updateRequired = true; - })); - - xSemaphoreGive(renderingMutex); - break; - break; - } - case EpubReaderMenuActivity::MenuAction::GO_HOME: { - // 2. Trigger the reader's "Go Home" callback - if (onGoHome) { - onGoHome(); - } - - break; - } - case EpubReaderMenuActivity::MenuAction::DELETE_CACHE: { - xSemaphoreTake(renderingMutex, portMAX_DELAY); - if (epub) { - // 2. BACKUP: Read current progress - // We use the current variables that track our position - uint16_t backupSpine = currentSpineIndex; - uint16_t backupPage = section->currentPage; - - section.reset(); - // 3. WIPE: Clear the cache directory - epub->clearCache(); - - // 4. RESTORE: Re-setup the directory and rewrite the progress file - epub->setupCacheDir(); - - saveProgress(backupSpine, backupPage); - } - exitActivity(); - updateRequired = true; - xSemaphoreGive(renderingMutex); - if (onGoHome) onGoHome(); - break; - } - } - })); + this->renderer, this->mappedInput, [this]() { onReaderMenuBack(); }, + [this](EpubReaderMenuActivity::MenuAction action) { onReaderMenuConfirm(action); })); xSemaphoreGive(renderingMutex); } @@ -301,6 +220,88 @@ void EpubReaderActivity::loop() { } } +void EpubReaderActivity::onReaderMenuBack() { + exitActivity(); + updateRequired = true; +} + +void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action) { + switch (action) { + case EpubReaderMenuActivity::MenuAction::SELECT_CHAPTER: { + // Calculate values BEFORE we start destroying things + const int currentP = section ? section->currentPage : 0; + const int totalP = section ? section->pageCount : 0; + const int spineIdx = currentSpineIndex; + const std::string path = epub->getPath(); + + xSemaphoreTake(renderingMutex, portMAX_DELAY); + + // 1. Close the menu + exitActivity(); + + // 2. Open the Chapter Selector + enterNewActivity(new EpubReaderChapterSelectionActivity( + this->renderer, this->mappedInput, epub, path, spineIdx, currentP, totalP, + [this] { + exitActivity(); + updateRequired = true; + }, + [this](const int newSpineIndex) { + if (currentSpineIndex != newSpineIndex) { + currentSpineIndex = newSpineIndex; + nextPageNumber = 0; + section.reset(); + } + exitActivity(); + updateRequired = true; + }, + [this](const int newSpineIndex, const int newPage) { + if (currentSpineIndex != newSpineIndex || (section && section->currentPage != newPage)) { + currentSpineIndex = newSpineIndex; + nextPageNumber = newPage; + section.reset(); + } + exitActivity(); + updateRequired = true; + })); + + xSemaphoreGive(renderingMutex); + break; + } + case EpubReaderMenuActivity::MenuAction::GO_HOME: { + // 2. Trigger the reader's "Go Home" callback + if (onGoHome) { + onGoHome(); + } + + break; + } + case EpubReaderMenuActivity::MenuAction::DELETE_CACHE: { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + if (epub) { + // 2. BACKUP: Read current progress + // We use the current variables that track our position + uint16_t backupSpine = currentSpineIndex; + uint16_t backupPage = section->currentPage; + + section.reset(); + // 3. WIPE: Clear the cache directory + epub->clearCache(); + + // 4. RESTORE: Re-setup the directory and rewrite the progress file + epub->setupCacheDir(); + + saveProgress(backupSpine, backupPage); + } + exitActivity(); + updateRequired = true; + xSemaphoreGive(renderingMutex); + if (onGoHome) onGoHome(); + break; + } + } +} + void EpubReaderActivity::displayTaskLoop() { while (true) { if (updateRequired) { diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index 2561be1f..88416571 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -5,6 +5,7 @@ #include #include +#include "EpubReaderMenuActivity.h" #include "activities/ActivityWithSubactivity.h" class EpubReaderActivity final : public ActivityWithSubactivity { @@ -28,6 +29,8 @@ class EpubReaderActivity final : public ActivityWithSubactivity { int orientedMarginBottom, int orientedMarginLeft); void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const; void saveProgress(int spineIndex, int page); + void onReaderMenuBack(); + void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action); public: explicit EpubReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr epub, From c51eb6a71fd227b4cccac3c0fe4814acb4eefc54 Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Tue, 27 Jan 2026 18:12:34 +0100 Subject: [PATCH 6/8] Unified styling (matching settings screen). --- src/activities/reader/EpubReaderActivity.cpp | 2 +- .../reader/EpubReaderChapterSelectionActivity.cpp | 4 +--- src/activities/reader/EpubReaderMenuActivity.cpp | 13 +++++++------ src/activities/reader/EpubReaderMenuActivity.h | 8 ++++++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 2981dd1f..4cd09d28 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -131,7 +131,7 @@ void EpubReaderActivity::loop() { const int totalPages = section ? section->pageCount : 0; exitActivity(); enterNewActivity(new EpubReaderMenuActivity( - this->renderer, this->mappedInput, [this]() { onReaderMenuBack(); }, + this->renderer, this->mappedInput, epub->getTitle(), [this]() { onReaderMenuBack(); }, [this](EpubReaderMenuActivity::MenuAction action) { onReaderMenuConfirm(action); })); xSemaphoreGive(renderingMutex); } diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp index 1b35e143..614227de 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp @@ -181,9 +181,7 @@ void EpubReaderChapterSelectionActivity::renderScreen() { const int pageItems = getPageItems(); const int totalItems = getTotalItems(); - const std::string title = - renderer.truncatedText(UI_12_FONT_ID, epub->getTitle().c_str(), pageWidth - 40, EpdFontFamily::BOLD); - renderer.drawCenteredText(UI_12_FONT_ID, 15, title.c_str(), true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 15, "Go to Chapter", true, EpdFontFamily::BOLD); const auto pageStartIndex = selectorIndex / pageItems * pageItems; renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30); diff --git a/src/activities/reader/EpubReaderMenuActivity.cpp b/src/activities/reader/EpubReaderMenuActivity.cpp index 2503575c..5ce4881d 100644 --- a/src/activities/reader/EpubReaderMenuActivity.cpp +++ b/src/activities/reader/EpubReaderMenuActivity.cpp @@ -76,22 +76,23 @@ void EpubReaderMenuActivity::renderScreen() { const auto pageWidth = renderer.getScreenWidth(); // Title - renderer.drawCenteredText(UI_12_FONT_ID, 20, "Reader Menu", true, EpdFontFamily::BOLD); - // renderer.fillRect(0, 60 + (selectedIndex % menuItems.size()) * 30 - 2, pageWidth - 1, 30); + const std::string truncTitle = + renderer.truncatedText(UI_12_FONT_ID, title.c_str(), pageWidth - 40, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 15, truncTitle.c_str(), true, EpdFontFamily::BOLD); // Menu Items - constexpr int startY = 80; - constexpr int lineHeight = 40; + constexpr int startY = 60; + constexpr int lineHeight = 30; for (size_t i = 0; i < menuItems.size(); ++i) { const int displayY = startY + (i * lineHeight); const bool isSelected = (static_cast(i) == selectedIndex); if (isSelected) { - renderer.fillRect(10, displayY - 5, pageWidth - 20, lineHeight, true); + renderer.fillRect(0, displayY, pageWidth - 1, lineHeight, true); } - renderer.drawText(UI_12_FONT_ID, 30, displayY + 5, menuItems[i].label.c_str(), !isSelected); + renderer.drawText(UI_10_FONT_ID, 20, displayY, menuItems[i].label.c_str(), !isSelected); } // Footer / Hints diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h index f9ca7673..471c620e 100644 --- a/src/activities/reader/EpubReaderMenuActivity.h +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -15,9 +15,12 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity { public: enum class MenuAction { SELECT_CHAPTER, GO_HOME, DELETE_CACHE }; - explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title, const std::function& onBack, const std::function& onAction) - : ActivityWithSubactivity("EpubReaderMenu", renderer, mappedInput), onBack(onBack), onAction(onAction) {} + : ActivityWithSubactivity("EpubReaderMenu", renderer, mappedInput), + title(title), + onBack(onBack), + onAction(onAction) {} void onEnter() override; void onExit() override; @@ -37,6 +40,7 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity { bool updateRequired = false; TaskHandle_t displayTaskHandle = nullptr; SemaphoreHandle_t renderingMutex = nullptr; + std::string title = "Reader Menu"; const std::function onBack; const std::function onAction; From 3312b72d9d6dd35d1bef49ef49deeb77a0968c1f Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Tue, 27 Jan 2026 18:13:54 +0100 Subject: [PATCH 7/8] Unified action. --- src/activities/reader/EpubReaderMenuActivity.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h index 471c620e..bd253f81 100644 --- a/src/activities/reader/EpubReaderMenuActivity.h +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -32,7 +32,7 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity { std::string label; }; - const std::vector menuItems = {{MenuAction::SELECT_CHAPTER, "Select Chapter"}, + const std::vector menuItems = {{MenuAction::SELECT_CHAPTER, "Go to Chapter"}, {MenuAction::GO_HOME, "Go Home"}, {MenuAction::DELETE_CACHE, "Delete Book Cache"}}; From 90d459f2c0711a44d927a365fbda803f6ce611fe Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Tue, 27 Jan 2026 19:14:06 +0100 Subject: [PATCH 8/8] Fix crash due to use of reset section. --- src/activities/reader/EpubReaderActivity.cpp | 17 +++++++++-------- src/activities/reader/EpubReaderActivity.h | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 4cd09d28..a6ec3367 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -283,6 +283,7 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction // We use the current variables that track our position uint16_t backupSpine = currentSpineIndex; uint16_t backupPage = section->currentPage; + uint16_t backupPageCount = section->pageCount; section.reset(); // 3. WIPE: Clear the cache directory @@ -291,7 +292,7 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction // 4. RESTORE: Re-setup the directory and rewrite the progress file epub->setupCacheDir(); - saveProgress(backupSpine, backupPage); + saveProgress(backupSpine, backupPage, backupPageCount); } exitActivity(); updateRequired = true; @@ -467,22 +468,22 @@ void EpubReaderActivity::renderScreen() { renderContents(std::move(p), orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft); Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start); } - saveProgress(currentSpineIndex, section->currentPage); + saveProgress(currentSpineIndex, section->currentPage, section->pageCount); } -void EpubReaderActivity::saveProgress(int spineIndex, int page) { +void EpubReaderActivity::saveProgress(int spineIndex, int currentPage, int pageCount) { FsFile f; if (SdMan.openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) { uint8_t data[6]; data[0] = currentSpineIndex & 0xFF; data[1] = (currentSpineIndex >> 8) & 0xFF; - data[2] = section->currentPage & 0xFF; - data[3] = (section->currentPage >> 8) & 0xFF; - data[4] = section->pageCount & 0xFF; - data[5] = (section->pageCount >> 8) & 0xFF; + data[2] = currentPage & 0xFF; + data[3] = (currentPage >> 8) & 0xFF; + data[4] = pageCount & 0xFF; + data[5] = (pageCount >> 8) & 0xFF; f.write(data, 6); f.close(); - Serial.printf("[ERS] Progress saved: Chapter %d, Page %d\n", spineIndex, page); + Serial.printf("[ERS] Progress saved: Chapter %d, Page %d\n", spineIndex, currentPage); } else { Serial.printf("[ERS] Could not save progress!\n"); } diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index 88416571..ca7c0dc9 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -28,7 +28,7 @@ class EpubReaderActivity final : public ActivityWithSubactivity { void renderContents(std::unique_ptr page, int orientedMarginTop, int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft); void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const; - void saveProgress(int spineIndex, int page); + void saveProgress(int spineIndex, int currentPage, int pageCount); void onReaderMenuBack(); void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action);