feat: BookInfo button mapping, ManageBook integration, cover regen fix

- Fix BookInfo buttons: Left/Right front = scroll down/up, Confirm = no-op,
  side buttons retained. Separate Up/Down hints on btn3/btn4.
- Fallback load: try epub.load(true, true) when cache-only load fails,
  so Book Info works for unopened books.
- Add "Book Info" to ManageBook menu (BOOK_INFO action) with handlers in
  all 4 result sites (Home, Recent, FileBrowser, Reader).
- Fix HomeActivity cover regen: call generateCoverBmp(false) + validate
  with isValidThumbnailBmp before falling to placeholder, matching the
  reader's multi-tier fallback pipeline. Same for XTC branch.

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-09 02:06:25 -04:00
parent 1105919359
commit edf273f1d8
8 changed files with 110 additions and 11 deletions

View File

@@ -0,0 +1,51 @@
# BookInfo Button Mapping, Fallback Load, ManageBook Integration, Cover Regen Fix
**Date**: 2026-03-09
**Task**: Implement fixes 1-5 from the BookInfo Buttons and Load plan
## Changes Made
### BookInfoActivity.cpp — Fixes 1-3
1. **Button mapping** (`loop()`): Removed `Confirm` as exit trigger. Added `Left` and `Right` front buttons as scroll-down/scroll-up handlers alongside existing side-button `Down`/`Up` and `PageForward`/`PageBack`.
2. **Button hints** (`render()`): Updated `mapLabels` to show `STR_DIR_DOWN` on Left (btn3) and `STR_DIR_UP` on Right (btn4), with separate variables for each direction instead of a single combined hint.
3. **Fallback load** (`onEnter()`): Changed `epub.load(false, true)` pattern to try `epub.load(true, true)` on failure, ensuring cache is built for books that were never opened. XTC branch already builds unconditionally via `xtc.load()`.
### BookManageMenuActivity — Fix 4
- Added `BOOK_INFO` to `Action` enum (first entry)
- Added `STR_BOOK_INFO` menu item as first entry in `buildMenuItems()`
- Handled `BOOK_INFO` in all 4 result sites:
- `HomeActivity::openManageMenu()` — launches `BookInfoActivity` via `startActivityForResult`
- `RecentBooksActivity::openManageMenu()` — same pattern
- `FileBrowserActivity::handleManageResult()` — same pattern
- `EpubReaderActivity` inline switch — same pattern
- Added `BOOK_INFO` no-op case to `executeManageAction()` in Home/Recent to prevent compiler warnings
### HomeActivity cover regeneration — Fix 5
- After `generateThumbBmp()` fails or produces invalid BMP, now calls `generateCoverBmp(false)` to extract the full cover from the EPUB before retrying thumbnail generation
- Added `Epub::isValidThumbnailBmp()` validation after each `generateThumbBmp()` call
- Applied same pattern to XTC branch using `xtc.generateCoverBmp()` + validation
- This aligns the HomeActivity pipeline with the EpubReaderActivity's multi-tier fallback approach
## Files Changed
- `src/activities/home/BookInfoActivity.cpp` — button mapping, hints, fallback load
- `src/activities/home/BookManageMenuActivity.h` — BOOK_INFO enum entry
- `src/activities/home/BookManageMenuActivity.cpp` — BOOK_INFO menu item
- `src/activities/home/HomeActivity.cpp` — BOOK_INFO handler + cover regen fix
- `src/activities/home/RecentBooksActivity.cpp` — BOOK_INFO handler
- `src/activities/home/FileBrowserActivity.cpp` — BOOK_INFO handler
- `src/activities/reader/EpubReaderActivity.cpp` — BOOK_INFO handler
## Build Result
Build succeeded — 0 errors, 0 code warnings. RAM: 30.3%, Flash: 95.7%.
## Follow-up Items
- Test on device: verify all 4 BookInfo entry points work (Home manage, Recent manage, FileBrowser manage, Reader manage)
- Test cover regeneration after DELETE_CACHE from reader menu
- Verify button mapping on BookInfo screen (Left=down, Right=up, side buttons=scroll)
- Verify Book Info shows for books that have never been opened

View File

@@ -63,7 +63,10 @@ void BookInfoActivity::onEnter() {
if (FsHelpers::hasEpubExtension(fileName)) {
Epub epub(filePath, "/.crosspoint");
if (epub.load(false, true)) {
if (!epub.load(false, true)) {
epub.load(true, true);
}
{
title = epub.getTitle();
author = epub.getAuthor();
series = epub.getSeries();
@@ -177,8 +180,7 @@ void BookInfoActivity::buildLayout(const std::string& title, const std::string&
}
void BookInfoActivity::loop() {
if (mappedInput.wasReleased(MappedInputManager::Button::Back) ||
mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
ActivityResult r;
r.isCancelled = true;
setResult(std::move(r));
@@ -189,14 +191,18 @@ void BookInfoActivity::loop() {
const int pageH = renderer.getScreenHeight();
const int scrollStep = pageH / 3;
if (mappedInput.wasReleased(MappedInputManager::Button::Down)) {
if (mappedInput.wasReleased(MappedInputManager::Button::Down) ||
mappedInput.wasReleased(MappedInputManager::Button::PageForward) ||
mappedInput.wasReleased(MappedInputManager::Button::Left)) {
if (scrollOffset + pageH < contentHeight) {
scrollOffset += scrollStep;
requestUpdate();
}
}
if (mappedInput.wasReleased(MappedInputManager::Button::Up)) {
if (mappedInput.wasReleased(MappedInputManager::Button::Up) ||
mappedInput.wasReleased(MappedInputManager::Button::PageBack) ||
mappedInput.wasReleased(MappedInputManager::Button::Right)) {
if (scrollOffset > 0) {
scrollOffset -= scrollStep;
if (scrollOffset < 0) scrollOffset = 0;
@@ -252,8 +258,9 @@ void BookInfoActivity::render(RenderLock&&) {
const bool canScrollDown = scrollOffset + pageH < contentHeight;
const bool canScrollUp = scrollOffset > 0;
const char* scrollHint = canScrollDown ? tr(STR_DIR_DOWN) : (canScrollUp ? tr(STR_DIR_UP) : "");
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", scrollHint, "");
const char* downHint = canScrollDown ? tr(STR_DIR_DOWN) : "";
const char* upHint = canScrollUp ? tr(STR_DIR_UP) : "";
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", downHint, upHint);
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();

View File

@@ -10,6 +10,7 @@
void BookManageMenuActivity::buildMenuItems() {
menuItems.clear();
menuItems.push_back({Action::BOOK_INFO, StrId::STR_BOOK_INFO});
if (archived) {
menuItems.push_back({Action::UNARCHIVE, StrId::STR_UNARCHIVE_BOOK});
} else {

View File

@@ -11,6 +11,7 @@
class BookManageMenuActivity final : public Activity {
public:
enum class Action {
BOOK_INFO,
ARCHIVE,
UNARCHIVE,
DELETE,

View File

@@ -137,9 +137,17 @@ void FileBrowserActivity::handleManageResult(const std::string& fullPath, const
const auto& menuResult = std::get<MenuResult>(result.data);
auto action = static_cast<BookManageMenuActivity::Action>(menuResult.action);
if (action == BookManageMenuActivity::Action::BOOK_INFO) {
startActivityForResult(std::make_unique<BookInfoActivity>(renderer, mappedInput, fullPath),
[this](const ActivityResult&) { requestUpdate(); });
return;
}
auto executeAction = [this, action, fullPath]() {
bool success = false;
switch (action) {
case BookManageMenuActivity::Action::BOOK_INFO:
return;
case BookManageMenuActivity::Action::ARCHIVE:
success = BookManager::archiveBook(fullPath);
break;

View File

@@ -15,6 +15,7 @@
#include <vector>
#include "../util/ConfirmationActivity.h"
#include "BookInfoActivity.h"
#include "BookManageMenuActivity.h"
#include "CrossPointSettings.h"
#include "CrossPointState.h"
@@ -81,7 +82,11 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
epub.load(true, true);
}
success = epub.generateThumbBmp(coverHeight);
if (success) {
if (!success || !Epub::isValidThumbnailBmp(epub.getThumbBmpPath(coverHeight))) {
epub.generateCoverBmp(false);
success = epub.generateThumbBmp(coverHeight);
}
if (success && Epub::isValidThumbnailBmp(epub.getThumbBmpPath(coverHeight))) {
const std::string thumbPath = epub.getThumbBmpPath(coverHeight);
RECENT_BOOKS.updateBook(book.path, book.title, book.author, book.series, thumbPath);
book.coverBmpPath = thumbPath;
@@ -93,10 +98,16 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
Xtc xtc(book.path, "/.crosspoint");
if (xtc.load()) {
success = xtc.generateThumbBmp(coverHeight);
if (success) {
if (!success || !Epub::isValidThumbnailBmp(xtc.getThumbBmpPath(coverHeight))) {
xtc.generateCoverBmp();
success = xtc.generateThumbBmp(coverHeight);
}
if (success && Epub::isValidThumbnailBmp(xtc.getThumbBmpPath(coverHeight))) {
const std::string thumbPath = xtc.getThumbBmpPath(coverHeight);
RECENT_BOOKS.updateBook(book.path, book.title, book.author, book.series, thumbPath);
book.coverBmpPath = thumbPath;
} else {
success = false;
}
}
if (!success) {
@@ -304,6 +315,8 @@ void HomeActivity::onOpdsBrowserOpen() { activityManager.goToBrowser(); }
void HomeActivity::executeManageAction(BookManageMenuActivity::Action action, const std::string& capturedPath) {
bool success = false;
switch (action) {
case BookManageMenuActivity::Action::BOOK_INFO:
return;
case BookManageMenuActivity::Action::ARCHIVE:
success = BookManager::archiveBook(capturedPath);
break;
@@ -352,7 +365,11 @@ void HomeActivity::openManageMenu(const std::string& bookPath) {
const auto& menuResult = std::get<MenuResult>(result.data);
auto action = static_cast<BookManageMenuActivity::Action>(menuResult.action);
if (action == BookManageMenuActivity::Action::DELETE || action == BookManageMenuActivity::Action::ARCHIVE) {
if (action == BookManageMenuActivity::Action::BOOK_INFO) {
startActivityForResult(std::make_unique<BookInfoActivity>(renderer, mappedInput, capturedPath),
[this](const ActivityResult&) { requestUpdate(); });
} else if (action == BookManageMenuActivity::Action::DELETE ||
action == BookManageMenuActivity::Action::ARCHIVE) {
const char* promptKey =
(action == BookManageMenuActivity::Action::DELETE) ? tr(STR_DELETE_BOOK) : tr(STR_ARCHIVE_BOOK);
std::string heading = std::string(promptKey) + "?";

View File

@@ -7,6 +7,7 @@
#include <algorithm>
#include "../util/ConfirmationActivity.h"
#include "BookInfoActivity.h"
#include "BookManageMenuActivity.h"
#include "MappedInputManager.h"
#include "RecentBooksStore.h"
@@ -51,6 +52,8 @@ void RecentBooksActivity::onExit() {
void RecentBooksActivity::executeManageAction(BookManageMenuActivity::Action action, const std::string& capturedPath) {
bool success = false;
switch (action) {
case BookManageMenuActivity::Action::BOOK_INFO:
return;
case BookManageMenuActivity::Action::ARCHIVE:
success = BookManager::archiveBook(capturedPath);
break;
@@ -92,7 +95,11 @@ void RecentBooksActivity::openManageMenu(const std::string& bookPath) {
const auto& menuResult = std::get<MenuResult>(result.data);
auto action = static_cast<BookManageMenuActivity::Action>(menuResult.action);
if (action == BookManageMenuActivity::Action::DELETE || action == BookManageMenuActivity::Action::ARCHIVE) {
if (action == BookManageMenuActivity::Action::BOOK_INFO) {
startActivityForResult(std::make_unique<BookInfoActivity>(renderer, mappedInput, capturedPath),
[this](const ActivityResult&) { requestUpdate(); });
} else if (action == BookManageMenuActivity::Action::DELETE ||
action == BookManageMenuActivity::Action::ARCHIVE) {
const char* promptKey =
(action == BookManageMenuActivity::Action::DELETE) ? tr(STR_DELETE_BOOK) : tr(STR_ARCHIVE_BOOK);
std::string heading = std::string(promptKey) + "?";

View File

@@ -26,6 +26,7 @@
#include "ReaderUtils.h"
#include "RecentBooksStore.h"
#include "activities/ActivityManager.h"
#include "activities/home/BookInfoActivity.h"
#include "activities/home/BookManageMenuActivity.h"
#include "activities/util/ConfirmationActivity.h"
#include "components/UITheme.h"
@@ -722,6 +723,12 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
const auto& menu = std::get<MenuResult>(result.data);
const auto bookAction = static_cast<BookManageMenuActivity::Action>(menu.action);
switch (bookAction) {
case BookManageMenuActivity::Action::BOOK_INFO: {
const std::string path = epub ? epub->getPath() : "";
startActivityForResult(std::make_unique<BookInfoActivity>(renderer, mappedInput, path),
[this](const ActivityResult&) { requestUpdate(); });
return;
}
case BookManageMenuActivity::Action::ARCHIVE: {
std::string heading = std::string(tr(STR_ARCHIVE_BOOK)) + "?";
std::string bookName = epub ? epub->getTitle() : "";