mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 14:47:37 +03:00
feat: Add reading menu and delete cache function (#433)
## Summary * Adds a menu in the Epub reader * The Chapter selection is moved there to pos 1 (so it can be reached by double tapping the confirm button) * A Go Home is there, too * Most significantly, a function "Delete Book Cache" is added. This returns to main (to avoid directly rebuilding cached items, eg. if this is used to debug/develop other areas - and it's also easier ;)) Probably, the Sync function could now be moved from the Chapter selection to this menu, too. --- ### 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**_
This commit is contained in:
parent
da4d3b5ea5
commit
f935b59a41
@ -130,31 +130,9 @@ void EpubReaderActivity::loop() {
|
|||||||
const int currentPage = section ? section->currentPage : 0;
|
const int currentPage = section ? section->currentPage : 0;
|
||||||
const int totalPages = section ? section->pageCount : 0;
|
const int totalPages = section ? section->pageCount : 0;
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new EpubReaderChapterSelectionActivity(
|
enterNewActivity(new EpubReaderMenuActivity(
|
||||||
this->renderer, this->mappedInput, epub, epub->getPath(), currentSpineIndex, currentPage, totalPages,
|
this->renderer, this->mappedInput, epub->getTitle(), [this]() { onReaderMenuBack(); },
|
||||||
[this] {
|
[this](EpubReaderMenuActivity::MenuAction action) { onReaderMenuConfirm(action); }));
|
||||||
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) {
|
|
||||||
// Handle sync position
|
|
||||||
if (currentSpineIndex != newSpineIndex || (section && section->currentPage != newPage)) {
|
|
||||||
currentSpineIndex = newSpineIndex;
|
|
||||||
nextPageNumber = newPage;
|
|
||||||
section.reset();
|
|
||||||
}
|
|
||||||
exitActivity();
|
|
||||||
updateRequired = true;
|
|
||||||
}));
|
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,6 +220,89 @@ 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;
|
||||||
|
uint16_t backupPageCount = section->pageCount;
|
||||||
|
|
||||||
|
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, backupPageCount);
|
||||||
|
}
|
||||||
|
exitActivity();
|
||||||
|
updateRequired = true;
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
if (onGoHome) onGoHome();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void EpubReaderActivity::displayTaskLoop() {
|
void EpubReaderActivity::displayTaskLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired) {
|
if (updateRequired) {
|
||||||
@ -407,21 +468,26 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
renderContents(std::move(p), orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
renderContents(std::move(p), orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||||
Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start);
|
Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start);
|
||||||
}
|
}
|
||||||
|
saveProgress(currentSpineIndex, section->currentPage, section->pageCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EpubReaderActivity::saveProgress(int spineIndex, int currentPage, int pageCount) {
|
||||||
FsFile f;
|
FsFile f;
|
||||||
if (SdMan.openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) {
|
if (SdMan.openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) {
|
||||||
uint8_t data[6];
|
uint8_t data[6];
|
||||||
data[0] = currentSpineIndex & 0xFF;
|
data[0] = currentSpineIndex & 0xFF;
|
||||||
data[1] = (currentSpineIndex >> 8) & 0xFF;
|
data[1] = (currentSpineIndex >> 8) & 0xFF;
|
||||||
data[2] = section->currentPage & 0xFF;
|
data[2] = currentPage & 0xFF;
|
||||||
data[3] = (section->currentPage >> 8) & 0xFF;
|
data[3] = (currentPage >> 8) & 0xFF;
|
||||||
data[4] = section->pageCount & 0xFF;
|
data[4] = pageCount & 0xFF;
|
||||||
data[5] = (section->pageCount >> 8) & 0xFF;
|
data[5] = (pageCount >> 8) & 0xFF;
|
||||||
f.write(data, 6);
|
f.write(data, 6);
|
||||||
f.close();
|
f.close();
|
||||||
|
Serial.printf("[ERS] Progress saved: Chapter %d, Page %d\n", spineIndex, currentPage);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[ERS] Could not save progress!\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int orientedMarginTop,
|
void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int orientedMarginTop,
|
||||||
const int orientedMarginRight, const int orientedMarginBottom,
|
const int orientedMarginRight, const int orientedMarginBottom,
|
||||||
const int orientedMarginLeft) {
|
const int orientedMarginLeft) {
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
#include <freertos/semphr.h>
|
#include <freertos/semphr.h>
|
||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include "EpubReaderMenuActivity.h"
|
||||||
#include "activities/ActivityWithSubactivity.h"
|
#include "activities/ActivityWithSubactivity.h"
|
||||||
|
|
||||||
class EpubReaderActivity final : public ActivityWithSubactivity {
|
class EpubReaderActivity final : public ActivityWithSubactivity {
|
||||||
@ -27,6 +28,9 @@ class EpubReaderActivity final : public ActivityWithSubactivity {
|
|||||||
void renderContents(std::unique_ptr<Page> page, int orientedMarginTop, int orientedMarginRight,
|
void renderContents(std::unique_ptr<Page> page, int orientedMarginTop, int orientedMarginRight,
|
||||||
int orientedMarginBottom, int orientedMarginLeft);
|
int orientedMarginBottom, int orientedMarginLeft);
|
||||||
void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const;
|
void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const;
|
||||||
|
void saveProgress(int spineIndex, int currentPage, int pageCount);
|
||||||
|
void onReaderMenuBack();
|
||||||
|
void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EpubReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr<Epub> epub,
|
explicit EpubReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr<Epub> epub,
|
||||||
|
|||||||
@ -181,9 +181,7 @@ void EpubReaderChapterSelectionActivity::renderScreen() {
|
|||||||
const int pageItems = getPageItems();
|
const int pageItems = getPageItems();
|
||||||
const int totalItems = getTotalItems();
|
const int totalItems = getTotalItems();
|
||||||
|
|
||||||
const std::string title =
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Go to Chapter", true, EpdFontFamily::BOLD);
|
||||||
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);
|
|
||||||
|
|
||||||
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
|
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
|
||||||
renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30);
|
renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30);
|
||||||
|
|||||||
103
src/activities/reader/EpubReaderMenuActivity.cpp
Normal file
103
src/activities/reader/EpubReaderMenuActivity.cpp
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#include "EpubReaderMenuActivity.h"
|
||||||
|
|
||||||
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
|
#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<EpubReaderMenuActivity*>(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
|
||||||
|
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 = 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<int>(i) == selectedIndex);
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
renderer.fillRect(0, displayY, pageWidth - 1, lineHeight, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.drawText(UI_10_FONT_ID, 20, displayY, 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();
|
||||||
|
}
|
||||||
51
src/activities/reader/EpubReaderMenuActivity.h
Normal file
51
src/activities/reader/EpubReaderMenuActivity.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Epub.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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::string& title,
|
||||||
|
const std::function<void()>& onBack, const std::function<void(MenuAction)>& onAction)
|
||||||
|
: ActivityWithSubactivity("EpubReaderMenu", renderer, mappedInput),
|
||||||
|
title(title),
|
||||||
|
onBack(onBack),
|
||||||
|
onAction(onAction) {}
|
||||||
|
|
||||||
|
void onEnter() override;
|
||||||
|
void onExit() override;
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct MenuItem {
|
||||||
|
MenuAction action;
|
||||||
|
std::string label;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<MenuItem> menuItems = {{MenuAction::SELECT_CHAPTER, "Go to 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;
|
||||||
|
std::string title = "Reader Menu";
|
||||||
|
|
||||||
|
const std::function<void()> onBack;
|
||||||
|
const std::function<void(MenuAction)> onAction;
|
||||||
|
|
||||||
|
static void taskTrampoline(void* param);
|
||||||
|
[[noreturn]] void displayTaskLoop();
|
||||||
|
void renderScreen();
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user