crosspoint-reader/src/activities/reader/ReaderActivity.cpp
Eunchurn Park b77af16caa
Add Continue Reading menu and remember last book folder (#129)
## Summary

* **What is the goal of this PR?**

Add a "Continue Reading" feature to improve user experience when
returning to a previously opened book.

* **What changes are included?**

- Add dynamic "Continue: <book name>" menu item in Home screen when a
book was previously opened

- File browser now starts from the folder of the last opened book
instead of always starting from root directory
- Menu dynamically shows 3 or 4 items based on reading history:
  - Without history: `Browse`, `File transfer`, `Settings`
- With history: `Continue: <book>`, `Browse`, `File transfer`,
`Settings`

## Additional Context

* This feature leverages the existing `APP_STATE.openEpubPath` which
already persists the last opened book path
* The Continue Reading menu only appears if the book file still exists
on the SD card
* Book name in the menu is truncated to 25 characters with "..." suffix
if too long
* If the last book's folder was deleted, the file browser gracefully
falls back to root directory
* No new dependencies or significant memory overhead - reuses existing
state management
2025-12-26 11:55:23 +11:00

84 lines
2.5 KiB
C++

#include "ReaderActivity.h"
#include <SD.h>
#include "Epub.h"
#include "EpubReaderActivity.h"
#include "FileSelectionActivity.h"
#include "activities/util/FullScreenMessageActivity.h"
std::string ReaderActivity::extractFolderPath(const std::string& filePath) {
const auto lastSlash = filePath.find_last_of('/');
if (lastSlash == std::string::npos || lastSlash == 0) {
return "/";
}
return filePath.substr(0, lastSlash);
}
std::unique_ptr<Epub> ReaderActivity::loadEpub(const std::string& path) {
if (!SD.exists(path.c_str())) {
Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str());
return nullptr;
}
auto epub = std::unique_ptr<Epub>(new Epub(path, "/.crosspoint"));
if (epub->load()) {
return epub;
}
Serial.printf("[%lu] [ ] Failed to load epub\n", millis());
return nullptr;
}
void ReaderActivity::onSelectEpubFile(const std::string& path) {
currentEpubPath = path; // Track current book path
exitActivity();
enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "Loading..."));
auto epub = loadEpub(path);
if (epub) {
onGoToEpubReader(std::move(epub));
} else {
exitActivity();
enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "Failed to load epub", REGULAR,
EInkDisplay::HALF_REFRESH));
delay(2000);
onGoToFileSelection();
}
}
void ReaderActivity::onGoToFileSelection(const std::string& fromEpubPath) {
exitActivity();
// If coming from a book, start in that book's folder; otherwise start from root
const auto initialPath = fromEpubPath.empty() ? "/" : extractFolderPath(fromEpubPath);
enterNewActivity(new FileSelectionActivity(
renderer, inputManager, [this](const std::string& path) { onSelectEpubFile(path); }, onGoBack, initialPath));
}
void ReaderActivity::onGoToEpubReader(std::unique_ptr<Epub> epub) {
const auto epubPath = epub->getPath();
currentEpubPath = epubPath;
exitActivity();
enterNewActivity(new EpubReaderActivity(
renderer, inputManager, std::move(epub), [this, epubPath] { onGoToFileSelection(epubPath); },
[this] { onGoBack(); }));
}
void ReaderActivity::onEnter() {
ActivityWithSubactivity::onEnter();
if (initialEpubPath.empty()) {
onGoToFileSelection(); // Start from root when entering via Browse
return;
}
currentEpubPath = initialEpubPath;
auto epub = loadEpub(initialEpubPath);
if (!epub) {
onGoBack();
return;
}
onGoToEpubReader(std::move(epub));
}