From b4881e1563cf00c83600450440340ffde6ddf905 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 17 Apr 2026 20:23:42 +0200 Subject: [PATCH] Refactor ActivityManager calls --- src/activities/Activity.cpp | 2 - src/activities/Activity.h | 1 - src/activities/ActivityManager.cpp | 76 ++++++++++++++----- src/activities/ActivityManager.h | 40 +++++++++- src/activities/home/FileBrowserActivity.cpp | 16 +++- src/activities/home/FileBrowserActivity.h | 8 +- .../home/GlobalBookmarksActivity.cpp | 6 +- src/activities/home/HomeActivity.cpp | 17 ++++- src/activities/home/HomeActivity.h | 6 +- src/activities/home/RecentBooksActivity.cpp | 10 ++- src/activities/home/RecentBooksActivity.h | 5 +- src/activities/reader/ReaderActivity.cpp | 14 ++-- 12 files changed, 156 insertions(+), 45 deletions(-) diff --git a/src/activities/Activity.cpp b/src/activities/Activity.cpp index 21aaa1b2..17ad577d 100644 --- a/src/activities/Activity.cpp +++ b/src/activities/Activity.cpp @@ -12,8 +12,6 @@ void Activity::requestUpdateAndWait() { activityManager.requestUpdateAndWait(); void Activity::onGoHome() { activityManager.goHome(); } -void Activity::onSelectBook(const std::string& path) { activityManager.pushReader(path); } - void Activity::startActivityForResult(std::unique_ptr&& activity, ActivityResultHandler resultHandler) { this->resultHandler = std::move(resultHandler); activityManager.pushActivity(std::move(activity)); diff --git a/src/activities/Activity.h b/src/activities/Activity.h index f74599a2..c5e4949e 100644 --- a/src/activities/Activity.h +++ b/src/activities/Activity.h @@ -58,5 +58,4 @@ class Activity { // Convenience method to facilitate API transition to ActivityManager // TODO: remove this in near future void onGoHome(); - void onSelectBook(const std::string& path); }; diff --git a/src/activities/ActivityManager.cpp b/src/activities/ActivityManager.cpp index 86acbeb1..7aa1f239 100644 --- a/src/activities/ActivityManager.cpp +++ b/src/activities/ActivityManager.cpp @@ -35,12 +35,8 @@ void ActivityManager::begin() { static void logActivityStackState(const char* stage, Activity* currentActivity, size_t stackSize) { const uint32_t freeHeap = esp_get_free_heap_size(); const uint32_t contigHeap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_DEFAULT); - LOG_DBG("ACT", "%s: current=%s stackSize=%zu free=%lu contig=%lu", - stage, - currentActivity ? currentActivity->getName().c_str() : "", - stackSize, - freeHeap, - contigHeap); + LOG_DBG("ACT", "%s: current=%s stackSize=%zu free=%lu contig=%lu", stage, + currentActivity ? currentActivity->getName().c_str() : "", stackSize, freeHeap, contigHeap); } void ActivityManager::renderTaskTrampoline(void* param) { @@ -125,10 +121,10 @@ void ActivityManager::loop() { pendingAction = PendingAction::None; if (stackActivities.empty()) { - LOG_DBG("ACT", "No more activities on stack, going home"); - lock.unlock(); // goHome may acquire its own lock - goHome(); - continue; // Will launch goHome immediately + LOG_DBG("ACT", "No more activities on stack, returning from child"); + lock.unlock(); // returnFromChild may acquire its own lock via replaceActivity + returnFromChild(); + continue; // Will launch the target activity immediately } else { currentActivity = std::move(stackActivities.back()); @@ -219,8 +215,8 @@ void ActivityManager::replaceActivity(std::unique_ptr&& newActivity) { if (currentActivity) { // Defer launch if we're currently in an activity, to avoid deleting the current activity // leading to the "delete this" problem - LOG_DBG("ACT", "replaceActivity requested: current=%s stackSize=%zu", - currentActivity->getName().c_str(), stackActivities.size()); + LOG_DBG("ACT", "replaceActivity requested: current=%s stackSize=%zu", currentActivity->getName().c_str(), + stackActivities.size()); pendingActivity = std::move(newActivity); pendingAction = PendingAction::Replace; } else { @@ -236,12 +232,14 @@ void ActivityManager::goToFileTransfer() { void ActivityManager::goToSettings() { replaceActivity(std::make_unique(renderer, mappedInput)); } -void ActivityManager::goToFileBrowser(std::string path) { - replaceActivity(std::make_unique(renderer, mappedInput, std::move(path))); +void ActivityManager::goToFileBrowser(std::string path, std::string focusName) { + hasReturnHint = false; + replaceActivity(std::make_unique(renderer, mappedInput, std::move(path), std::move(focusName))); } -void ActivityManager::goToRecentBooks() { - replaceActivity(std::make_unique(renderer, mappedInput)); +void ActivityManager::goToRecentBooks(int focusIndex) { + hasReturnHint = false; + replaceActivity(std::make_unique(renderer, mappedInput, focusIndex)); } void ActivityManager::goToGlobalBookmarks() { @@ -269,8 +267,45 @@ void ActivityManager::goToKOReaderSync() { sync.hasParagraphIndex, sync.intent)); } -void ActivityManager::pushReader(std::string path) { - pushActivity(std::make_unique(renderer, mappedInput, std::move(path))); +void ActivityManager::replaceWithReader(std::string path, ReturnHint hint) { + returnHint = std::move(hint); + hasReturnHint = true; + replaceActivity(std::make_unique(renderer, mappedInput, std::move(path))); +} + +void ActivityManager::replaceWithFileBrowser(std::string path, ReturnHint hint, std::string focusName) { + returnHint = std::move(hint); + hasReturnHint = true; + replaceActivity(std::make_unique(renderer, mappedInput, std::move(path), std::move(focusName))); +} + +void ActivityManager::replaceWithRecentBooks(ReturnHint hint) { + returnHint = std::move(hint); + hasReturnHint = true; + replaceActivity(std::make_unique(renderer, mappedInput, -1)); +} + +void ActivityManager::returnFromChild() { + if (!hasReturnHint) { + goHome(); + return; + } + ReturnHint hint = std::move(returnHint); + returnHint = {}; + hasReturnHint = false; + + switch (hint.target) { + case ReturnTo::FileBrowser: + goToFileBrowser(std::move(hint.path), std::move(hint.selectName)); + break; + case ReturnTo::RecentBooks: + goToRecentBooks(hint.selectIndex); + break; + case ReturnTo::Home: + default: + goHome(std::move(hint.selectName)); + break; + } } void ActivityManager::goToSleep() { @@ -286,7 +321,10 @@ void ActivityManager::goToFullScreenMessage(std::string message, EpdFontFamily:: void ActivityManager::goToWeather() { replaceActivity(std::make_unique(renderer, mappedInput)); } -void ActivityManager::goHome() { replaceActivity(std::make_unique(renderer, mappedInput)); } +void ActivityManager::goHome(std::string focusBookPath) { + hasReturnHint = false; + replaceActivity(std::make_unique(renderer, mappedInput, std::move(focusBookPath))); +} void ActivityManager::pushActivity(std::unique_ptr&& activity) { if (pendingActivity) { diff --git a/src/activities/ActivityManager.h b/src/activities/ActivityManager.h index b59f6e96..b108e988 100644 --- a/src/activities/ActivityManager.h +++ b/src/activities/ActivityManager.h @@ -15,6 +15,21 @@ class Activity; // forward declaration class RenderLock; // forward declaration +// Where a "child" activity (launched via one of the replaceWith* helpers) should route +// control when it exits successfully. See ActivityManager::returnFromChild(). +enum class ReturnTo : uint8_t { Home, FileBrowser, RecentBooks }; + +// Minimal state the returning parent needs to restore its previous view (directory, +// focused item, list index). Kept as a plain struct stored by value on the +// ActivityManager — single instance, overwritten per transition, no heap churn +// beyond the two small std::strings. +struct ReturnHint { + ReturnTo target = ReturnTo::Home; + std::string path; // FileBrowser directory to restore + std::string selectName; // item to re-focus in a list (file name, book title) + int selectIndex = -1; // e.g. Recents index +}; + /** * ActivityManager * @@ -68,6 +83,12 @@ class ActivityManager { // into the next one. bool drainInput = false; + // Where returnFromChild() should route to. Set by replaceWith*() helpers, cleared + // in returnFromChild(). Cleared on any manual goHome()/goTo*() to avoid stale hints + // outliving the flow they were recorded for. + ReturnHint returnHint; + bool hasReturnHint = false; + public: explicit ActivityManager(GfxRenderer& renderer, MappedInputManager& mappedInput) : renderer(renderer), mappedInput(mappedInput), renderingMutex(xSemaphoreCreateMutex()) { @@ -85,18 +106,29 @@ class ActivityManager { // goTo... functions are convenient wrapper for replaceActivity() void goToFileTransfer(); void goToSettings(); - void goToFileBrowser(std::string path = {}); - void goToRecentBooks(); + void goToFileBrowser(std::string path = {}, std::string focusName = {}); + void goToRecentBooks(int focusIndex = -1); void goToGlobalBookmarks(); void goToBrowser(); void goToReader(std::string path); void goToKOReaderSync(); - void pushReader(std::string path); void goToSleep(); void goToBoot(); void goToFullScreenMessage(std::string message, EpdFontFamily::Style style = EpdFontFamily::REGULAR); void goToWeather(); - void goHome(); + void goHome(std::string focusBookPath = {}); + + // Replace-with-hint helpers: destroy the current activity before launching the new + // one (freeing its memory) and record where to route control when the new activity + // exits. Consumed by returnFromChild(). + void replaceWithReader(std::string path, ReturnHint hint); + void replaceWithFileBrowser(std::string path, ReturnHint hint, std::string focusName = {}); + void replaceWithRecentBooks(ReturnHint hint); + + // Called by a "child" activity on successful exit. Consults the stored ReturnHint, + // clears it, and dispatches to the corresponding parent with restoration args. If + // no hint is set, defaults to goHome(). + void returnFromChild(); // This will move current activity to stack instead of deleting it void pushActivity(std::unique_ptr&& activity); diff --git a/src/activities/home/FileBrowserActivity.cpp b/src/activities/home/FileBrowserActivity.cpp index a4e771df..0e5229a9 100644 --- a/src/activities/home/FileBrowserActivity.cpp +++ b/src/activities/home/FileBrowserActivity.cpp @@ -8,6 +8,7 @@ #include +#include "../ActivityManager.h" #include "../util/ConfirmationActivity.h" #include "BookInfoActivity.h" #include "CrossPointSettings.h" @@ -113,6 +114,14 @@ void FileBrowserActivity::onEnter() { loadFiles(); selectorIndex = 0; + if (!focusName.empty()) { + const size_t idx = findEntry(focusName); + if (idx < files.size()) { + selectorIndex = idx; + } + focusName.clear(); + } + requestUpdate(); } @@ -171,7 +180,12 @@ void FileBrowserActivity::loop() { } else { std::string fullPath = basepath; if (fullPath.back() != '/') fullPath += "/"; - onSelectBook(fullPath + entry); + fullPath += entry; + ReturnHint hint; + hint.target = ReturnTo::FileBrowser; + hint.path = basepath; + hint.selectName = entry; + activityManager.replaceWithReader(std::move(fullPath), std::move(hint)); } return; } diff --git a/src/activities/home/FileBrowserActivity.h b/src/activities/home/FileBrowserActivity.h index c4f359dd..991e1c55 100644 --- a/src/activities/home/FileBrowserActivity.h +++ b/src/activities/home/FileBrowserActivity.h @@ -19,6 +19,7 @@ class FileBrowserActivity final : public Activity { // Files state std::string basepath = "/"; + std::string focusName; // entry to select on first load (e.g. the file just returned from) std::vector files; // Data loading @@ -26,8 +27,11 @@ class FileBrowserActivity final : public Activity { size_t findEntry(const std::string& name) const; public: - explicit FileBrowserActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string initialPath = "/") - : Activity("FileBrowser", renderer, mappedInput), basepath(initialPath.empty() ? "/" : std::move(initialPath)) {} + explicit FileBrowserActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string initialPath = "/", + std::string focusName = {}) + : Activity("FileBrowser", renderer, mappedInput), + basepath(initialPath.empty() ? "/" : std::move(initialPath)), + focusName(std::move(focusName)) {} void onEnter() override; void onExit() override; void loop() override; diff --git a/src/activities/home/GlobalBookmarksActivity.cpp b/src/activities/home/GlobalBookmarksActivity.cpp index 09912068..0739f8de 100644 --- a/src/activities/home/GlobalBookmarksActivity.cpp +++ b/src/activities/home/GlobalBookmarksActivity.cpp @@ -9,6 +9,7 @@ #include #include +#include "../ActivityManager.h" #include "BookmarkStore.h" #include "CrossPointState.h" #include "GlobalBookmarkIndex.h" @@ -124,7 +125,10 @@ void GlobalBookmarksActivity::openSelected() { APP_STATE.saveToFile(); LOG_DBG("GBA", "Jumping to bookmark in %s at %u/%u", entry.sourcePath.c_str(), bm.spineIndex, bm.pageNumber); - onSelectBook(entry.sourcePath); + ReturnHint hint; + hint.target = ReturnTo::Home; + hint.selectName = entry.sourcePath; + activityManager.replaceWithReader(entry.sourcePath, std::move(hint)); } template diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index 52ec7658..fe9ad3d0 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -211,6 +211,16 @@ void HomeActivity::onEnter() { recentsLoaded = true; } + if (!focusBookPath.empty()) { + for (size_t i = 0; i < recentBooks.size(); ++i) { + if (recentBooks[i].path == focusBookPath) { + selectorIndex = static_cast(i); + break; + } + } + focusBookPath.clear(); + } + // Trigger first update menuEntriesDirty = true; requestUpdate(); @@ -348,7 +358,12 @@ void HomeActivity::render(RenderLock&&) { } } -void HomeActivity::onSelectBook(const std::string& path) { activityManager.pushReader(path); } +void HomeActivity::onSelectBook(const std::string& path) { + ReturnHint hint; + hint.target = ReturnTo::Home; + hint.selectName = path; // used to re-focus the book in the recents strip after return + activityManager.replaceWithReader(path, std::move(hint)); +} void HomeActivity::dispatchMenuAction(MenuAction action) { switch (action) { diff --git a/src/activities/home/HomeActivity.h b/src/activities/home/HomeActivity.h index c4fad72a..32cece1f 100644 --- a/src/activities/home/HomeActivity.h +++ b/src/activities/home/HomeActivity.h @@ -44,6 +44,8 @@ class HomeActivity final : public Activity { std::vector menuEntries; bool menuEntriesDirty = true; + std::string focusBookPath; // book path to re-select on first render, if present in recents + void onSelectBook(const std::string& path); void dispatchMenuAction(MenuAction action); @@ -55,8 +57,8 @@ class HomeActivity final : public Activity { void loadRecentCovers(int coverHeight); public: - explicit HomeActivity(GfxRenderer& renderer, MappedInputManager& mappedInput) - : Activity("Home", renderer, mappedInput) {} + explicit HomeActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string focusBookPath = {}) + : Activity("Home", renderer, mappedInput), focusBookPath(std::move(focusBookPath)) {} void onEnter() override; void onExit() override; void loop() override; diff --git a/src/activities/home/RecentBooksActivity.cpp b/src/activities/home/RecentBooksActivity.cpp index 1966cd3a..750546ad 100644 --- a/src/activities/home/RecentBooksActivity.cpp +++ b/src/activities/home/RecentBooksActivity.cpp @@ -7,6 +7,7 @@ #include +#include "../ActivityManager.h" #include "../util/ConfirmationActivity.h" #include "BookInfoActivity.h" #include "MappedInputManager.h" @@ -35,6 +36,10 @@ void RecentBooksActivity::onEnter() { loadRecentBooks(); selectorIndex = 0; + if (initialFocusIndex >= 0 && static_cast(initialFocusIndex) < recentBooks.size()) { + selectorIndex = static_cast(initialFocusIndex); + } + initialFocusIndex = -1; requestUpdate(); } @@ -49,7 +54,10 @@ void RecentBooksActivity::loop() { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) && !recentBooks.empty() && selectorIndex < static_cast(recentBooks.size())) { LOG_DBG("RBA", "Selected recent book: %s", recentBooks[selectorIndex].path.c_str()); - onSelectBook(recentBooks[selectorIndex].path); + ReturnHint hint; + hint.target = ReturnTo::RecentBooks; + hint.selectIndex = static_cast(selectorIndex); + activityManager.replaceWithReader(recentBooks[selectorIndex].path, std::move(hint)); return; } diff --git a/src/activities/home/RecentBooksActivity.h b/src/activities/home/RecentBooksActivity.h index b1103f04..8c028e19 100644 --- a/src/activities/home/RecentBooksActivity.h +++ b/src/activities/home/RecentBooksActivity.h @@ -14,6 +14,7 @@ class RecentBooksActivity final : public Activity { ButtonNavigator buttonNavigator; size_t selectorIndex = 0; + int initialFocusIndex = -1; // applied once in onEnter(), then cleared // Recent tab state std::vector recentBooks; @@ -22,8 +23,8 @@ class RecentBooksActivity final : public Activity { void loadRecentBooks(); public: - explicit RecentBooksActivity(GfxRenderer& renderer, MappedInputManager& mappedInput) - : Activity("RecentBooks", renderer, mappedInput) {} + explicit RecentBooksActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, int focusIndex = -1) + : Activity("RecentBooks", renderer, mappedInput), initialFocusIndex(focusIndex) {} void onEnter() override; void onExit() override; void loop() override; diff --git a/src/activities/reader/ReaderActivity.cpp b/src/activities/reader/ReaderActivity.cpp index 4c293b8e..031bd78b 100644 --- a/src/activities/reader/ReaderActivity.cpp +++ b/src/activities/reader/ReaderActivity.cpp @@ -102,28 +102,24 @@ void ReaderActivity::goToLibrary(const std::string& fromBookPath) { void ReaderActivity::onGoToEpubReader(std::unique_ptr epub) { const auto epubPath = epub->getPath(); currentBookPath = epubPath; - logReaderLaunchMemSnapshot("before_push_epub_reader"); - startActivityForResult(std::make_unique(renderer, mappedInput, std::move(epub)), - [this](const ActivityResult&) { finish(); }); + logReaderLaunchMemSnapshot("before_replace_epub_reader"); + activityManager.replaceActivity(std::make_unique(renderer, mappedInput, std::move(epub))); } void ReaderActivity::onGoToBmpViewer(const std::string& path) { - startActivityForResult(std::make_unique(renderer, mappedInput, path), - [this](const ActivityResult&) { finish(); }); + activityManager.replaceActivity(std::make_unique(renderer, mappedInput, path)); } void ReaderActivity::onGoToXtcReader(std::unique_ptr xtc) { const auto xtcPath = xtc->getPath(); currentBookPath = xtcPath; - startActivityForResult(std::make_unique(renderer, mappedInput, std::move(xtc)), - [this](const ActivityResult&) { finish(); }); + activityManager.replaceActivity(std::make_unique(renderer, mappedInput, std::move(xtc))); } void ReaderActivity::onGoToTxtReader(std::unique_ptr txt) { const auto txtPath = txt->getPath(); currentBookPath = txtPath; - startActivityForResult(std::make_unique(renderer, mappedInput, std::move(txt)), - [this](const ActivityResult&) { finish(); }); + activityManager.replaceActivity(std::make_unique(renderer, mappedInput, std::move(txt))); } void ReaderActivity::onEnter() {