feat: show loading popup in BookInfo when parsing unopened book

When BookInfo is opened for a book with no existing cache,
epub.load(true, true) triggers a 1-2s full parse. Show a
"Loading..." popup with progress bar so the device doesn't
appear frozen. Popup only appears on the fallback path —
cached books load silently. Same pattern for XTC books.

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-09 02:22:51 -04:00
parent edf273f1d8
commit 42ca85d560
2 changed files with 60 additions and 18 deletions

View File

@@ -0,0 +1,23 @@
# BookInfo Loading Popup for Unopened Books
**Date**: 2026-03-09
**Task**: Show a progress popup when BookInfo needs to parse an unopened book
## Changes Made
### BookInfoActivity.cpp
Added a "Loading..." progress popup with progress bar to `onEnter()` when the book cache doesn't exist and a full parse is required:
- **EPUB branch**: `epub.load(false, true)` is tried first (fast, cache-only). If it fails, `GUI.drawPopup(tr(STR_LOADING))` is shown at 10%, `epub.load(true, true)` runs (slow full parse), progress updates to 50%, thumbnail generation runs, then progress hits 100%.
- **XTC branch**: Checks `Storage.exists(xtc.getCachePath())` before `xtc.load()`. If cache directory is missing, shows the same popup pattern around load + thumbnail generation.
- For books with existing cache, no popup is shown — the fast path completes silently.
## Build Result
Build succeeded — 0 errors, 0 linter warnings. RAM: 30.3%, Flash: 95.7%.
## Follow-up
- Test on device: open BookInfo for a book that has never been opened, verify popup appears
- Verify no popup appears for books that have been previously opened/cached

View File

@@ -63,32 +63,48 @@ void BookInfoActivity::onEnter() {
if (FsHelpers::hasEpubExtension(fileName)) {
Epub epub(filePath, "/.crosspoint");
if (!epub.load(false, true)) {
bool needsBuild = !epub.load(false, true);
Rect popupRect{};
if (needsBuild) {
popupRect = GUI.drawPopup(renderer, tr(STR_LOADING));
GUI.fillPopupProgress(renderer, popupRect, 10);
epub.load(true, true);
GUI.fillPopupProgress(renderer, popupRect, 50);
}
{
title = epub.getTitle();
author = epub.getAuthor();
series = epub.getSeries();
seriesIndex = epub.getSeriesIndex();
description = normalizeWhitespace(epub.getDescription());
language = epub.getLanguage();
const int coverH = renderer.getScreenHeight() * 2 / 5;
if (epub.generateThumbBmp(coverH)) {
coverBmpPath = epub.getThumbBmpPath(coverH);
} else {
const int thumbW = static_cast<int>(coverH * 0.6);
const std::string placeholderPath = epub.getCachePath() + "/placeholder_" + std::to_string(coverH) + ".bmp";
if (PlaceholderCoverGenerator::generate(placeholderPath, title.empty() ? fileName : title, author, thumbW,
coverH)) {
coverBmpPath = placeholderPath;
}
title = epub.getTitle();
author = epub.getAuthor();
series = epub.getSeries();
seriesIndex = epub.getSeriesIndex();
description = normalizeWhitespace(epub.getDescription());
language = epub.getLanguage();
const int coverH = renderer.getScreenHeight() * 2 / 5;
if (epub.generateThumbBmp(coverH)) {
coverBmpPath = epub.getThumbBmpPath(coverH);
} else {
const int thumbW = static_cast<int>(coverH * 0.6);
const std::string placeholderPath = epub.getCachePath() + "/placeholder_" + std::to_string(coverH) + ".bmp";
if (PlaceholderCoverGenerator::generate(placeholderPath, title.empty() ? fileName : title, author, thumbW,
coverH)) {
coverBmpPath = placeholderPath;
}
}
if (needsBuild) {
GUI.fillPopupProgress(renderer, popupRect, 100);
}
} else if (FsHelpers::hasXtcExtension(fileName)) {
Xtc xtc(filePath, "/.crosspoint");
bool needsBuild = !Storage.exists(xtc.getCachePath().c_str());
Rect popupRect{};
if (needsBuild) {
popupRect = GUI.drawPopup(renderer, tr(STR_LOADING));
GUI.fillPopupProgress(renderer, popupRect, 10);
}
if (xtc.load()) {
if (needsBuild) {
GUI.fillPopupProgress(renderer, popupRect, 50);
}
title = xtc.getTitle();
author = xtc.getAuthor();
@@ -104,6 +120,9 @@ void BookInfoActivity::onEnter() {
}
}
}
if (needsBuild) {
GUI.fillPopupProgress(renderer, popupRect, 100);
}
}
if (title.empty()) {