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:
@@ -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())) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
160
src/activities/home/BookInfoActivity.cpp
Normal file
160
src/activities/home/BookInfoActivity.cpp
Normal 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;
|
||||
}
|
||||
32
src/activities/home/BookInfoActivity.h
Normal file
32
src/activities/home/BookInfoActivity.h
Normal 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);
|
||||
};
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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); });
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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&&) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user