port: upstream PR #1342 - Book Info screen, richer metadata, safer controls
Ports upstream PR #1342 (feat: Add Book Info screen, richer metadata, and safer file-browser controls) with mod-specific adaptations: - Parse and cache series, seriesIndex, description from EPUB OPF - Bump book.bin cache version to 6 for new metadata fields - Add BookInfoActivity (new screen) accessible via Right button in FileBrowser - Add ManageBook menu via Left button in FileBrowser (replaces upstream hidden delete) - Guard all delete/archive actions with ConfirmationActivity (10 call sites) - Add inputArmed gating to ConfirmationActivity to prevent accidental confirmation - Safe deserialization: readString now returns bool with MAX_STRING_LENGTH guard - Add series field to RecentBooksStore with JSON and binary serialization - Add i18n keys: STR_BOOK_INFO, STR_AUTHOR, STR_SERIES, STR_FILE_SIZE, etc. Made-with: Cursor
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "../util/ConfirmationActivity.h"
|
||||
#include "BookManageMenuActivity.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "RecentBooksStore.h"
|
||||
@@ -47,6 +48,37 @@ void RecentBooksActivity::onExit() {
|
||||
recentBooks.clear();
|
||||
}
|
||||
|
||||
void RecentBooksActivity::executeManageAction(BookManageMenuActivity::Action action, const std::string& capturedPath) {
|
||||
bool success = false;
|
||||
switch (action) {
|
||||
case BookManageMenuActivity::Action::ARCHIVE:
|
||||
success = BookManager::archiveBook(capturedPath);
|
||||
break;
|
||||
case BookManageMenuActivity::Action::UNARCHIVE:
|
||||
success = BookManager::unarchiveBook(capturedPath);
|
||||
break;
|
||||
case BookManageMenuActivity::Action::DELETE:
|
||||
success = BookManager::deleteBook(capturedPath);
|
||||
break;
|
||||
case BookManageMenuActivity::Action::DELETE_CACHE:
|
||||
success = BookManager::deleteBookCache(capturedPath);
|
||||
break;
|
||||
case BookManageMenuActivity::Action::REINDEX:
|
||||
success = BookManager::reindexBook(capturedPath, false);
|
||||
break;
|
||||
case BookManageMenuActivity::Action::REINDEX_FULL:
|
||||
success = BookManager::reindexBook(capturedPath, true);
|
||||
break;
|
||||
}
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
GUI.drawPopup(renderer, success ? tr(STR_DONE) : tr(STR_ACTION_FAILED));
|
||||
}
|
||||
loadRecentBooks();
|
||||
selectorIndex = 0;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void RecentBooksActivity::openManageMenu(const std::string& bookPath) {
|
||||
const bool isArchived = BookManager::isArchived(bookPath);
|
||||
const std::string capturedPath = bookPath;
|
||||
@@ -59,34 +91,26 @@ void RecentBooksActivity::openManageMenu(const std::string& bookPath) {
|
||||
}
|
||||
const auto& menuResult = std::get<MenuResult>(result.data);
|
||||
auto action = static_cast<BookManageMenuActivity::Action>(menuResult.action);
|
||||
bool success = false;
|
||||
switch (action) {
|
||||
case BookManageMenuActivity::Action::ARCHIVE:
|
||||
success = BookManager::archiveBook(capturedPath);
|
||||
break;
|
||||
case BookManageMenuActivity::Action::UNARCHIVE:
|
||||
success = BookManager::unarchiveBook(capturedPath);
|
||||
break;
|
||||
case BookManageMenuActivity::Action::DELETE:
|
||||
success = BookManager::deleteBook(capturedPath);
|
||||
break;
|
||||
case BookManageMenuActivity::Action::DELETE_CACHE:
|
||||
success = BookManager::deleteBookCache(capturedPath);
|
||||
break;
|
||||
case BookManageMenuActivity::Action::REINDEX:
|
||||
success = BookManager::reindexBook(capturedPath, false);
|
||||
break;
|
||||
case BookManageMenuActivity::Action::REINDEX_FULL:
|
||||
success = BookManager::reindexBook(capturedPath, true);
|
||||
break;
|
||||
|
||||
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) + "?";
|
||||
std::string fileName = capturedPath;
|
||||
const auto slash = capturedPath.rfind('/');
|
||||
if (slash != std::string::npos) fileName = capturedPath.substr(slash + 1);
|
||||
|
||||
startActivityForResult(std::make_unique<ConfirmationActivity>(renderer, mappedInput, heading, fileName),
|
||||
[this, action, capturedPath](const ActivityResult& confirmResult) {
|
||||
if (!confirmResult.isCancelled) {
|
||||
executeManageAction(action, capturedPath);
|
||||
} else {
|
||||
requestUpdate();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
executeManageAction(action, capturedPath);
|
||||
}
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
GUI.drawPopup(renderer, success ? tr(STR_DONE) : tr(STR_ACTION_FAILED));
|
||||
}
|
||||
loadRecentBooks();
|
||||
selectorIndex = 0;
|
||||
requestUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,7 +180,15 @@ void RecentBooksActivity::render(RenderLock&&) {
|
||||
} else {
|
||||
GUI.drawList(
|
||||
renderer, Rect{0, contentTop, pageWidth, contentHeight}, recentBooks.size(), selectorIndex,
|
||||
[this](int index) { return recentBooks[index].title; }, [this](int index) { return recentBooks[index].author; },
|
||||
[this](int index) { return recentBooks[index].title; },
|
||||
[this](int index) {
|
||||
const auto& book = recentBooks[index];
|
||||
if (!book.series.empty() && !book.author.empty()) {
|
||||
return book.author + " \xE2\x80\xA2 " + book.series;
|
||||
}
|
||||
if (!book.series.empty()) return book.series;
|
||||
return book.author;
|
||||
},
|
||||
[this](int index) { return UITheme::getFileIcon(recentBooks[index].path); });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user