diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index 68e64b3..38dc854 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -4,22 +4,24 @@ #include #include +#include "CrossPointState.h" #include "config.h" -namespace { -constexpr int menuItemCount = 3; -} - void HomeActivity::taskTrampoline(void* param) { auto* self = static_cast(param); self->displayTaskLoop(); } +int HomeActivity::getMenuItemCount() const { return hasContinueReading ? 4 : 3; } + void HomeActivity::onEnter() { Activity::onEnter(); renderingMutex = xSemaphoreCreateMutex(); + // Check if we have a book to continue reading + hasContinueReading = !APP_STATE.openEpubPath.empty() && SD.exists(APP_STATE.openEpubPath.c_str()); + selectorIndex = 0; // Trigger first update @@ -52,19 +54,35 @@ void HomeActivity::loop() { const bool nextPressed = inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT); + const int menuCount = getMenuItemCount(); + if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { - if (selectorIndex == 0) { - onReaderOpen(); - } else if (selectorIndex == 1) { - onFileTransferOpen(); - } else if (selectorIndex == 2) { - onSettingsOpen(); + if (hasContinueReading) { + // Menu: Continue Reading, Browse, File transfer, Settings + if (selectorIndex == 0) { + onContinueReading(); + } else if (selectorIndex == 1) { + onReaderOpen(); + } else if (selectorIndex == 2) { + onFileTransferOpen(); + } else if (selectorIndex == 3) { + onSettingsOpen(); + } + } else { + // Menu: Browse, File transfer, Settings + if (selectorIndex == 0) { + onReaderOpen(); + } else if (selectorIndex == 1) { + onFileTransferOpen(); + } else if (selectorIndex == 2) { + onSettingsOpen(); + } } } else if (prevPressed) { - selectorIndex = (selectorIndex + menuItemCount - 1) % menuItemCount; + selectorIndex = (selectorIndex + menuCount - 1) % menuCount; updateRequired = true; } else if (nextPressed) { - selectorIndex = (selectorIndex + 1) % menuItemCount; + selectorIndex = (selectorIndex + 1) % menuCount; updateRequired = true; } } @@ -89,9 +107,41 @@ void HomeActivity::render() const { // Draw selection renderer.fillRect(0, 60 + selectorIndex * 30 + 2, pageWidth - 1, 30); - renderer.drawText(UI_FONT_ID, 20, 60, "Read", selectorIndex != 0); - renderer.drawText(UI_FONT_ID, 20, 90, "File transfer", selectorIndex != 1); - renderer.drawText(UI_FONT_ID, 20, 120, "Settings", selectorIndex != 2); + + int menuY = 60; + int menuIndex = 0; + + if (hasContinueReading) { + // Extract filename from path for display + std::string bookName = APP_STATE.openEpubPath; + const size_t lastSlash = bookName.find_last_of('/'); + if (lastSlash != std::string::npos) { + bookName = bookName.substr(lastSlash + 1); + } + // Remove .epub extension + if (bookName.length() > 5 && bookName.substr(bookName.length() - 5) == ".epub") { + bookName.resize(bookName.length() - 5); + } + // Truncate if too long + if (bookName.length() > 25) { + bookName.resize(22); + bookName += "..."; + } + std::string continueLabel = "Continue: " + bookName; + renderer.drawText(UI_FONT_ID, 20, menuY, continueLabel.c_str(), selectorIndex != menuIndex); + menuY += 30; + menuIndex++; + } + + renderer.drawText(UI_FONT_ID, 20, menuY, "Browse", selectorIndex != menuIndex); + menuY += 30; + menuIndex++; + + renderer.drawText(UI_FONT_ID, 20, menuY, "File transfer", selectorIndex != menuIndex); + menuY += 30; + menuIndex++; + + renderer.drawText(UI_FONT_ID, 20, menuY, "Settings", selectorIndex != menuIndex); renderer.drawButtonHints(UI_FONT_ID, "Back", "Confirm", "Left", "Right"); diff --git a/src/activities/home/HomeActivity.h b/src/activities/home/HomeActivity.h index 943a466..0704819 100644 --- a/src/activities/home/HomeActivity.h +++ b/src/activities/home/HomeActivity.h @@ -12,6 +12,8 @@ class HomeActivity final : public Activity { SemaphoreHandle_t renderingMutex = nullptr; int selectorIndex = 0; bool updateRequired = false; + bool hasContinueReading = false; + const std::function onContinueReading; const std::function onReaderOpen; const std::function onSettingsOpen; const std::function onFileTransferOpen; @@ -19,11 +21,14 @@ class HomeActivity final : public Activity { static void taskTrampoline(void* param); [[noreturn]] void displayTaskLoop(); void render() const; + int getMenuItemCount() const; public: - explicit HomeActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function& onReaderOpen, + explicit HomeActivity(GfxRenderer& renderer, InputManager& inputManager, + const std::function& onContinueReading, const std::function& onReaderOpen, const std::function& onSettingsOpen, const std::function& onFileTransferOpen) : Activity("Home", renderer, inputManager), + onContinueReading(onContinueReading), onReaderOpen(onReaderOpen), onSettingsOpen(onSettingsOpen), onFileTransferOpen(onFileTransferOpen) {} diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 6195ec2..f4905d6 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -14,6 +14,7 @@ namespace { constexpr int pagesPerRefresh = 15; constexpr unsigned long skipChapterMs = 700; +constexpr unsigned long goHomeMs = 1000; constexpr float lineCompression = 0.95f; constexpr int marginTop = 8; constexpr int marginRight = 10; @@ -108,7 +109,14 @@ void EpubReaderActivity::loop() { xSemaphoreGive(renderingMutex); } - if (inputManager.wasPressed(InputManager::BTN_BACK)) { + // Long press BACK (1s+) goes directly to home + if (inputManager.isPressed(InputManager::BTN_BACK) && inputManager.getHeldTime() >= goHomeMs) { + onGoHome(); + return; + } + + // Short press BACK goes to file selection + if (inputManager.wasReleased(InputManager::BTN_BACK) && inputManager.getHeldTime() < goHomeMs) { onGoBack(); return; } diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index 4edbabc..143f56b 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -17,6 +17,7 @@ class EpubReaderActivity final : public ActivityWithSubactivity { int pagesUntilFullRefresh = 0; bool updateRequired = false; const std::function onGoBack; + const std::function onGoHome; static void taskTrampoline(void* param); [[noreturn]] void displayTaskLoop(); @@ -26,8 +27,11 @@ class EpubReaderActivity final : public ActivityWithSubactivity { public: explicit EpubReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::unique_ptr epub, - const std::function& onGoBack) - : ActivityWithSubactivity("EpubReader", renderer, inputManager), epub(std::move(epub)), onGoBack(onGoBack) {} + const std::function& onGoBack, const std::function& onGoHome) + : ActivityWithSubactivity("EpubReader", renderer, inputManager), + epub(std::move(epub)), + onGoBack(onGoBack), + onGoHome(onGoHome) {} void onEnter() override; void onExit() override; void loop() override; diff --git a/src/activities/reader/FileSelectionActivity.cpp b/src/activities/reader/FileSelectionActivity.cpp index d6504b8..853b06f 100644 --- a/src/activities/reader/FileSelectionActivity.cpp +++ b/src/activities/reader/FileSelectionActivity.cpp @@ -9,6 +9,7 @@ namespace { constexpr int PAGE_ITEMS = 23; constexpr int SKIP_PAGE_MS = 700; +constexpr unsigned long GO_HOME_MS = 1000; } // namespace void sortFileList(std::vector& strs) { @@ -53,7 +54,7 @@ void FileSelectionActivity::onEnter() { renderingMutex = xSemaphoreCreateMutex(); - basepath = "/"; + // basepath is set via constructor parameter (defaults to "/" if not specified) loadFiles(); selectorIndex = 0; @@ -83,6 +84,16 @@ void FileSelectionActivity::onExit() { } void FileSelectionActivity::loop() { + // Long press BACK (1s+) goes to root folder + if (inputManager.isPressed(InputManager::BTN_BACK) && inputManager.getHeldTime() >= GO_HOME_MS) { + if (basepath != "/") { + basepath = "/"; + loadFiles(); + updateRequired = true; + } + return; + } + const bool prevReleased = inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT); const bool nextReleased = @@ -103,15 +114,17 @@ void FileSelectionActivity::loop() { } else { onSelect(basepath + files[selectorIndex]); } - } else if (inputManager.wasPressed(InputManager::BTN_BACK)) { - if (basepath != "/") { - basepath.replace(basepath.find_last_of('/'), std::string::npos, ""); - if (basepath.empty()) basepath = "/"; - loadFiles(); - updateRequired = true; - } else { - // At root level, go back home - onGoHome(); + } else if (inputManager.wasReleased(InputManager::BTN_BACK)) { + // Short press: go up one directory, or go home if at root + if (inputManager.getHeldTime() < GO_HOME_MS) { + if (basepath != "/") { + basepath.replace(basepath.find_last_of('/'), std::string::npos, ""); + if (basepath.empty()) basepath = "/"; + loadFiles(); + updateRequired = true; + } else { + onGoHome(); + } } } else if (prevReleased) { if (skipPage) { diff --git a/src/activities/reader/FileSelectionActivity.h b/src/activities/reader/FileSelectionActivity.h index 2a8f8ae..f642e20 100644 --- a/src/activities/reader/FileSelectionActivity.h +++ b/src/activities/reader/FileSelectionActivity.h @@ -27,8 +27,11 @@ class FileSelectionActivity final : public Activity { public: explicit FileSelectionActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function& onSelect, - const std::function& onGoHome) - : Activity("FileSelection", renderer, inputManager), onSelect(onSelect), onGoHome(onGoHome) {} + const std::function& onGoHome, std::string initialPath = "/") + : Activity("FileSelection", renderer, inputManager), + basepath(initialPath.empty() ? "/" : std::move(initialPath)), + onSelect(onSelect), + onGoHome(onGoHome) {} void onEnter() override; void onExit() override; void loop() override; diff --git a/src/activities/reader/ReaderActivity.cpp b/src/activities/reader/ReaderActivity.cpp index d888fb6..519a33a 100644 --- a/src/activities/reader/ReaderActivity.cpp +++ b/src/activities/reader/ReaderActivity.cpp @@ -7,6 +7,14 @@ #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 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()); @@ -23,6 +31,7 @@ std::unique_ptr ReaderActivity::loadEpub(const std::string& path) { } void ReaderActivity::onSelectEpubFile(const std::string& path) { + currentEpubPath = path; // Track current book path exitActivity(); enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "Loading...")); @@ -38,25 +47,32 @@ void ReaderActivity::onSelectEpubFile(const std::string& path) { } } -void ReaderActivity::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)); + renderer, inputManager, [this](const std::string& path) { onSelectEpubFile(path); }, onGoBack, initialPath)); } void ReaderActivity::onGoToEpubReader(std::unique_ptr epub) { + const auto epubPath = epub->getPath(); + currentEpubPath = epubPath; exitActivity(); - enterNewActivity(new EpubReaderActivity(renderer, inputManager, std::move(epub), [this] { onGoToFileSelection(); })); + enterNewActivity(new EpubReaderActivity( + renderer, inputManager, std::move(epub), [this, epubPath] { onGoToFileSelection(epubPath); }, + [this] { onGoBack(); })); } void ReaderActivity::onEnter() { ActivityWithSubactivity::onEnter(); if (initialEpubPath.empty()) { - onGoToFileSelection(); + onGoToFileSelection(); // Start from root when entering via Browse return; } + currentEpubPath = initialEpubPath; auto epub = loadEpub(initialEpubPath); if (!epub) { onGoBack(); diff --git a/src/activities/reader/ReaderActivity.h b/src/activities/reader/ReaderActivity.h index e566d6d..5bb3419 100644 --- a/src/activities/reader/ReaderActivity.h +++ b/src/activities/reader/ReaderActivity.h @@ -7,11 +7,13 @@ class Epub; class ReaderActivity final : public ActivityWithSubactivity { std::string initialEpubPath; + std::string currentEpubPath; // Track current book path for navigation const std::function onGoBack; static std::unique_ptr loadEpub(const std::string& path); + static std::string extractFolderPath(const std::string& filePath); void onSelectEpubFile(const std::string& path); - void onGoToFileSelection(); + void onGoToFileSelection(const std::string& fromEpubPath = ""); void onGoToEpubReader(std::unique_ptr epub); public: diff --git a/src/main.cpp b/src/main.cpp index b71ea39..9b950f1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -142,6 +142,7 @@ void onGoToReader(const std::string& initialEpubPath) { enterNewActivity(new ReaderActivity(renderer, inputManager, initialEpubPath, onGoHome)); } void onGoToReaderHome() { onGoToReader(std::string()); } +void onContinueReading() { onGoToReader(APP_STATE.openEpubPath); } void onGoToFileTransfer() { exitActivity(); @@ -155,7 +156,8 @@ void onGoToSettings() { void onGoHome() { exitActivity(); - enterNewActivity(new HomeActivity(renderer, inputManager, onGoToReaderHome, onGoToSettings, onGoToFileTransfer)); + enterNewActivity(new HomeActivity(renderer, inputManager, onContinueReading, onGoToReaderHome, onGoToSettings, + onGoToFileTransfer)); } void setupDisplayAndFonts() {