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(); +};