mod: Phase 2b - adapt HomeActivity, EpubReaderMenuActivity, EpubReaderActivity
HomeActivity: Add mod features on top of upstream ActivityManager pattern: - Multi-server OPDS support (OpdsServerStore instead of single URL) - Long-press recent book for BookManageMenuActivity - Long-press Browse Files to open archive folder - Placeholder cover generation for books without covers - startActivityForResult pattern for manage menu EpubReaderMenuActivity: Replace upstream menu items with mod menu: - Add/Remove Bookmark, Lookup Word, Go to Bookmark, Lookup History - Table of Contents, Toggle Orientation, Toggle Font Size - Close Book, Delete Dictionary Cache - Pass isBookmarked and currentFontSize to constructor - Show current orientation/font size value inline EpubReaderActivity: Add mod action handlers: - Bookmark add/remove via BookmarkStore - Go to Bookmark via EpubReaderBookmarkSelectionActivity - Dictionary word lookup via DictionaryWordSelectActivity - Lookup history via LookedUpWordsActivity - Delete dictionary cache - Font size toggle with section re-layout - Close Book action ActivityResult: Add fontSize field to MenuResult Made-with: Cursor
This commit is contained in:
1486
epub-reader-activity-diff.txt
Normal file
1486
epub-reader-activity-diff.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@ struct MenuResult {
|
|||||||
int action = -1;
|
int action = -1;
|
||||||
uint8_t orientation = 0;
|
uint8_t orientation = 0;
|
||||||
uint8_t pageTurnOption = 0;
|
uint8_t pageTurnOption = 0;
|
||||||
|
uint8_t fontSize = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ChapterResult {
|
struct ChapterResult {
|
||||||
|
|||||||
@@ -6,25 +6,32 @@
|
|||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <I18n.h>
|
#include <I18n.h>
|
||||||
|
#include <PlaceholderCoverGenerator.h>
|
||||||
#include <Utf8.h>
|
#include <Utf8.h>
|
||||||
#include <Xtc.h>
|
#include <Xtc.h>
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "BookManageMenuActivity.h"
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "CrossPointState.h"
|
#include "CrossPointState.h"
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
|
#include "OpdsServerStore.h"
|
||||||
#include "RecentBooksStore.h"
|
#include "RecentBooksStore.h"
|
||||||
|
#include "activities/ActivityResult.h"
|
||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
#include "util/BookManager.h"
|
||||||
|
#include "util/StringUtils.h"
|
||||||
|
|
||||||
int HomeActivity::getMenuItemCount() const {
|
int HomeActivity::getMenuItemCount() const {
|
||||||
int count = 4; // File Browser, Recents, File transfer, Settings
|
int count = 4; // File Browser, Recents, File transfer, Settings
|
||||||
if (!recentBooks.empty()) {
|
if (!recentBooks.empty()) {
|
||||||
count += recentBooks.size();
|
count += recentBooks.size();
|
||||||
}
|
}
|
||||||
if (hasOpdsUrl) {
|
if (hasOpdsServers) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
@@ -59,47 +66,56 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
|
|||||||
for (RecentBook& book : recentBooks) {
|
for (RecentBook& book : recentBooks) {
|
||||||
if (!book.coverBmpPath.empty()) {
|
if (!book.coverBmpPath.empty()) {
|
||||||
std::string coverPath = UITheme::getCoverThumbPath(book.coverBmpPath, coverHeight);
|
std::string coverPath = UITheme::getCoverThumbPath(book.coverBmpPath, coverHeight);
|
||||||
if (!Storage.exists(coverPath.c_str())) {
|
if (!Epub::isValidThumbnailBmp(coverPath)) {
|
||||||
// If epub, try to load the metadata for title/author and cover
|
|
||||||
if (FsHelpers::hasEpubExtension(book.path)) {
|
|
||||||
Epub epub(book.path, "/.crosspoint");
|
|
||||||
// Skip loading css since we only need metadata here
|
|
||||||
epub.load(false, true);
|
|
||||||
|
|
||||||
// Try to generate thumbnail image for Continue Reading card
|
|
||||||
if (!showingLoading) {
|
if (!showingLoading) {
|
||||||
showingLoading = true;
|
showingLoading = true;
|
||||||
popupRect = GUI.drawPopup(renderer, tr(STR_LOADING_POPUP));
|
popupRect = GUI.drawPopup(renderer, tr(STR_LOADING));
|
||||||
}
|
}
|
||||||
GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size()));
|
GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size()));
|
||||||
bool success = epub.generateThumbBmp(coverHeight);
|
|
||||||
if (!success) {
|
bool success = false;
|
||||||
RECENT_BOOKS.updateBook(book.path, book.title, book.author, "");
|
|
||||||
book.coverBmpPath = "";
|
if (StringUtils::checkFileExtension(book.path, ".epub")) {
|
||||||
|
Epub epub(book.path, "/.crosspoint");
|
||||||
|
if (!epub.load(false, true)) {
|
||||||
|
epub.load(true, true);
|
||||||
}
|
}
|
||||||
coverRendered = false;
|
success = epub.generateThumbBmp(coverHeight);
|
||||||
requestUpdate();
|
if (success) {
|
||||||
} else if (FsHelpers::hasXtcExtension(book.path)) {
|
const std::string thumbPath = epub.getThumbBmpPath(coverHeight);
|
||||||
// Handle XTC file
|
RECENT_BOOKS.updateBook(book.path, book.title, book.author, thumbPath);
|
||||||
|
book.coverBmpPath = thumbPath;
|
||||||
|
} else {
|
||||||
|
const int thumbWidth = static_cast<int>(coverHeight * 0.6);
|
||||||
|
success = PlaceholderCoverGenerator::generate(coverPath, book.title, book.author, thumbWidth, coverHeight);
|
||||||
|
if (!success) {
|
||||||
|
epub.generateInvalidFormatThumbBmp(coverHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (StringUtils::checkFileExtension(book.path, ".xtch") ||
|
||||||
|
StringUtils::checkFileExtension(book.path, ".xtc")) {
|
||||||
Xtc xtc(book.path, "/.crosspoint");
|
Xtc xtc(book.path, "/.crosspoint");
|
||||||
if (xtc.load()) {
|
if (xtc.load()) {
|
||||||
// Try to generate thumbnail image for Continue Reading card
|
success = xtc.generateThumbBmp(coverHeight);
|
||||||
if (!showingLoading) {
|
if (success) {
|
||||||
showingLoading = true;
|
const std::string thumbPath = xtc.getThumbBmpPath(coverHeight);
|
||||||
popupRect = GUI.drawPopup(renderer, tr(STR_LOADING_POPUP));
|
RECENT_BOOKS.updateBook(book.path, book.title, book.author, thumbPath);
|
||||||
|
book.coverBmpPath = thumbPath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size()));
|
|
||||||
bool success = xtc.generateThumbBmp(coverHeight);
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
RECENT_BOOKS.updateBook(book.path, book.title, book.author, "");
|
const int thumbWidth = static_cast<int>(coverHeight * 0.6);
|
||||||
book.coverBmpPath = "";
|
PlaceholderCoverGenerator::generate(coverPath, book.title, book.author, thumbWidth, coverHeight);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const int thumbWidth = static_cast<int>(coverHeight * 0.6);
|
||||||
|
PlaceholderCoverGenerator::generate(coverPath, book.title, book.author, thumbWidth, coverHeight);
|
||||||
|
}
|
||||||
|
|
||||||
coverRendered = false;
|
coverRendered = false;
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
progress++;
|
progress++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,8 +126,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
|
|||||||
void HomeActivity::onEnter() {
|
void HomeActivity::onEnter() {
|
||||||
Activity::onEnter();
|
Activity::onEnter();
|
||||||
|
|
||||||
// Check if OPDS browser URL is configured
|
hasOpdsServers = OPDS_STORE.hasServers();
|
||||||
hasOpdsUrl = strlen(SETTINGS.opdsServerUrl) > 0;
|
|
||||||
|
|
||||||
selectorIndex = 0;
|
selectorIndex = 0;
|
||||||
|
|
||||||
@@ -184,17 +199,37 @@ void HomeActivity::loop() {
|
|||||||
requestUpdate();
|
requestUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Long-press Confirm: manage menu for recent books, or browse archive for Browse Files
|
||||||
|
if (mappedInput.isPressed(MappedInputManager::Button::Confirm) && mappedInput.getHeldTime() >= LONG_PRESS_MS &&
|
||||||
|
!ignoreNextConfirmRelease) {
|
||||||
|
if (selectorIndex < static_cast<int>(recentBooks.size())) {
|
||||||
|
ignoreNextConfirmRelease = true;
|
||||||
|
openManageMenu(recentBooks[selectorIndex].path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const int menuSelectedIndex = selectorIndex - static_cast<int>(recentBooks.size());
|
||||||
|
if (menuSelectedIndex == 0) {
|
||||||
|
ignoreNextConfirmRelease = true;
|
||||||
|
activityManager.goToFileBrowser("/.archive");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
// Calculate dynamic indices based on which options are available
|
if (ignoreNextConfirmRelease) {
|
||||||
|
ignoreNextConfirmRelease = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
int menuSelectedIndex = selectorIndex - static_cast<int>(recentBooks.size());
|
int menuSelectedIndex = selectorIndex - static_cast<int>(recentBooks.size());
|
||||||
const int fileBrowserIdx = idx++;
|
const int fileBrowserIdx = idx++;
|
||||||
const int recentsIdx = idx++;
|
const int recentsIdx = idx++;
|
||||||
const int opdsLibraryIdx = hasOpdsUrl ? idx++ : -1;
|
const int opdsLibraryIdx = hasOpdsServers ? idx++ : -1;
|
||||||
const int fileTransferIdx = idx++;
|
const int fileTransferIdx = idx++;
|
||||||
const int settingsIdx = idx;
|
const int settingsIdx = idx;
|
||||||
|
|
||||||
if (selectorIndex < recentBooks.size()) {
|
if (selectorIndex < static_cast<int>(recentBooks.size())) {
|
||||||
onSelectBook(recentBooks[selectorIndex].path);
|
onSelectBook(recentBooks[selectorIndex].path);
|
||||||
} else if (menuSelectedIndex == fileBrowserIdx) {
|
} else if (menuSelectedIndex == fileBrowserIdx) {
|
||||||
onFileBrowserOpen();
|
onFileBrowserOpen();
|
||||||
@@ -229,7 +264,7 @@ void HomeActivity::render(RenderLock&&) {
|
|||||||
tr(STR_SETTINGS_TITLE)};
|
tr(STR_SETTINGS_TITLE)};
|
||||||
std::vector<UIIcon> menuIcons = {Folder, Recent, Transfer, Settings};
|
std::vector<UIIcon> menuIcons = {Folder, Recent, Transfer, Settings};
|
||||||
|
|
||||||
if (hasOpdsUrl) {
|
if (hasOpdsServers) {
|
||||||
// Insert OPDS Browser after File Browser
|
// Insert OPDS Browser after File Browser
|
||||||
menuItems.insert(menuItems.begin() + 2, tr(STR_OPDS_BROWSER));
|
menuItems.insert(menuItems.begin() + 2, tr(STR_OPDS_BROWSER));
|
||||||
menuIcons.insert(menuIcons.begin() + 2, Library);
|
menuIcons.insert(menuIcons.begin() + 2, Library);
|
||||||
@@ -269,3 +304,52 @@ void HomeActivity::onSettingsOpen() { activityManager.goToSettings(); }
|
|||||||
void HomeActivity::onFileTransferOpen() { activityManager.goToFileTransfer(); }
|
void HomeActivity::onFileTransferOpen() { activityManager.goToFileTransfer(); }
|
||||||
|
|
||||||
void HomeActivity::onOpdsBrowserOpen() { activityManager.goToBrowser(); }
|
void HomeActivity::onOpdsBrowserOpen() { activityManager.goToBrowser(); }
|
||||||
|
|
||||||
|
void HomeActivity::openManageMenu(const std::string& bookPath) {
|
||||||
|
const bool isArchived = BookManager::isArchived(bookPath);
|
||||||
|
const std::string capturedPath = bookPath;
|
||||||
|
startActivityForResult(
|
||||||
|
std::make_unique<BookManageMenuActivity>(renderer, mappedInput, capturedPath, isArchived, true),
|
||||||
|
[this, capturedPath](const ActivityResult& result) {
|
||||||
|
if (result.isCancelled) {
|
||||||
|
requestUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
RenderLock lock(*this);
|
||||||
|
GUI.drawPopup(renderer, success ? tr(STR_DONE) : tr(STR_ACTION_FAILED));
|
||||||
|
}
|
||||||
|
requestUpdateAndWait();
|
||||||
|
recentBooks.clear();
|
||||||
|
recentsLoaded = false;
|
||||||
|
recentsLoading = false;
|
||||||
|
coverRendered = false;
|
||||||
|
freeCoverBuffer();
|
||||||
|
selectorIndex = 0;
|
||||||
|
firstRenderDone = false;
|
||||||
|
requestUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,22 +15,27 @@ class HomeActivity final : public Activity {
|
|||||||
bool recentsLoading = false;
|
bool recentsLoading = false;
|
||||||
bool recentsLoaded = false;
|
bool recentsLoaded = false;
|
||||||
bool firstRenderDone = false;
|
bool firstRenderDone = false;
|
||||||
bool hasOpdsUrl = false;
|
bool hasOpdsServers = false;
|
||||||
bool coverRendered = false; // Track if cover has been rendered once
|
bool coverRendered = false;
|
||||||
bool coverBufferStored = false; // Track if cover buffer is stored
|
bool coverBufferStored = false;
|
||||||
uint8_t* coverBuffer = nullptr; // HomeActivity's own buffer for cover image
|
uint8_t* coverBuffer = nullptr;
|
||||||
std::vector<RecentBook> recentBooks;
|
std::vector<RecentBook> recentBooks;
|
||||||
|
|
||||||
|
bool ignoreNextConfirmRelease = false;
|
||||||
|
static constexpr unsigned long LONG_PRESS_MS = 700;
|
||||||
|
|
||||||
void onSelectBook(const std::string& path);
|
void onSelectBook(const std::string& path);
|
||||||
void onFileBrowserOpen();
|
void onFileBrowserOpen();
|
||||||
void onRecentsOpen();
|
void onRecentsOpen();
|
||||||
void onSettingsOpen();
|
void onSettingsOpen();
|
||||||
void onFileTransferOpen();
|
void onFileTransferOpen();
|
||||||
void onOpdsBrowserOpen();
|
void onOpdsBrowserOpen();
|
||||||
|
void openManageMenu(const std::string& bookPath);
|
||||||
|
|
||||||
int getMenuItemCount() const;
|
int getMenuItemCount() const;
|
||||||
bool storeCoverBuffer(); // Store frame buffer for cover image
|
bool storeCoverBuffer();
|
||||||
bool restoreCoverBuffer(); // Restore frame buffer from stored cover
|
bool restoreCoverBuffer();
|
||||||
void freeCoverBuffer(); // Free the stored cover buffer
|
void freeCoverBuffer();
|
||||||
void loadRecentBooks(int maxBooks);
|
void loadRecentBooks(int maxBooks);
|
||||||
void loadRecentCovers(int coverHeight);
|
void loadRecentCovers(int coverHeight);
|
||||||
|
|
||||||
|
|||||||
@@ -10,16 +10,21 @@
|
|||||||
|
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "CrossPointState.h"
|
#include "CrossPointState.h"
|
||||||
|
#include "DictionaryWordSelectActivity.h"
|
||||||
|
#include "EpubReaderBookmarkSelectionActivity.h"
|
||||||
#include "EpubReaderChapterSelectionActivity.h"
|
#include "EpubReaderChapterSelectionActivity.h"
|
||||||
#include "EpubReaderFootnotesActivity.h"
|
#include "EpubReaderFootnotesActivity.h"
|
||||||
#include "EpubReaderPercentSelectionActivity.h"
|
#include "EpubReaderPercentSelectionActivity.h"
|
||||||
#include "KOReaderCredentialStore.h"
|
#include "KOReaderCredentialStore.h"
|
||||||
#include "KOReaderSyncActivity.h"
|
#include "KOReaderSyncActivity.h"
|
||||||
|
#include "LookedUpWordsActivity.h"
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "QrDisplayActivity.h"
|
#include "QrDisplayActivity.h"
|
||||||
#include "RecentBooksStore.h"
|
#include "RecentBooksStore.h"
|
||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
#include "util/BookmarkStore.h"
|
||||||
|
#include "util/Dictionary.h"
|
||||||
#include "util/ScreenshotUtil.h"
|
#include "util/ScreenshotUtil.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -164,13 +169,16 @@ void EpubReaderActivity::loop() {
|
|||||||
bookProgress = epub->calculateProgress(currentSpineIndex, chapterProgress) * 100.0f;
|
bookProgress = epub->calculateProgress(currentSpineIndex, chapterProgress) * 100.0f;
|
||||||
}
|
}
|
||||||
const int bookProgressPercent = clampPercent(static_cast<int>(bookProgress + 0.5f));
|
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>(
|
startActivityForResult(std::make_unique<EpubReaderMenuActivity>(
|
||||||
renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent,
|
renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent,
|
||||||
SETTINGS.orientation, !currentPageFootnotes.empty()),
|
SETTINGS.orientation, !currentPageFootnotes.empty(), isBookmarked, SETTINGS.fontSize),
|
||||||
[this](const ActivityResult& result) {
|
[this](const ActivityResult& result) {
|
||||||
// Always apply orientation change even if the menu was cancelled
|
// Always apply orientation and font size even if the menu was cancelled
|
||||||
const auto& menu = std::get<MenuResult>(result.data);
|
const auto& menu = std::get<MenuResult>(result.data);
|
||||||
applyOrientation(menu.orientation);
|
applyOrientation(menu.orientation);
|
||||||
|
applyFontSize(menu.fontSize);
|
||||||
toggleAutoPageTurn(menu.pageTurnOption);
|
toggleAutoPageTurn(menu.pageTurnOption);
|
||||||
if (!result.isCancelled) {
|
if (!result.isCancelled) {
|
||||||
onReaderMenuConfirm(static_cast<EpubReaderMenuActivity::MenuAction>(menu.action));
|
onReaderMenuConfirm(static_cast<EpubReaderMenuActivity::MenuAction>(menu.action));
|
||||||
@@ -313,6 +321,7 @@ void EpubReaderActivity::jumpToPercent(int percent) {
|
|||||||
|
|
||||||
void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action) {
|
void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
case EpubReaderMenuActivity::MenuAction::TABLE_OF_CONTENTS:
|
||||||
case EpubReaderMenuActivity::MenuAction::SELECT_CHAPTER: {
|
case EpubReaderMenuActivity::MenuAction::SELECT_CHAPTER: {
|
||||||
const int spineIdx = currentSpineIndex;
|
const int spineIdx = currentSpineIndex;
|
||||||
const std::string path = epub->getPath();
|
const std::string path = epub->getPath();
|
||||||
@@ -432,6 +441,79 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case EpubReaderMenuActivity::MenuAction::CLOSE_BOOK:
|
||||||
|
onGoHome();
|
||||||
|
return;
|
||||||
|
case EpubReaderMenuActivity::MenuAction::ADD_BOOKMARK: {
|
||||||
|
if (section && BookmarkStore::addBookmark(epub->getCachePath(), currentSpineIndex, section->currentPage, "")) {
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EpubReaderMenuActivity::MenuAction::REMOVE_BOOKMARK: {
|
||||||
|
if (section && BookmarkStore::removeBookmark(epub->getCachePath(), currentSpineIndex, section->currentPage)) {
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EpubReaderMenuActivity::MenuAction::GO_TO_BOOKMARK: {
|
||||||
|
auto bookmarks = BookmarkStore::load(epub->getCachePath());
|
||||||
|
startActivityForResult(
|
||||||
|
std::make_unique<EpubReaderBookmarkSelectionActivity>(renderer, mappedInput, epub, std::move(bookmarks),
|
||||||
|
epub->getCachePath()),
|
||||||
|
[this](const ActivityResult& result) {
|
||||||
|
if (!result.isCancelled) {
|
||||||
|
const auto& sync = std::get<SyncResult>(result.data);
|
||||||
|
if (currentSpineIndex != sync.spineIndex || (section && section->currentPage != sync.page)) {
|
||||||
|
RenderLock lock(*this);
|
||||||
|
currentSpineIndex = sync.spineIndex;
|
||||||
|
nextPageNumber = sync.page;
|
||||||
|
section.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EpubReaderMenuActivity::MenuAction::LOOKUP_WORD: {
|
||||||
|
if (!section || !Dictionary::cacheExists()) {
|
||||||
|
requestUpdate();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto p = section->loadPageFromSectionFile();
|
||||||
|
if (!p) {
|
||||||
|
requestUpdate();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft;
|
||||||
|
renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom,
|
||||||
|
&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(); });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EpubReaderMenuActivity::MenuAction::LOOKUP_HISTORY: {
|
||||||
|
if (!Dictionary::cacheExists()) {
|
||||||
|
requestUpdate();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
startActivityForResult(
|
||||||
|
std::make_unique<LookedUpWordsActivity>(renderer, mappedInput, epub->getCachePath(),
|
||||||
|
SETTINGS.getReaderFontId(), SETTINGS.orientation),
|
||||||
|
[this](const ActivityResult&) { requestUpdate(); });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EpubReaderMenuActivity::MenuAction::DELETE_DICT_CACHE: {
|
||||||
|
if (Dictionary::cacheExists()) {
|
||||||
|
Dictionary::deleteCache();
|
||||||
|
}
|
||||||
|
requestUpdate();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,6 +544,25 @@ void EpubReaderActivity::applyOrientation(const uint8_t orientation) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EpubReaderActivity::applyFontSize(const uint8_t fontSize) {
|
||||||
|
if (fontSize >= CrossPointSettings::FONT_SIZE_COUNT || SETTINGS.fontSize == fontSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
RenderLock lock(*this);
|
||||||
|
if (section) {
|
||||||
|
cachedSpineIndex = currentSpineIndex;
|
||||||
|
cachedChapterTotalPageCount = section->pageCount;
|
||||||
|
nextPageNumber = section->currentPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
SETTINGS.fontSize = fontSize;
|
||||||
|
SETTINGS.saveToFile();
|
||||||
|
section.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void EpubReaderActivity::toggleAutoPageTurn(const uint8_t selectedPageTurnOption) {
|
void EpubReaderActivity::toggleAutoPageTurn(const uint8_t selectedPageTurnOption) {
|
||||||
if (selectedPageTurnOption == 0 || selectedPageTurnOption >= PAGE_TURN_LABELS.size()) {
|
if (selectedPageTurnOption == 0 || selectedPageTurnOption >= PAGE_TURN_LABELS.size()) {
|
||||||
automaticPageTurnActive = false;
|
automaticPageTurnActive = false;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class EpubReaderActivity final : public Activity {
|
|||||||
void jumpToPercent(int percent);
|
void jumpToPercent(int percent);
|
||||||
void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action);
|
void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action);
|
||||||
void applyOrientation(uint8_t orientation);
|
void applyOrientation(uint8_t orientation);
|
||||||
|
void applyFontSize(uint8_t fontSize);
|
||||||
void toggleAutoPageTurn(uint8_t selectedPageTurnOption);
|
void toggleAutoPageTurn(uint8_t selectedPageTurnOption);
|
||||||
void pageTurn(bool isForwardTurn);
|
void pageTurn(bool isForwardTurn);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <I18n.h>
|
#include <I18n.h>
|
||||||
|
|
||||||
|
#include "CrossPointSettings.h"
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
@@ -10,30 +11,37 @@
|
|||||||
EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
const std::string& title, const int currentPage, const int totalPages,
|
const std::string& title, const int currentPage, const int totalPages,
|
||||||
const int bookProgressPercent, const uint8_t currentOrientation,
|
const int bookProgressPercent, const uint8_t currentOrientation,
|
||||||
const bool hasFootnotes)
|
const bool hasFootnotes, bool isBookmarked, uint8_t currentFontSize)
|
||||||
: Activity("EpubReaderMenu", renderer, mappedInput),
|
: Activity("EpubReaderMenu", renderer, mappedInput),
|
||||||
menuItems(buildMenuItems(hasFootnotes)),
|
menuItems(buildMenuItems(hasFootnotes, isBookmarked)),
|
||||||
title(title),
|
title(title),
|
||||||
pendingOrientation(currentOrientation),
|
pendingOrientation(currentOrientation),
|
||||||
|
pendingFontSize(currentFontSize < CrossPointSettings::FONT_SIZE_COUNT ? currentFontSize : 0),
|
||||||
currentPage(currentPage),
|
currentPage(currentPage),
|
||||||
totalPages(totalPages),
|
totalPages(totalPages),
|
||||||
bookProgressPercent(bookProgressPercent) {}
|
bookProgressPercent(bookProgressPercent) {}
|
||||||
|
|
||||||
std::vector<EpubReaderMenuActivity::MenuItem> EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes) {
|
std::vector<EpubReaderMenuActivity::MenuItem> EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes,
|
||||||
|
bool isBookmarked) {
|
||||||
std::vector<MenuItem> items;
|
std::vector<MenuItem> items;
|
||||||
items.reserve(10);
|
items.reserve(13);
|
||||||
items.push_back({MenuAction::SELECT_CHAPTER, StrId::STR_SELECT_CHAPTER});
|
// Mod menu order
|
||||||
if (hasFootnotes) {
|
if (isBookmarked) {
|
||||||
items.push_back({MenuAction::FOOTNOTES, StrId::STR_FOOTNOTES});
|
items.push_back({MenuAction::REMOVE_BOOKMARK, StrId::STR_REMOVE_BOOKMARK});
|
||||||
|
} else {
|
||||||
|
items.push_back({MenuAction::ADD_BOOKMARK, StrId::STR_ADD_BOOKMARK});
|
||||||
}
|
}
|
||||||
items.push_back({MenuAction::ROTATE_SCREEN, StrId::STR_ORIENTATION});
|
items.push_back({MenuAction::LOOKUP_WORD, StrId::STR_LOOKUP_WORD});
|
||||||
items.push_back({MenuAction::AUTO_PAGE_TURN, StrId::STR_AUTO_TURN_PAGES_PER_MIN});
|
items.push_back({MenuAction::GO_TO_BOOKMARK, StrId::STR_GO_TO_BOOKMARK});
|
||||||
|
items.push_back({MenuAction::LOOKUP_HISTORY, StrId::STR_LOOKUP_HISTORY});
|
||||||
|
items.push_back({MenuAction::TABLE_OF_CONTENTS, StrId::STR_TABLE_OF_CONTENTS});
|
||||||
items.push_back({MenuAction::GO_TO_PERCENT, StrId::STR_GO_TO_PERCENT});
|
items.push_back({MenuAction::GO_TO_PERCENT, StrId::STR_GO_TO_PERCENT});
|
||||||
items.push_back({MenuAction::SCREENSHOT, StrId::STR_SCREENSHOT_BUTTON});
|
items.push_back({MenuAction::TOGGLE_ORIENTATION, StrId::STR_TOGGLE_ORIENTATION});
|
||||||
items.push_back({MenuAction::DISPLAY_QR, StrId::STR_DISPLAY_QR});
|
items.push_back({MenuAction::TOGGLE_FONT_SIZE, StrId::STR_TOGGLE_FONT_SIZE});
|
||||||
items.push_back({MenuAction::GO_HOME, StrId::STR_GO_HOME_BUTTON});
|
|
||||||
items.push_back({MenuAction::SYNC, StrId::STR_SYNC_PROGRESS});
|
items.push_back({MenuAction::SYNC, StrId::STR_SYNC_PROGRESS});
|
||||||
|
items.push_back({MenuAction::CLOSE_BOOK, StrId::STR_CLOSE_BOOK});
|
||||||
items.push_back({MenuAction::DELETE_CACHE, StrId::STR_DELETE_CACHE});
|
items.push_back({MenuAction::DELETE_CACHE, StrId::STR_DELETE_CACHE});
|
||||||
|
items.push_back({MenuAction::DELETE_DICT_CACHE, StrId::STR_DELETE_DICT_CACHE});
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,26 +66,28 @@ void EpubReaderMenuActivity::loop() {
|
|||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
const auto selectedAction = menuItems[selectedIndex].action;
|
const auto selectedAction = menuItems[selectedIndex].action;
|
||||||
if (selectedAction == MenuAction::ROTATE_SCREEN) {
|
if (selectedAction == MenuAction::TOGGLE_ORIENTATION) {
|
||||||
// Cycle orientation preview locally; actual rotation happens on menu exit.
|
// Toggle between preferred portrait and preferred landscape.
|
||||||
pendingOrientation = (pendingOrientation + 1) % orientationLabels.size();
|
const bool isCurrentlyPortrait =
|
||||||
|
(pendingOrientation == CrossPointSettings::PORTRAIT || pendingOrientation == CrossPointSettings::INVERTED);
|
||||||
|
pendingOrientation = isCurrentlyPortrait ? SETTINGS.preferredLandscape : SETTINGS.preferredPortrait;
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedAction == MenuAction::AUTO_PAGE_TURN) {
|
if (selectedAction == MenuAction::TOGGLE_FONT_SIZE) {
|
||||||
selectedPageTurnOption = (selectedPageTurnOption + 1) % pageTurnLabels.size();
|
pendingFontSize = (pendingFontSize + 1) % CrossPointSettings::FONT_SIZE_COUNT;
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setResult(MenuResult{static_cast<int>(selectedAction), pendingOrientation, selectedPageTurnOption});
|
setResult(MenuResult{static_cast<int>(selectedAction), pendingOrientation, selectedPageTurnOption, pendingFontSize});
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
ActivityResult result;
|
ActivityResult result;
|
||||||
result.isCancelled = true;
|
result.isCancelled = true;
|
||||||
result.data = MenuResult{-1, pendingOrientation, selectedPageTurnOption};
|
result.data = MenuResult{-1, pendingOrientation, selectedPageTurnOption, pendingFontSize};
|
||||||
setResult(std::move(result));
|
setResult(std::move(result));
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
@@ -134,16 +144,14 @@ void EpubReaderMenuActivity::render(RenderLock&&) {
|
|||||||
|
|
||||||
renderer.drawText(UI_10_FONT_ID, contentX + 20, displayY, I18N.get(menuItems[i].labelId), !isSelected);
|
renderer.drawText(UI_10_FONT_ID, contentX + 20, displayY, I18N.get(menuItems[i].labelId), !isSelected);
|
||||||
|
|
||||||
if (menuItems[i].action == MenuAction::ROTATE_SCREEN) {
|
if (menuItems[i].action == MenuAction::TOGGLE_ORIENTATION) {
|
||||||
// Render current orientation value on the right edge of the content area.
|
|
||||||
const char* value = I18N.get(orientationLabels[pendingOrientation]);
|
const char* value = I18N.get(orientationLabels[pendingOrientation]);
|
||||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, value);
|
const auto width = renderer.getTextWidth(UI_10_FONT_ID, value);
|
||||||
renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected);
|
renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (menuItems[i].action == MenuAction::AUTO_PAGE_TURN) {
|
if (menuItems[i].action == MenuAction::TOGGLE_FONT_SIZE) {
|
||||||
// Render current page turn value on the right edge of the content area.
|
const char* value = I18N.get(fontSizeLabels[pendingFontSize]);
|
||||||
const auto value = pageTurnLabels[selectedPageTurnOption];
|
|
||||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, value);
|
const auto width = renderer.getTextWidth(UI_10_FONT_ID, value);
|
||||||
renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected);
|
renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,24 @@ class EpubReaderMenuActivity final : public Activity {
|
|||||||
DISPLAY_QR,
|
DISPLAY_QR,
|
||||||
GO_HOME,
|
GO_HOME,
|
||||||
SYNC,
|
SYNC,
|
||||||
DELETE_CACHE
|
DELETE_CACHE,
|
||||||
|
// Mod-specific actions
|
||||||
|
ADD_BOOKMARK,
|
||||||
|
REMOVE_BOOKMARK,
|
||||||
|
LOOKUP_WORD,
|
||||||
|
LOOKUP_HISTORY,
|
||||||
|
GO_TO_BOOKMARK,
|
||||||
|
TABLE_OF_CONTENTS,
|
||||||
|
TOGGLE_ORIENTATION,
|
||||||
|
TOGGLE_FONT_SIZE,
|
||||||
|
CLOSE_BOOK,
|
||||||
|
DELETE_DICT_CACHE
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title,
|
explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title,
|
||||||
const int currentPage, const int totalPages, const int bookProgressPercent,
|
const int currentPage, const int totalPages, const int bookProgressPercent,
|
||||||
const uint8_t currentOrientation, const bool hasFootnotes);
|
const uint8_t currentOrientation, const bool hasFootnotes,
|
||||||
|
bool isBookmarked = false, uint8_t currentFontSize = 0);
|
||||||
|
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
@@ -39,7 +51,7 @@ class EpubReaderMenuActivity final : public Activity {
|
|||||||
StrId labelId;
|
StrId labelId;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::vector<MenuItem> buildMenuItems(bool hasFootnotes);
|
static std::vector<MenuItem> buildMenuItems(bool hasFootnotes, bool isBookmarked);
|
||||||
|
|
||||||
// Fixed menu layout
|
// Fixed menu layout
|
||||||
const std::vector<MenuItem> menuItems;
|
const std::vector<MenuItem> menuItems;
|
||||||
@@ -49,9 +61,11 @@ class EpubReaderMenuActivity final : public Activity {
|
|||||||
ButtonNavigator buttonNavigator;
|
ButtonNavigator buttonNavigator;
|
||||||
std::string title = "Reader Menu";
|
std::string title = "Reader Menu";
|
||||||
uint8_t pendingOrientation = 0;
|
uint8_t pendingOrientation = 0;
|
||||||
|
uint8_t pendingFontSize = 0;
|
||||||
uint8_t selectedPageTurnOption = 0;
|
uint8_t selectedPageTurnOption = 0;
|
||||||
const std::vector<StrId> orientationLabels = {StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED,
|
const std::vector<StrId> orientationLabels = {StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED,
|
||||||
StrId::STR_LANDSCAPE_CCW};
|
StrId::STR_LANDSCAPE_CCW};
|
||||||
|
const std::vector<StrId> fontSizeLabels = {StrId::STR_SMALL, StrId::STR_MEDIUM, StrId::STR_LARGE, StrId::STR_X_LARGE};
|
||||||
const std::vector<const char*> pageTurnLabels = {I18N.get(StrId::STR_STATE_OFF), "1", "3", "6", "12"};
|
const std::vector<const char*> pageTurnLabels = {I18N.get(StrId::STR_STATE_OFF), "1", "3", "6", "12"};
|
||||||
int currentPage = 0;
|
int currentPage = 0;
|
||||||
int totalPages = 0;
|
int totalPages = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user