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:
cottongin
2026-03-09 00:39:32 -04:00
parent 255b98bda0
commit 4cf395aee9
129 changed files with 244823 additions and 248138 deletions

View File

@@ -5,9 +5,9 @@
#include <Logging.h>
#include <WiFi.h>
#include "activities/ActivityResult.h"
#include "CrossPointSettings.h"
#include "MappedInputManager.h"
#include "activities/ActivityResult.h"
#include "activities/network/WifiSelectionActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
@@ -57,12 +57,11 @@ void NtpSyncActivity::onEnter() {
WiFi.mode(WIFI_STA);
LOG_DBG("NTP", "Launching WifiSelectionActivity...");
startActivityForResult(
std::make_unique<WifiSelectionActivity>(renderer, mappedInput),
[this](const ActivityResult& result) {
const bool success = !result.isCancelled && std::holds_alternative<WifiResult>(result.data);
onWifiSelectionComplete(success);
});
startActivityForResult(std::make_unique<WifiSelectionActivity>(renderer, mappedInput),
[this](const ActivityResult& result) {
const bool success = !result.isCancelled && std::holds_alternative<WifiResult>(result.data);
onWifiSelectionComplete(success);
});
}
void NtpSyncActivity::onExit() {

View File

@@ -3,10 +3,10 @@
#include <GfxRenderer.h>
#include <I18n.h>
#include "activities/ActivityResult.h"
#include "MappedInputManager.h"
#include "OpdsServerStore.h"
#include "OpdsSettingsActivity.h"
#include "activities/ActivityResult.h"
#include "components/UITheme.h"
#include "fontIds.h"
@@ -65,12 +65,11 @@ void OpdsServerListActivity::handleSelection() {
}
const int idx = selectedIndex;
startActivityForResult(
std::make_unique<OpdsSettingsActivity>(renderer, mappedInput, idx < serverCount ? idx : -1),
[this](const ActivityResult&) {
selectedIndex = 0;
requestUpdate();
});
startActivityForResult(std::make_unique<OpdsSettingsActivity>(renderer, mappedInput, idx < serverCount ? idx : -1),
[this](const ActivityResult&) {
selectedIndex = 0;
requestUpdate();
});
}
void OpdsServerListActivity::render(RenderLock&&) {

View File

@@ -6,9 +6,9 @@
#include <cstring>
#include <string>
#include "activities/ActivityResult.h"
#include "MappedInputManager.h"
#include "OpdsServerStore.h"
#include "activities/ActivityResult.h"
#include "activities/util/DirectoryPickerActivity.h"
#include "activities/util/KeyboardEntryActivity.h"
#include "activities/util/NumericStepperActivity.h"
@@ -113,51 +113,47 @@ void OpdsSettingsActivity::handleSelection() {
});
} else if (selectedIndex == 2) {
// Server URL
startActivityForResult(
std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_OPDS_SERVER_URL), editServer.url, 127,
false),
[this](const ActivityResult& result) {
if (!result.isCancelled && std::holds_alternative<KeyboardResult>(result.data)) {
editServer.url = std::get<KeyboardResult>(result.data).text;
saveServer();
}
requestUpdate();
});
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_OPDS_SERVER_URL),
editServer.url, 127, false),
[this](const ActivityResult& result) {
if (!result.isCancelled && std::holds_alternative<KeyboardResult>(result.data)) {
editServer.url = std::get<KeyboardResult>(result.data).text;
saveServer();
}
requestUpdate();
});
} else if (selectedIndex == 3) {
// Username
startActivityForResult(
std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_USERNAME), editServer.username, 63,
false),
[this](const ActivityResult& result) {
if (!result.isCancelled && std::holds_alternative<KeyboardResult>(result.data)) {
editServer.username = std::get<KeyboardResult>(result.data).text;
saveServer();
}
requestUpdate();
});
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_USERNAME),
editServer.username, 63, false),
[this](const ActivityResult& result) {
if (!result.isCancelled && std::holds_alternative<KeyboardResult>(result.data)) {
editServer.username = std::get<KeyboardResult>(result.data).text;
saveServer();
}
requestUpdate();
});
} else if (selectedIndex == 4) {
// Password
startActivityForResult(
std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_PASSWORD), editServer.password, 63,
false),
[this](const ActivityResult& result) {
if (!result.isCancelled && std::holds_alternative<KeyboardResult>(result.data)) {
editServer.password = std::get<KeyboardResult>(result.data).text;
saveServer();
}
requestUpdate();
});
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_PASSWORD),
editServer.password, 63, false),
[this](const ActivityResult& result) {
if (!result.isCancelled && std::holds_alternative<KeyboardResult>(result.data)) {
editServer.password = std::get<KeyboardResult>(result.data).text;
saveServer();
}
requestUpdate();
});
} else if (selectedIndex == 5) {
// Download Path
startActivityForResult(
std::make_unique<DirectoryPickerActivity>(renderer, mappedInput, editServer.downloadPath),
[this](const ActivityResult& result) {
if (!result.isCancelled && std::holds_alternative<KeyboardResult>(result.data)) {
editServer.downloadPath = std::get<KeyboardResult>(result.data).text;
saveServer();
}
requestUpdate();
});
startActivityForResult(std::make_unique<DirectoryPickerActivity>(renderer, mappedInput, editServer.downloadPath),
[this](const ActivityResult& result) {
if (!result.isCancelled && std::holds_alternative<KeyboardResult>(result.data)) {
editServer.downloadPath = std::get<KeyboardResult>(result.data).text;
saveServer();
}
requestUpdate();
});
} else if (selectedIndex == 6) {
// After Download — toggle between 0 (back to listing) and 1 (open book)
editServer.afterDownloadAction = editServer.afterDownloadAction == 0 ? 1 : 0;
@@ -185,8 +181,8 @@ void OpdsSettingsActivity::render(RenderLock&&) {
const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing * 2;
const int menuItems = getMenuItemCount();
const StrId fieldNames[] = {StrId::STR_POSITION, StrId::STR_SERVER_NAME, StrId::STR_OPDS_SERVER_URL,
StrId::STR_USERNAME, StrId::STR_PASSWORD, StrId::STR_DOWNLOAD_PATH,
const StrId fieldNames[] = {StrId::STR_POSITION, StrId::STR_SERVER_NAME, StrId::STR_OPDS_SERVER_URL,
StrId::STR_USERNAME, StrId::STR_PASSWORD, StrId::STR_DOWNLOAD_PATH,
StrId::STR_AFTER_DOWNLOAD};
GUI.drawList(

View File

@@ -13,7 +13,6 @@ class SetTimeActivity final : public Activity {
void render(RenderLock&&) override;
private:
// 0 = editing hours, 1 = editing minutes
uint8_t selectedField = 0;
int hour = 12;