Refactor ActivityManager calls

This commit is contained in:
jpirnay
2026-04-17 20:23:42 +02:00
parent ef088db789
commit b4881e1563
12 changed files with 156 additions and 45 deletions
-2
View File
@@ -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>&& activity, ActivityResultHandler resultHandler) {
this->resultHandler = std::move(resultHandler);
activityManager.pushActivity(std::move(activity));
-1
View File
@@ -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);
};
+57 -19
View File
@@ -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() : "<none>",
stackSize,
freeHeap,
contigHeap);
LOG_DBG("ACT", "%s: current=%s stackSize=%zu free=%lu contig=%lu", stage,
currentActivity ? currentActivity->getName().c_str() : "<none>", 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<Activity>&& 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<SettingsActivity>(renderer, mappedInput)); }
void ActivityManager::goToFileBrowser(std::string path) {
replaceActivity(std::make_unique<FileBrowserActivity>(renderer, mappedInput, std::move(path)));
void ActivityManager::goToFileBrowser(std::string path, std::string focusName) {
hasReturnHint = false;
replaceActivity(std::make_unique<FileBrowserActivity>(renderer, mappedInput, std::move(path), std::move(focusName)));
}
void ActivityManager::goToRecentBooks() {
replaceActivity(std::make_unique<RecentBooksActivity>(renderer, mappedInput));
void ActivityManager::goToRecentBooks(int focusIndex) {
hasReturnHint = false;
replaceActivity(std::make_unique<RecentBooksActivity>(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<ReaderActivity>(renderer, mappedInput, std::move(path)));
void ActivityManager::replaceWithReader(std::string path, ReturnHint hint) {
returnHint = std::move(hint);
hasReturnHint = true;
replaceActivity(std::make_unique<ReaderActivity>(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<FileBrowserActivity>(renderer, mappedInput, std::move(path), std::move(focusName)));
}
void ActivityManager::replaceWithRecentBooks(ReturnHint hint) {
returnHint = std::move(hint);
hasReturnHint = true;
replaceActivity(std::make_unique<RecentBooksActivity>(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<WeatherActivity>(renderer, mappedInput)); }
void ActivityManager::goHome() { replaceActivity(std::make_unique<HomeActivity>(renderer, mappedInput)); }
void ActivityManager::goHome(std::string focusBookPath) {
hasReturnHint = false;
replaceActivity(std::make_unique<HomeActivity>(renderer, mappedInput, std::move(focusBookPath)));
}
void ActivityManager::pushActivity(std::unique_ptr<Activity>&& activity) {
if (pendingActivity) {
+36 -4
View File
@@ -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>&& activity);
+15 -1
View File
@@ -8,6 +8,7 @@
#include <algorithm>
#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;
}
+6 -2
View File
@@ -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<std::string> 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;
@@ -9,6 +9,7 @@
#include <algorithm>
#include <cstdio>
#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 <typename Op>
+16 -1
View File
@@ -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<int>(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) {
+4 -2
View File
@@ -44,6 +44,8 @@ class HomeActivity final : public Activity {
std::vector<MenuEntry> 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;
+9 -1
View File
@@ -7,6 +7,7 @@
#include <algorithm>
#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<size_t>(initialFocusIndex) < recentBooks.size()) {
selectorIndex = static_cast<size_t>(initialFocusIndex);
}
initialFocusIndex = -1;
requestUpdate();
}
@@ -49,7 +54,10 @@ void RecentBooksActivity::loop() {
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) && !recentBooks.empty() &&
selectorIndex < static_cast<int>(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<int>(selectorIndex);
activityManager.replaceWithReader(recentBooks[selectorIndex].path, std::move(hint));
return;
}
+3 -2
View File
@@ -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<RecentBook> 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;
+5 -9
View File
@@ -102,28 +102,24 @@ void ReaderActivity::goToLibrary(const std::string& fromBookPath) {
void ReaderActivity::onGoToEpubReader(std::unique_ptr<Epub> epub) {
const auto epubPath = epub->getPath();
currentBookPath = epubPath;
logReaderLaunchMemSnapshot("before_push_epub_reader");
startActivityForResult(std::make_unique<EpubReaderActivity>(renderer, mappedInput, std::move(epub)),
[this](const ActivityResult&) { finish(); });
logReaderLaunchMemSnapshot("before_replace_epub_reader");
activityManager.replaceActivity(std::make_unique<EpubReaderActivity>(renderer, mappedInput, std::move(epub)));
}
void ReaderActivity::onGoToBmpViewer(const std::string& path) {
startActivityForResult(std::make_unique<BmpViewerActivity>(renderer, mappedInput, path),
[this](const ActivityResult&) { finish(); });
activityManager.replaceActivity(std::make_unique<BmpViewerActivity>(renderer, mappedInput, path));
}
void ReaderActivity::onGoToXtcReader(std::unique_ptr<Xtc> xtc) {
const auto xtcPath = xtc->getPath();
currentBookPath = xtcPath;
startActivityForResult(std::make_unique<XtcReaderActivity>(renderer, mappedInput, std::move(xtc)),
[this](const ActivityResult&) { finish(); });
activityManager.replaceActivity(std::make_unique<XtcReaderActivity>(renderer, mappedInput, std::move(xtc)));
}
void ReaderActivity::onGoToTxtReader(std::unique_ptr<Txt> txt) {
const auto txtPath = txt->getPath();
currentBookPath = txtPath;
startActivityForResult(std::make_unique<TxtReaderActivity>(renderer, mappedInput, std::move(txt)),
[this](const ActivityResult&) { finish(); });
activityManager.replaceActivity(std::make_unique<TxtReaderActivity>(renderer, mappedInput, std::move(txt)));
}
void ReaderActivity::onEnter() {