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

@@ -160,8 +160,14 @@ LetterboxFillData computeEdgeAverages(const Bitmap& bitmap, int imgX, int imgY,
for (int bmpX = cropPixX; bmpX < bitmap.getWidth() - cropPixX; bmpX++) {
const uint8_t val = (outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8))) & 0x3;
const uint8_t gray = val2bitToGray(val);
if (inTop) { sumTop += gray; countTop++; }
if (inBot) { sumBot += gray; countBot++; }
if (inTop) {
sumTop += gray;
countTop++;
}
if (inBot) {
sumBot += gray;
countBot++;
}
}
}
@@ -550,8 +556,8 @@ void SleepActivity::renderCoverSleepScreen() const {
if (!lastEpub.generateCoverBmp(cropped)) {
LOG_DBG("SLP", "EPUB cover generation failed, trying placeholder");
PlaceholderCoverGenerator::generate(lastEpub.getCoverBmpPath(cropped), lastEpub.getTitle(),
lastEpub.getAuthor(), 480, 800);
PlaceholderCoverGenerator::generate(lastEpub.getCoverBmpPath(cropped), lastEpub.getTitle(), lastEpub.getAuthor(),
480, 800);
}
if (!Storage.exists(lastEpub.getCoverBmpPath(cropped).c_str())) {

View File

@@ -7,9 +7,9 @@
#include <OpdsStream.h>
#include <WiFi.h>
#include "activities/ActivityResult.h"
#include "MappedInputManager.h"
#include "OpdsServerStore.h"
#include "activities/ActivityResult.h"
#include "activities/network/WifiSelectionActivity.h"
#include "activities/util/DirectoryPickerActivity.h"
#include "components/UITheme.h"
@@ -364,9 +364,8 @@ void OpdsBookBrowserActivity::launchDirectoryPicker(const OpdsEntry& book) {
state = BrowserState::PICKING_DIRECTORY;
requestUpdate();
startActivityForResult(
std::make_unique<DirectoryPickerActivity>(renderer, mappedInput, server.downloadPath),
[this](const ActivityResult& result) { onDirectoryPickerResult(result); });
startActivityForResult(std::make_unique<DirectoryPickerActivity>(renderer, mappedInput, server.downloadPath),
[this](const ActivityResult& result) { onDirectoryPickerResult(result); });
}
void OpdsBookBrowserActivity::onDirectoryPickerResult(const ActivityResult& result) {

View File

@@ -17,18 +17,18 @@
class OpdsBookBrowserActivity final : public Activity {
public:
enum class BrowserState {
CHECK_WIFI, // Checking WiFi connection
WIFI_SELECTION, // WiFi selection subactivity is active
LOADING, // Fetching OPDS feed
BROWSING, // Displaying entries (navigation or books)
PICKING_DIRECTORY, // Directory picker subactivity is active
DOWNLOADING, // Downloading selected EPUB
DOWNLOAD_COMPLETE, // Prompt: open book or go back to listing
ERROR // Error state with message
CHECK_WIFI, // Checking WiFi connection
WIFI_SELECTION, // WiFi selection subactivity is active
LOADING, // Fetching OPDS feed
BROWSING, // Displaying entries (navigation or books)
PICKING_DIRECTORY, // Directory picker subactivity is active
DOWNLOADING, // Downloading selected EPUB
DOWNLOAD_COMPLETE, // Prompt: open book or go back to listing
ERROR // Error state with message
};
explicit OpdsBookBrowserActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const OpdsServer* server = nullptr)
const OpdsServer* server = nullptr)
: Activity("OpdsBookBrowser", renderer, mappedInput), server(server ? *server : OpdsServer{}) {}
void onEnter() override;

View File

@@ -0,0 +1,160 @@
#include "BookInfoActivity.h"
#include <Epub.h>
#include <FsHelpers.h>
#include <GfxRenderer.h>
#include <HalStorage.h>
#include <I18n.h>
#include <Logging.h>
#include "components/UITheme.h"
#include "fontIds.h"
void BookInfoActivity::onEnter() {
Activity::onEnter();
std::string fileName = filePath;
const size_t lastSlash = filePath.rfind('/');
if (lastSlash != std::string::npos) {
fileName = filePath.substr(lastSlash + 1);
}
FsFile file;
if (Storage.openFileForRead("BIF", filePath, file)) {
fileSize = file.fileSize();
file.close();
}
if (FsHelpers::hasEpubExtension(fileName)) {
Epub epub(filePath, "/.crosspoint");
if (epub.load(false, true)) {
title = epub.getTitle();
author = epub.getAuthor();
series = epub.getSeries();
seriesIndex = epub.getSeriesIndex();
description = epub.getDescription();
language = epub.getLanguage();
}
}
if (title.empty()) {
title = fileName;
}
requestUpdate();
}
void BookInfoActivity::onExit() { Activity::onExit(); }
void BookInfoActivity::loop() {
if (mappedInput.wasReleased(MappedInputManager::Button::Back) ||
mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
ActivityResult r;
r.isCancelled = true;
setResult(std::move(r));
finish();
return;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Down)) {
if (scrollOffset + renderer.getScreenHeight() < contentHeight) {
scrollOffset += renderer.getScreenHeight() / 3;
requestUpdate();
}
}
if (mappedInput.wasReleased(MappedInputManager::Button::Up)) {
if (scrollOffset > 0) {
scrollOffset -= renderer.getScreenHeight() / 3;
if (scrollOffset < 0) scrollOffset = 0;
requestUpdate();
}
}
}
void BookInfoActivity::render(RenderLock&&) {
renderer.clearScreen();
const int pageW = renderer.getScreenWidth();
const int pageH = renderer.getScreenHeight();
constexpr int margin = 20;
constexpr int labelValueGap = 4;
constexpr int sectionGap = 14;
constexpr int maxWrappedLines = 30;
const int contentW = pageW - margin * 2;
const int lineH10 = renderer.getLineHeight(UI_10_FONT_ID);
const int lineH12 = renderer.getLineHeight(UI_12_FONT_ID);
int y = margin - scrollOffset;
auto drawLabel = [&](const char* label) {
renderer.drawText(UI_10_FONT_ID, margin, y, label, true, EpdFontFamily::BOLD);
y += lineH10 + labelValueGap;
};
auto drawWrapped = [&](int fontId, const std::string& text, int lineH, EpdFontFamily::Style style) {
auto lines = renderer.wrappedText(fontId, text.c_str(), contentW, maxWrappedLines, style);
for (const auto& line : lines) {
renderer.drawText(fontId, margin, y, line.c_str(), true, style);
y += lineH;
}
y += sectionGap;
};
// Title
drawWrapped(UI_12_FONT_ID, title, lineH12, EpdFontFamily::BOLD);
// Author
if (!author.empty()) {
drawLabel(tr(STR_AUTHOR));
drawWrapped(UI_12_FONT_ID, author, lineH12, EpdFontFamily::REGULAR);
}
// Series
if (!series.empty()) {
drawLabel(tr(STR_SERIES));
std::string seriesStr = series;
if (!seriesIndex.empty()) {
seriesStr += " #" + seriesIndex;
}
drawWrapped(UI_12_FONT_ID, seriesStr, lineH12, EpdFontFamily::REGULAR);
}
// Language
if (!language.empty()) {
drawLabel(tr(STR_LANGUAGE));
drawWrapped(UI_12_FONT_ID, language, lineH12, EpdFontFamily::REGULAR);
}
// File size
if (fileSize > 0) {
drawLabel(tr(STR_FILE_SIZE));
drawWrapped(UI_12_FONT_ID, formatFileSize(fileSize), lineH12, EpdFontFamily::REGULAR);
}
// Description
if (!description.empty()) {
drawLabel(tr(STR_DESCRIPTION));
drawWrapped(UI_12_FONT_ID, description, lineH12, EpdFontFamily::REGULAR);
}
contentHeight = y + scrollOffset;
// Button hints
const char* scrollHint = contentHeight > pageH ? tr(STR_DIR_DOWN) : "";
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", scrollHint, "");
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();
}
std::string BookInfoActivity::formatFileSize(size_t bytes) {
char buf[32];
if (bytes < 1024) {
snprintf(buf, sizeof(buf), "%u B", static_cast<unsigned>(bytes));
} else if (bytes < 1024 * 1024) {
snprintf(buf, sizeof(buf), "%.1f KB", static_cast<float>(bytes) / 1024.0f);
} else {
snprintf(buf, sizeof(buf), "%.1f MB", static_cast<float>(bytes) / (1024.0f * 1024.0f));
}
return buf;
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include <string>
#include "../Activity.h"
class BookInfoActivity final : public Activity {
public:
explicit BookInfoActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string filePath)
: Activity("BookInfo", renderer, mappedInput), filePath(std::move(filePath)) {}
void onEnter() override;
void onExit() override;
void loop() override;
void render(RenderLock&&) override;
private:
std::string filePath;
std::string title;
std::string author;
std::string series;
std::string seriesIndex;
std::string description;
std::string language;
size_t fileSize = 0;
int scrollOffset = 0;
int contentHeight = 0;
static std::string formatFileSize(size_t bytes);
};

View File

@@ -3,8 +3,8 @@
#include <GfxRenderer.h>
#include <I18n.h>
#include "activities/ActivityResult.h"
#include "MappedInputManager.h"
#include "activities/ActivityResult.h"
#include "components/UITheme.h"
#include "fontIds.h"

View File

@@ -19,9 +19,8 @@ class BookManageMenuActivity final : public Activity {
REINDEX_FULL,
};
explicit BookManageMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::string& bookPath, bool isArchived,
bool initialSkipRelease = false)
explicit BookManageMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& bookPath,
bool isArchived, bool initialSkipRelease = false)
: Activity("BookManageMenu", renderer, mappedInput),
bookPath(bookPath),
archived(isArchived),

View File

@@ -9,9 +9,12 @@
#include <algorithm>
#include "../util/ConfirmationActivity.h"
#include "BookInfoActivity.h"
#include "BookManageMenuActivity.h"
#include "MappedInputManager.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "util/BookManager.h"
namespace {
constexpr unsigned long GO_HOME_MS = 1000;
@@ -126,6 +129,67 @@ void FileBrowserActivity::clearFileMetadata(const std::string& fullPath) {
}
}
void FileBrowserActivity::handleManageResult(const std::string& fullPath, const ActivityResult& result) {
if (result.isCancelled) {
requestUpdate();
return;
}
const auto& menuResult = std::get<MenuResult>(result.data);
auto action = static_cast<BookManageMenuActivity::Action>(menuResult.action);
auto executeAction = [this, action, fullPath]() {
bool success = false;
switch (action) {
case BookManageMenuActivity::Action::ARCHIVE:
success = BookManager::archiveBook(fullPath);
break;
case BookManageMenuActivity::Action::UNARCHIVE:
success = BookManager::unarchiveBook(fullPath);
break;
case BookManageMenuActivity::Action::DELETE:
success = BookManager::deleteBook(fullPath);
break;
case BookManageMenuActivity::Action::DELETE_CACHE:
success = BookManager::deleteBookCache(fullPath);
break;
case BookManageMenuActivity::Action::REINDEX:
success = BookManager::reindexBook(fullPath, false);
break;
case BookManageMenuActivity::Action::REINDEX_FULL:
success = BookManager::reindexBook(fullPath, true);
break;
}
{
RenderLock lock(*this);
GUI.drawPopup(renderer, success ? tr(STR_DONE) : tr(STR_ACTION_FAILED));
}
requestUpdateAndWait();
loadFiles();
if (selectorIndex >= files.size()) {
selectorIndex = files.empty() ? 0 : files.size() - 1;
}
requestUpdate();
};
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 = fullPath;
const auto slash = fullPath.rfind('/');
if (slash != std::string::npos) fileName = fullPath.substr(slash + 1);
startActivityForResult(std::make_unique<ConfirmationActivity>(renderer, mappedInput, heading, fileName),
[executeAction](const ActivityResult& confirmResult) {
if (!confirmResult.isCancelled) {
executeAction();
}
});
} else {
executeAction();
}
}
void FileBrowserActivity::loop() {
// Long press BACK (1s+) goes to root folder
if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= GO_HOME_MS &&
@@ -138,63 +202,60 @@ void FileBrowserActivity::loop() {
const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false);
// Confirm = open file or enter directory
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
if (files.empty()) return;
const std::string& entry = files[selectorIndex];
bool isDirectory = (entry.back() == '/');
std::string cleanBasePath = basepath;
if (cleanBasePath.back() != '/') cleanBasePath += "/";
if (mappedInput.getHeldTime() >= GO_HOME_MS && !isDirectory) {
// --- LONG PRESS ACTION: DELETE FILE ---
std::string cleanBasePath = basepath;
if (cleanBasePath.back() != '/') cleanBasePath += "/";
const std::string fullPath = cleanBasePath + entry;
auto handler = [this, fullPath](const ActivityResult& res) {
if (!res.isCancelled) {
LOG_DBG("FileBrowser", "Attempting to delete: %s", fullPath.c_str());
clearFileMetadata(fullPath);
if (Storage.remove(fullPath.c_str())) {
LOG_DBG("FileBrowser", "Deleted successfully");
loadFiles();
if (files.empty()) {
selectorIndex = 0;
} else if (selectorIndex >= files.size()) {
// Move selection to the new "last" item
selectorIndex = files.size() - 1;
}
requestUpdate(true);
} else {
LOG_ERR("FileBrowser", "Failed to delete file: %s", fullPath.c_str());
}
} else {
LOG_DBG("FileBrowser", "Delete cancelled by user");
}
};
std::string heading = tr(STR_DELETE) + std::string("? ");
startActivityForResult(std::make_unique<ConfirmationActivity>(renderer, mappedInput, heading, entry), handler);
return;
if (entry.back() == '/') {
basepath = cleanBasePath + entry.substr(0, entry.length() - 1);
loadFiles();
selectorIndex = 0;
requestUpdate();
} else {
// --- SHORT PRESS ACTION: OPEN/NAVIGATE ---
if (basepath.back() != '/') basepath += "/";
if (isDirectory) {
basepath += entry.substr(0, entry.length() - 1);
loadFiles();
selectorIndex = 0;
requestUpdate();
} else {
onSelectBook(basepath + entry);
}
onSelectBook(cleanBasePath + entry);
}
return;
}
// Left = Manage (files only)
if (mappedInput.wasReleased(MappedInputManager::Button::Left)) {
if (files.empty()) return;
const std::string& entry = files[selectorIndex];
if (entry.back() == '/') return;
std::string cleanBasePath = basepath;
if (cleanBasePath.back() != '/') cleanBasePath += "/";
const std::string fullPath = cleanBasePath + entry;
const bool isArchived = BookManager::isArchived(fullPath);
startActivityForResult(std::make_unique<BookManageMenuActivity>(renderer, mappedInput, fullPath, isArchived),
[this, fullPath](const ActivityResult& res) { handleManageResult(fullPath, res); });
return;
}
// Right = Book Info (EPUB/XTC only)
if (mappedInput.wasReleased(MappedInputManager::Button::Right)) {
if (files.empty()) return;
const std::string& entry = files[selectorIndex];
if (entry.back() == '/') return;
if (FsHelpers::hasEpubExtension(entry) || FsHelpers::hasXtcExtension(entry)) {
std::string cleanBasePath = basepath;
if (cleanBasePath.back() != '/') cleanBasePath += "/";
const std::string fullPath = cleanBasePath + entry;
startActivityForResult(std::make_unique<BookInfoActivity>(renderer, mappedInput, fullPath),
[this](const ActivityResult&) { requestUpdate(); });
return;
}
}
// Back = go up one directory, or go home if at root
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
// Short press: go up one directory, or go home if at root
if (mappedInput.getHeldTime() < GO_HOME_MS) {
if (basepath != "/") {
const std::string oldPath = basepath;
@@ -275,10 +336,24 @@ void FileBrowserActivity::render(RenderLock&&) {
[this](int index) { return getFileExtension(files[index]); }, false);
}
// Help text
const auto labels =
mappedInput.mapLabels(basepath == "/" ? tr(STR_HOME) : tr(STR_BACK), files.empty() ? "" : tr(STR_OPEN),
files.empty() ? "" : tr(STR_DIR_UP), files.empty() ? "" : tr(STR_DIR_DOWN));
// Build contextual button hints
const char* btn1Label = basepath == "/" ? tr(STR_HOME) : tr(STR_BACK);
const char* btn2Label = files.empty() ? "" : tr(STR_OPEN);
const char* btn3Label = "";
const char* btn4Label = "";
if (!files.empty()) {
const std::string& entry = files[selectorIndex];
bool isDir = entry.back() == '/';
if (!isDir) {
btn3Label = tr(STR_MANAGE);
if (FsHelpers::hasEpubExtension(entry) || FsHelpers::hasXtcExtension(entry)) {
btn4Label = tr(STR_INFO);
}
}
}
const auto labels = mappedInput.mapLabels(btn1Label, btn2Label, btn3Label, btn4Label);
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();

View File

@@ -10,8 +10,8 @@
class FileBrowserActivity final : public Activity {
private:
// Deletion
void clearFileMetadata(const std::string& fullPath);
void handleManageResult(const std::string& fullPath, const ActivityResult& result);
ButtonNavigator buttonNavigator;

View File

@@ -14,6 +14,7 @@
#include <cstring>
#include <vector>
#include "../util/ConfirmationActivity.h"
#include "BookManageMenuActivity.h"
#include "CrossPointSettings.h"
#include "CrossPointState.h"
@@ -25,7 +26,6 @@
#include "fontIds.h"
#include "util/BookManager.h"
int HomeActivity::getMenuItemCount() const {
int count = 4; // File Browser, Recents, File transfer, Settings
if (!recentBooks.empty()) {
@@ -83,7 +83,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
success = epub.generateThumbBmp(coverHeight);
if (success) {
const std::string thumbPath = epub.getThumbBmpPath(coverHeight);
RECENT_BOOKS.updateBook(book.path, book.title, book.author, thumbPath);
RECENT_BOOKS.updateBook(book.path, book.title, book.author, book.series, thumbPath);
book.coverBmpPath = thumbPath;
} else {
const int thumbWidth = static_cast<int>(coverHeight * 0.6);
@@ -95,7 +95,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
success = xtc.generateThumbBmp(coverHeight);
if (success) {
const std::string thumbPath = xtc.getThumbBmpPath(coverHeight);
RECENT_BOOKS.updateBook(book.path, book.title, book.author, thumbPath);
RECENT_BOOKS.updateBook(book.path, book.title, book.author, book.series, thumbPath);
book.coverBmpPath = thumbPath;
}
}
@@ -301,6 +301,43 @@ void HomeActivity::onFileTransferOpen() { activityManager.goToFileTransfer(); }
void HomeActivity::onOpdsBrowserOpen() { activityManager.goToBrowser(); }
void HomeActivity::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));
}
requestUpdateAndWait();
recentsLoaded = false;
recentsLoading = false;
coverRendered = false;
freeCoverBuffer();
selectorIndex = 0;
firstRenderDone = false;
loadRecentBooks(UITheme::getInstance().getMetrics().homeRecentBooksCount);
requestUpdate();
}
void HomeActivity::openManageMenu(const std::string& bookPath) {
const bool isArchived = BookManager::isArchived(bookPath);
const std::string capturedPath = bookPath;
@@ -314,39 +351,25 @@ void HomeActivity::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));
}
requestUpdateAndWait();
recentsLoaded = false;
recentsLoading = false;
coverRendered = false;
freeCoverBuffer();
selectorIndex = 0;
firstRenderDone = false;
loadRecentBooks(UITheme::getInstance().getMetrics().homeRecentBooksCount);
requestUpdate();
});
}

View File

@@ -3,6 +3,7 @@
#include <vector>
#include "../Activity.h"
#include "./BookManageMenuActivity.h"
#include "./FileBrowserActivity.h"
#include "util/ButtonNavigator.h"
@@ -31,6 +32,7 @@ class HomeActivity final : public Activity {
void onFileTransferOpen();
void onOpdsBrowserOpen();
void openManageMenu(const std::string& bookPath);
void executeManageAction(BookManageMenuActivity::Action action, const std::string& capturedPath);
int getMenuItemCount() const;
bool storeCoverBuffer();

View File

@@ -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); });
}

View File

@@ -6,6 +6,7 @@
#include <vector>
#include "../Activity.h"
#include "BookManageMenuActivity.h"
#include "RecentBooksStore.h"
#include "util/ButtonNavigator.h"
@@ -23,6 +24,7 @@ class RecentBooksActivity final : public Activity {
void loadRecentBooks();
void openManageMenu(const std::string& bookPath);
void executeManageAction(BookManageMenuActivity::Action action, const std::string& capturedPath);
public:
explicit RecentBooksActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)

View File

@@ -3,13 +3,12 @@
#include <GfxRenderer.h>
#include <algorithm>
#include "activities/ActivityResult.h"
#include <cctype>
#include <cstdlib>
#include "CrossPointSettings.h"
#include "MappedInputManager.h"
#include "activities/ActivityResult.h"
#include "components/UITheme.h"
#include "fontIds.h"
@@ -478,7 +477,8 @@ void DictionaryDefinitionActivity::render(RenderLock&&) {
}
// Button hints (bottom face buttons)
const auto labels = mappedInput.mapLabels("\xC2\xAB Back", hasDoneButton ? "Done" : "", "\xC2\xAB Page", "Page \xC2\xBB");
const auto labels =
mappedInput.mapLabels("\xC2\xAB Back", hasDoneButton ? "Done" : "", "\xC2\xAB Page", "Page \xC2\xBB");
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
// Side button hints (drawn in portrait coordinates for correct placement)

View File

@@ -3,8 +3,8 @@
#include <GfxRenderer.h>
#include <I18n.h>
#include "activities/ActivityResult.h"
#include "MappedInputManager.h"
#include "activities/ActivityResult.h"
#include "components/UITheme.h"
#include "fontIds.h"

View File

@@ -53,17 +53,16 @@ void DictionarySuggestionsActivity::loop() {
return;
}
startActivityForResult(
std::make_unique<DictionaryDefinitionActivity>(renderer, mappedInput, selected, definition, readerFontId,
orientation, true),
[this](ActivityResult result) {
if (!result.isCancelled) {
setResult(std::move(result));
finish();
} else {
requestUpdate();
}
});
startActivityForResult(std::make_unique<DictionaryDefinitionActivity>(renderer, mappedInput, selected, definition,
readerFontId, orientation, true),
[this](ActivityResult result) {
if (!result.isCancelled) {
setResult(std::move(result));
finish();
} else {
requestUpdate();
}
});
return;
}

View File

@@ -5,11 +5,11 @@
#include <algorithm>
#include <climits>
#include "activities/ActivityResult.h"
#include "CrossPointSettings.h"
#include "DictionaryDefinitionActivity.h"
#include "DictionarySuggestionsActivity.h"
#include "MappedInputManager.h"
#include "activities/ActivityResult.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "util/Dictionary.h"
@@ -350,17 +350,16 @@ void DictionaryWordSelectActivity::loop() {
LookupHistory::addWord(cachePath, cleaned);
if (!definition.empty()) {
startActivityForResult(
std::make_unique<DictionaryDefinitionActivity>(renderer, mappedInput, cleaned, definition, fontId, orientation,
true),
[this](ActivityResult result) {
if (!result.isCancelled) {
setResult(std::move(result));
finish();
} else {
requestUpdate();
}
});
startActivityForResult(std::make_unique<DictionaryDefinitionActivity>(renderer, mappedInput, cleaned, definition,
fontId, orientation, true),
[this](ActivityResult result) {
if (!result.isCancelled) {
setResult(std::move(result));
finish();
} else {
requestUpdate();
}
});
return;
}
@@ -369,17 +368,16 @@ void DictionaryWordSelectActivity::loop() {
for (const auto& stem : stems) {
std::string stemDef = Dictionary::lookup(stem);
if (!stemDef.empty()) {
startActivityForResult(
std::make_unique<DictionaryDefinitionActivity>(renderer, mappedInput, stem, stemDef, fontId, orientation,
true),
[this](ActivityResult result) {
if (!result.isCancelled) {
setResult(std::move(result));
finish();
} else {
requestUpdate();
}
});
startActivityForResult(std::make_unique<DictionaryDefinitionActivity>(renderer, mappedInput, stem, stemDef,
fontId, orientation, true),
[this](ActivityResult result) {
if (!result.isCancelled) {
setResult(std::move(result));
finish();
} else {
requestUpdate();
}
});
return;
}
}
@@ -387,17 +385,16 @@ void DictionaryWordSelectActivity::loop() {
// Find similar words for suggestions
auto similar = Dictionary::findSimilar(cleaned, 6);
if (!similar.empty()) {
startActivityForResult(
std::make_unique<DictionarySuggestionsActivity>(renderer, mappedInput, cleaned, similar, fontId, orientation,
cachePath),
[this](ActivityResult result) {
if (!result.isCancelled) {
setResult(std::move(result));
finish();
} else {
requestUpdate();
}
});
startActivityForResult(std::make_unique<DictionarySuggestionsActivity>(renderer, mappedInput, cleaned, similar,
fontId, orientation, cachePath),
[this](ActivityResult result) {
if (!result.isCancelled) {
setResult(std::move(result));
finish();
} else {
requestUpdate();
}
});
return;
}

View File

@@ -3,8 +3,8 @@
#include <GfxRenderer.h>
#include <I18n.h>
#include "activities/ActivityResult.h"
#include "MappedInputManager.h"
#include "activities/ActivityResult.h"
#include "components/UITheme.h"
#include "fontIds.h"
@@ -66,9 +66,8 @@ void EndOfBookMenuActivity::render(RenderLock&&) {
const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing;
const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing;
GUI.drawList(
renderer, Rect{0, contentTop, pageWidth, contentHeight}, static_cast<int>(menuItems.size()), selectedIndex,
[this](int index) { return std::string(I18N.get(menuItems[index].labelId)); });
GUI.drawList(renderer, Rect{0, contentTop, pageWidth, contentHeight}, static_cast<int>(menuItems.size()),
selectedIndex, [this](int index) { return std::string(I18N.get(menuItems[index].labelId)); });
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);

View File

@@ -3,16 +3,14 @@
#include <Epub/Page.h>
#include <Epub/blocks/TextBlock.h>
#include <FsHelpers.h>
#include <PlaceholderCoverGenerator.h>
#include <GfxRenderer.h>
#include <HalStorage.h>
#include <I18n.h>
#include <Logging.h>
#include <PlaceholderCoverGenerator.h>
#include "CrossPointSettings.h"
#include "CrossPointState.h"
#include "activities/ActivityManager.h"
#include "activities/home/BookManageMenuActivity.h"
#include "DictionaryMenuActivity.h"
#include "DictionaryWordSelectActivity.h"
#include "EndOfBookMenuActivity.h"
@@ -27,6 +25,9 @@
#include "QrDisplayActivity.h"
#include "ReaderUtils.h"
#include "RecentBooksStore.h"
#include "activities/ActivityManager.h"
#include "activities/home/BookManageMenuActivity.h"
#include "activities/util/ConfirmationActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "util/BookManager.h"
@@ -153,7 +154,13 @@ void EpubReaderActivity::onEnter() {
// Save current epub as last opened epub and add to recent books
APP_STATE.openEpubPath = epub->getPath();
APP_STATE.saveToFile();
RECENT_BOOKS.addBook(epub->getPath(), epub->getTitle(), epub->getAuthor(), epub->getThumbBmpPath());
{
std::string epubSeries = epub->getSeries();
if (!epubSeries.empty() && !epub->getSeriesIndex().empty()) {
epubSeries += " #" + epub->getSeriesIndex();
}
RECENT_BOOKS.addBook(epub->getPath(), epub->getTitle(), epub->getAuthor(), epubSeries, epub->getThumbBmpPath());
}
// Trigger first update
requestUpdate();
@@ -189,14 +196,30 @@ void EpubReaderActivity::loop() {
}
const auto action = static_cast<EndOfBookMenuActivity::Action>(std::get<MenuResult>(result.data).action);
switch (action) {
case EndOfBookMenuActivity::Action::ARCHIVE:
BookManager::archiveBook(epub->getPath());
activityManager.goHome();
case EndOfBookMenuActivity::Action::ARCHIVE: {
std::string heading = std::string(tr(STR_ARCHIVE_BOOK)) + "?";
std::string bookName = epub->getTitle();
startActivityForResult(std::make_unique<ConfirmationActivity>(renderer, mappedInput, heading, bookName),
[this](const ActivityResult& confirmResult) {
if (!confirmResult.isCancelled && epub) {
BookManager::archiveBook(epub->getPath());
}
activityManager.goHome();
});
return;
case EndOfBookMenuActivity::Action::DELETE:
BookManager::deleteBook(epub->getPath());
activityManager.goHome();
}
case EndOfBookMenuActivity::Action::DELETE: {
std::string heading = std::string(tr(STR_DELETE_BOOK)) + "?";
std::string bookName = epub->getTitle();
startActivityForResult(std::make_unique<ConfirmationActivity>(renderer, mappedInput, heading, bookName),
[this](const ActivityResult& confirmResult) {
if (!confirmResult.isCancelled && epub) {
BookManager::deleteBook(epub->getPath());
}
activityManager.goHome();
});
return;
}
case EndOfBookMenuActivity::Action::TABLE_OF_CONTENTS: {
endOfBookMenuOpened = false;
RenderLock lock(*this);
@@ -276,20 +299,20 @@ void EpubReaderActivity::loop() {
const int bookProgressPercent = clampPercent(static_cast<int>(bookProgress + 0.5f));
const bool isBookmarked =
section ? BookmarkStore::hasBookmark(epub->getCachePath(), currentSpineIndex, section->currentPage) : false;
startActivityForResult(std::make_unique<EpubReaderMenuActivity>(
renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent,
SETTINGS.orientation, !currentPageFootnotes.empty(), isBookmarked, SETTINGS.fontSize,
epub->getCachePath()),
[this](const ActivityResult& result) {
// Always apply orientation and font size even if the menu was cancelled
const auto& menu = std::get<MenuResult>(result.data);
applyOrientation(menu.orientation);
applyFontSize(menu.fontSize);
toggleAutoPageTurn(menu.pageTurnOption);
if (!result.isCancelled) {
onReaderMenuConfirm(static_cast<EpubReaderMenuActivity::MenuAction>(menu.action));
}
});
startActivityForResult(
std::make_unique<EpubReaderMenuActivity>(
renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent, SETTINGS.orientation,
!currentPageFootnotes.empty(), isBookmarked, SETTINGS.fontSize, epub->getCachePath()),
[this](const ActivityResult& result) {
// Always apply orientation and font size even if the menu was cancelled
const auto& menu = std::get<MenuResult>(result.data);
applyOrientation(menu.orientation);
applyFontSize(menu.fontSize);
toggleAutoPageTurn(menu.pageTurnOption);
if (!result.isCancelled) {
onReaderMenuConfirm(static_cast<EpubReaderMenuActivity::MenuAction>(menu.action));
}
});
}
// Long press BACK (1s+) goes to file selection
@@ -327,8 +350,8 @@ void EpubReaderActivity::loop() {
if (skipChapter) {
lastPageTurnTime = millis();
int currentTocIdx = section ? section->getTocIndexForPage(section->currentPage)
: epub->getTocIndexForSpineIndex(currentSpineIndex);
int currentTocIdx =
section ? section->getTocIndexForPage(section->currentPage) : epub->getTocIndexForSpineIndex(currentSpineIndex);
const int nextTocIdx = nextTriggered ? currentTocIdx + 1 : currentTocIdx - 1;
if (nextTocIdx >= 0 && nextTocIdx < epub->getTocItemsCount()) {
@@ -448,24 +471,23 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
const std::string path = epub->getPath();
const bool consumeRelease = ignoreNextConfirmRelease;
ignoreNextConfirmRelease = false;
startActivityForResult(
std::make_unique<EpubReaderChapterSelectionActivity>(renderer, mappedInput, epub, path, spineIdx, tocIdx,
consumeRelease),
[this](const ActivityResult& result) {
if (result.isCancelled) return;
const auto& ch = std::get<ChapterResult>(result.data);
RenderLock lock(*this);
if (ch.spineIndex == currentSpineIndex && section && ch.tocIndex >= 0) {
if (auto page = section->getPageForTocIndex(ch.tocIndex)) {
section->currentPage = *page;
}
} else if (ch.spineIndex != currentSpineIndex) {
currentSpineIndex = ch.spineIndex;
pendingTocIndex = ch.tocIndex;
nextPageNumber = 0;
section.reset();
}
});
startActivityForResult(std::make_unique<EpubReaderChapterSelectionActivity>(renderer, mappedInput, epub, path,
spineIdx, tocIdx, consumeRelease),
[this](const ActivityResult& result) {
if (result.isCancelled) return;
const auto& ch = std::get<ChapterResult>(result.data);
RenderLock lock(*this);
if (ch.spineIndex == currentSpineIndex && section && ch.tocIndex >= 0) {
if (auto page = section->getPageForTocIndex(ch.tocIndex)) {
section->currentPage = *page;
}
} else if (ch.spineIndex != currentSpineIndex) {
currentSpineIndex = ch.spineIndex;
pendingTocIndex = ch.tocIndex;
nextPageNumber = 0;
section.reset();
}
});
break;
}
case EpubReaderMenuActivity::MenuAction::FOOTNOTES: {
@@ -604,7 +626,7 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
auto bookmarks = BookmarkStore::load(epub->getCachePath());
startActivityForResult(
std::make_unique<EpubReaderBookmarkSelectionActivity>(renderer, mappedInput, epub, std::move(bookmarks),
epub->getCachePath()),
epub->getCachePath()),
[this](const ActivityResult& result) {
if (!result.isCancelled) {
const auto& sync = std::get<SyncResult>(result.data);
@@ -633,11 +655,10 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
&orientedMarginLeft);
orientedMarginTop += SETTINGS.screenMargin;
orientedMarginLeft += SETTINGS.screenMargin;
startActivityForResult(
std::make_unique<DictionaryWordSelectActivity>(
renderer, mappedInput, std::move(p), SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop,
epub->getCachePath(), SETTINGS.orientation, ""),
[this](const ActivityResult&) { requestUpdate(); });
startActivityForResult(std::make_unique<DictionaryWordSelectActivity>(
renderer, mappedInput, std::move(p), SETTINGS.getReaderFontId(), orientedMarginLeft,
orientedMarginTop, epub->getCachePath(), SETTINGS.orientation, ""),
[this](const ActivityResult&) { requestUpdate(); });
break;
}
case EpubReaderMenuActivity::MenuAction::LOOKUP_HISTORY: {
@@ -645,10 +666,9 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
requestUpdate();
break;
}
startActivityForResult(
std::make_unique<LookedUpWordsActivity>(renderer, mappedInput, epub->getCachePath(),
SETTINGS.getReaderFontId(), SETTINGS.orientation),
[this](const ActivityResult&) { requestUpdate(); });
startActivityForResult(std::make_unique<LookedUpWordsActivity>(renderer, mappedInput, epub->getCachePath(),
SETTINGS.getReaderFontId(), SETTINGS.orientation),
[this](const ActivityResult&) { requestUpdate(); });
break;
}
case EpubReaderMenuActivity::MenuAction::DELETE_DICT_CACHE: {
@@ -659,13 +679,29 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
break;
}
case EpubReaderMenuActivity::MenuAction::ARCHIVE_BOOK: {
if (epub) BookManager::archiveBook(epub->getPath());
activityManager.goHome();
if (!epub) break;
std::string heading = std::string(tr(STR_ARCHIVE_BOOK)) + "?";
std::string bookName = epub->getTitle();
startActivityForResult(std::make_unique<ConfirmationActivity>(renderer, mappedInput, heading, bookName),
[this](const ActivityResult& confirmResult) {
if (!confirmResult.isCancelled && epub) {
BookManager::archiveBook(epub->getPath());
}
activityManager.goHome();
});
return;
}
case EpubReaderMenuActivity::MenuAction::DELETE_BOOK: {
if (epub) BookManager::deleteBook(epub->getPath());
activityManager.goHome();
if (!epub) break;
std::string heading = std::string(tr(STR_DELETE_BOOK)) + "?";
std::string bookName = epub->getTitle();
startActivityForResult(std::make_unique<ConfirmationActivity>(renderer, mappedInput, heading, bookName),
[this](const ActivityResult& confirmResult) {
if (!confirmResult.isCancelled && epub) {
BookManager::deleteBook(epub->getPath());
}
activityManager.goHome();
});
return;
}
case EpubReaderMenuActivity::MenuAction::REINDEX_BOOK: {
@@ -686,18 +722,34 @@ 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::ARCHIVE:
BookManager::archiveBook(epub->getPath());
activityManager.goHome();
case BookManageMenuActivity::Action::ARCHIVE: {
std::string heading = std::string(tr(STR_ARCHIVE_BOOK)) + "?";
std::string bookName = epub ? epub->getTitle() : "";
startActivityForResult(std::make_unique<ConfirmationActivity>(renderer, mappedInput, heading, bookName),
[this](const ActivityResult& confirmResult) {
if (!confirmResult.isCancelled && epub) {
BookManager::archiveBook(epub->getPath());
}
activityManager.goHome();
});
return;
}
case BookManageMenuActivity::Action::UNARCHIVE:
BookManager::unarchiveBook(epub->getPath());
if (epub) BookManager::unarchiveBook(epub->getPath());
activityManager.goHome();
return;
case BookManageMenuActivity::Action::DELETE:
BookManager::deleteBook(epub->getPath());
activityManager.goHome();
case BookManageMenuActivity::Action::DELETE: {
std::string heading = std::string(tr(STR_DELETE_BOOK)) + "?";
std::string bookName = epub ? epub->getTitle() : "";
startActivityForResult(std::make_unique<ConfirmationActivity>(renderer, mappedInput, heading, bookName),
[this](const ActivityResult& confirmResult) {
if (!confirmResult.isCancelled && epub) {
BookManager::deleteBook(epub->getPath());
}
activityManager.goHome();
});
return;
}
case BookManageMenuActivity::Action::DELETE_CACHE: {
RenderLock lock(*this);
if (section) {
@@ -725,27 +777,26 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
break;
}
case EpubReaderMenuActivity::MenuAction::DICTIONARY: {
startActivityForResult(
std::make_unique<DictionaryMenuActivity>(renderer, mappedInput),
[this](ActivityResult result) {
if (result.isCancelled) {
requestUpdate();
return;
}
const auto& menu = std::get<MenuResult>(result.data);
const auto dictAction = static_cast<DictionaryMenuActivity::Action>(menu.action);
switch (dictAction) {
case DictionaryMenuActivity::Action::LOOKUP_WORD:
onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction::LOOKUP_WORD);
return;
case DictionaryMenuActivity::Action::LOOKUP_HISTORY:
onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction::LOOKUP_HISTORY);
return;
case DictionaryMenuActivity::Action::DELETE_DICT_CACHE:
onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction::DELETE_DICT_CACHE);
return;
}
});
startActivityForResult(std::make_unique<DictionaryMenuActivity>(renderer, mappedInput),
[this](ActivityResult result) {
if (result.isCancelled) {
requestUpdate();
return;
}
const auto& menu = std::get<MenuResult>(result.data);
const auto dictAction = static_cast<DictionaryMenuActivity::Action>(menu.action);
switch (dictAction) {
case DictionaryMenuActivity::Action::LOOKUP_WORD:
onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction::LOOKUP_WORD);
return;
case DictionaryMenuActivity::Action::LOOKUP_HISTORY:
onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction::LOOKUP_HISTORY);
return;
case DictionaryMenuActivity::Action::DELETE_DICT_CACHE:
onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction::DELETE_DICT_CACHE);
return;
}
});
break;
}
case EpubReaderMenuActivity::MenuAction::LETTERBOX_FILL:
@@ -920,11 +971,10 @@ void EpubReaderActivity::cacheMultiSpineChapter(const uint16_t vpWidth, const ui
uncached.reserve(endSpine - startSpine);
for (int i = startSpine; i < endSpine; i++) {
if (i == currentSpineIndex) continue;
auto cached = Section::readCachedPageCount(epub->getCachePath(), i, SETTINGS.getReaderFontId(),
SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing,
SETTINGS.paragraphAlignment, vpWidth, vpHeight,
SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle,
SETTINGS.imageRendering);
auto cached = Section::readCachedPageCount(
epub->getCachePath(), i, SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, vpWidth, vpHeight, SETTINGS.hyphenationEnabled,
SETTINGS.embeddedStyle, SETTINGS.imageRendering);
if (!cached) uncached.push_back(i);
}
@@ -1241,8 +1291,8 @@ void EpubReaderActivity::renderStatusBar() const {
} else if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::CHAPTER_TITLE) {
title = tr(STR_UNNAMED);
const int tocIndex = section ? section->getTocIndexForPage(section->currentPage)
: epub->getTocIndexForSpineIndex(currentSpineIndex);
const int tocIndex =
section ? section->getTocIndexForPage(section->currentPage) : epub->getTocIndexForSpineIndex(currentSpineIndex);
if (tocIndex != -1) {
const auto tocItem = epub->getTocItem(tocIndex);
title = tocItem.title;
@@ -1258,7 +1308,8 @@ void EpubReaderActivity::renderStatusBar() const {
int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft;
renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom,
&orientedMarginLeft);
const int textY = renderer.getScreenHeight() - UITheme::getInstance().getStatusBarHeight() - orientedMarginBottom - 4;
const int textY =
renderer.getScreenHeight() - UITheme::getInstance().getStatusBarHeight() - orientedMarginBottom - 4;
const bool showBatteryPercentage =
SETTINGS.hideBatteryPercentage == CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_NEVER;
const int batteryWidth = SETTINGS.statusBarBattery ? (showBatteryPercentage ? 50 : 20) : 0;

View File

@@ -2,8 +2,8 @@
#include <GfxRenderer.h>
#include "activities/ActivityResult.h"
#include "MappedInputManager.h"
#include "activities/ActivityResult.h"
#include "components/UITheme.h"
#include "fontIds.h"

View File

@@ -68,8 +68,7 @@ void EpubReaderMenuActivity::loop() {
// Long-press Confirm on orientation item opens the sub-menu
if (!orientationSubMenuOpen && mappedInput.isPressed(MappedInputManager::Button::Confirm) &&
mappedInput.getHeldTime() >= longPressMs &&
menuItems[selectedIndex].action == MenuAction::TOGGLE_ORIENTATION) {
mappedInput.getHeldTime() >= longPressMs && menuItems[selectedIndex].action == MenuAction::TOGGLE_ORIENTATION) {
orientationSubMenuOpen = true;
ignoreNextConfirmRelease = true;
orientationSubMenuIndex = pendingOrientation;
@@ -146,7 +145,8 @@ void EpubReaderMenuActivity::loop() {
return;
}
setResult(MenuResult{static_cast<int>(selectedAction), pendingOrientation, selectedPageTurnOption, pendingFontSize});
setResult(
MenuResult{static_cast<int>(selectedAction), pendingOrientation, selectedPageTurnOption, pendingFontSize});
finish();
return;
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
@@ -176,8 +176,7 @@ void EpubReaderMenuActivity::render(RenderLock&&) {
if (orientationSubMenuOpen) {
const char* subTitle = tr(STR_TOGGLE_ORIENTATION);
const int subTitleX =
contentX +
(contentWidth - renderer.getTextWidth(UI_12_FONT_ID, subTitle, EpdFontFamily::BOLD)) / 2;
contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, subTitle, EpdFontFamily::BOLD)) / 2;
renderer.drawText(UI_12_FONT_ID, subTitleX, 15 + contentY, subTitle, true, EpdFontFamily::BOLD);
constexpr int lineHeight = 35;

View File

@@ -45,9 +45,8 @@ class EpubReaderMenuActivity final : public Activity {
explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title,
const int currentPage, const int totalPages, const int bookProgressPercent,
const uint8_t currentOrientation, const bool hasFootnotes,
bool isBookmarked = false, uint8_t currentFontSize = 0,
const std::string& bookCachePath = "");
const uint8_t currentOrientation, const bool hasFootnotes, bool isBookmarked = false,
uint8_t currentFontSize = 0, const std::string& bookCachePath = "");
void onEnter() override;
void onExit() override;

View File

@@ -30,8 +30,7 @@ class KOReaderSyncActivity final : public Activity {
explicit KOReaderSyncActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::shared_ptr<Epub>& epub, const std::string& epubPath, int currentSpineIndex,
int currentPage, int totalPagesInSpine,
SyncMode syncMode = SyncMode::INTERACTIVE)
int currentPage, int totalPagesInSpine, SyncMode syncMode = SyncMode::INTERACTIVE)
: Activity("KOReaderSync", renderer, mappedInput),
epub(epub),
epubPath(epubPath),

View File

@@ -5,10 +5,10 @@
#include <algorithm>
#include "activities/ActivityResult.h"
#include "DictionaryDefinitionActivity.h"
#include "DictionarySuggestionsActivity.h"
#include "MappedInputManager.h"
#include "activities/ActivityResult.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "util/Dictionary.h"
@@ -140,17 +140,16 @@ void LookedUpWordsActivity::loop() {
});
if (!definition.empty()) {
startActivityForResult(
std::make_unique<DictionaryDefinitionActivity>(renderer, mappedInput, headword, definition, readerFontId,
orientation, true),
[this](ActivityResult result) {
if (!result.isCancelled) {
setResult(std::move(result));
finish();
} else {
requestUpdate();
}
});
startActivityForResult(std::make_unique<DictionaryDefinitionActivity>(renderer, mappedInput, headword, definition,
readerFontId, orientation, true),
[this](ActivityResult result) {
if (!result.isCancelled) {
setResult(std::move(result));
finish();
} else {
requestUpdate();
}
});
return;
}
@@ -159,17 +158,16 @@ void LookedUpWordsActivity::loop() {
for (const auto& stem : stems) {
std::string stemDef = Dictionary::lookup(stem);
if (!stemDef.empty()) {
startActivityForResult(
std::make_unique<DictionaryDefinitionActivity>(renderer, mappedInput, stem, stemDef, readerFontId,
orientation, true),
[this](ActivityResult result) {
if (!result.isCancelled) {
setResult(std::move(result));
finish();
} else {
requestUpdate();
}
});
startActivityForResult(std::make_unique<DictionaryDefinitionActivity>(renderer, mappedInput, stem, stemDef,
readerFontId, orientation, true),
[this](ActivityResult result) {
if (!result.isCancelled) {
setResult(std::move(result));
finish();
} else {
requestUpdate();
}
});
return;
}
}
@@ -177,17 +175,16 @@ void LookedUpWordsActivity::loop() {
// Show similar word suggestions
auto similar = Dictionary::findSimilar(headword, 6);
if (!similar.empty()) {
startActivityForResult(
std::make_unique<DictionarySuggestionsActivity>(renderer, mappedInput, headword, similar, readerFontId,
orientation, cachePath),
[this](ActivityResult result) {
if (!result.isCancelled) {
setResult(std::move(result));
finish();
} else {
requestUpdate();
}
});
startActivityForResult(std::make_unique<DictionarySuggestionsActivity>(renderer, mappedInput, headword, similar,
readerFontId, orientation, cachePath),
[this](ActivityResult result) {
if (!result.isCancelled) {
setResult(std::move(result));
finish();
} else {
requestUpdate();
}
});
return;
}

View File

@@ -37,7 +37,7 @@ void TxtReaderActivity::onEnter() {
auto fileName = filePath.substr(filePath.rfind('/') + 1);
APP_STATE.openEpubPath = filePath;
APP_STATE.saveToFile();
RECENT_BOOKS.addBook(filePath, fileName, "", "");
RECENT_BOOKS.addBook(filePath, fileName, "", "", "");
// Trigger first update
requestUpdate();

View File

@@ -40,7 +40,7 @@ void XtcReaderActivity::onEnter() {
// Save current XTC as last opened book and add to recent books
APP_STATE.openEpubPath = xtc->getPath();
APP_STATE.saveToFile();
RECENT_BOOKS.addBook(xtc->getPath(), xtc->getTitle(), xtc->getAuthor(), xtc->getThumbBmpPath());
RECENT_BOOKS.addBook(xtc->getPath(), xtc->getTitle(), xtc->getAuthor(), "", xtc->getThumbBmpPath());
// Trigger first update
requestUpdate();

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;

View File

@@ -56,6 +56,16 @@ void ConfirmationActivity::render(RenderLock&& lock) {
}
void ConfirmationActivity::loop() {
if (!inputArmed) {
if (!mappedInput.isPressed(MappedInputManager::Button::Left) &&
!mappedInput.isPressed(MappedInputManager::Button::Right) &&
!mappedInput.isPressed(MappedInputManager::Button::Confirm) &&
!mappedInput.isPressed(MappedInputManager::Button::Back)) {
inputArmed = true;
}
return;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Right)) {
ActivityResult res;
res.isCancelled = false;

View File

@@ -19,6 +19,7 @@ class ConfirmationActivity : public Activity {
std::string safeBody;
int startY = 0;
int lineHeight = 0;
bool inputArmed = false;
public:
ConfirmationActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& heading,

View File

@@ -5,8 +5,8 @@
#include <cstring>
#include "activities/ActivityResult.h"
#include "MappedInputManager.h"
#include "activities/ActivityResult.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "util/StringUtils.h"
@@ -166,10 +166,7 @@ void DirectoryPickerActivity::render(RenderLock&&) {
const auto& dir = directories[index - offset];
return dir.substr(0, dir.length() - 1);
},
nullptr,
[](int index) -> UIIcon {
return (index == 0) ? UIIcon::File : UIIcon::Folder;
});
nullptr, [](int index) -> UIIcon { return (index == 0) ? UIIcon::File : UIIcon::Folder; });
const char* backLabel = (basepath == "/") ? tr(STR_CANCEL) : tr(STR_BACK);
const char* confirmLabel = (selectorIndex == 0) ? tr(STR_SAVE_HERE) : tr(STR_OPEN);

View File

@@ -6,8 +6,8 @@
#include <algorithm>
#include <cstdio>
#include "activities/ActivityResult.h"
#include "MappedInputManager.h"
#include "activities/ActivityResult.h"
#include "components/UITheme.h"
#include "fontIds.h"

View File

@@ -12,8 +12,8 @@
*/
class NumericStepperActivity final : public Activity {
public:
explicit NumericStepperActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string title,
int value, int minValue, int maxValue)
explicit NumericStepperActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string title, int value,
int minValue, int maxValue)
: Activity("NumericStepper", renderer, mappedInput),
title(std::move(title)),
value(value),