feat: restructure reader menu with submenus and long-press TOC

- Replace scattered book management actions (Archive, Delete, Reindex,
  Delete Cache) with single "Manage Book" entry that opens
  BookManageMenuActivity as a submenu.
- Replace scattered dictionary actions (Lookup Word, Lookup History,
  Delete Dict Cache) with single "Dictionary" entry that opens new
  DictionaryMenuActivity submenu.
- Add long-press Confirm (700ms) to open Table of Contents directly
  from the reader, bypassing the menu.
- Add STR_DICTIONARY i18n key and regenerate I18nKeys.h/I18nStrings.h.

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-07 21:12:09 -05:00
parent f44657aeba
commit 0493f300be
9 changed files with 800 additions and 13 deletions

View File

@@ -11,6 +11,8 @@
#include "CrossPointSettings.h"
#include "CrossPointState.h"
#include "activities/ActivityManager.h"
#include "activities/home/BookManageMenuActivity.h"
#include "DictionaryMenuActivity.h"
#include "DictionaryWordSelectActivity.h"
#include "EndOfBookMenuActivity.h"
#include "EpubReaderBookmarkSelectionActivity.h"
@@ -34,6 +36,7 @@ namespace {
// pagesPerRefresh now comes from SETTINGS.getRefreshFrequency()
constexpr unsigned long skipChapterMs = 700;
constexpr unsigned long goHomeMs = 1000;
constexpr unsigned long longPressConfirmMs = 700;
// pages per minute, first item is 1 to prevent division by zero if accessed
const std::vector<int> PAGE_TURN_LABELS = {1, 1, 3, 6, 12};
@@ -209,8 +212,21 @@ void EpubReaderActivity::loop() {
}
}
// Enter reader menu activity.
// Long press CONFIRM opens Table of Contents directly (skip menu)
if (mappedInput.isPressed(MappedInputManager::Button::Confirm) && mappedInput.getHeldTime() >= longPressConfirmMs) {
ignoreNextConfirmRelease = true;
if (epub && epub->getTocItemsCount() > 0) {
onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction::TABLE_OF_CONTENTS);
}
return;
}
// Short press CONFIRM opens reader menu
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
if (ignoreNextConfirmRelease) {
ignoreNextConfirmRelease = false;
return;
}
const int currentPage = section ? section->currentPage + 1 : 0;
const int totalPages = section ? section->pageCount : 0;
float bookProgress = 0.0f;
@@ -595,6 +611,81 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
activityManager.goHome();
return;
}
case EpubReaderMenuActivity::MenuAction::MANAGE_BOOK: {
if (!epub) break;
const bool isArchived = BookManager::isArchived(epub->getPath());
startActivityForResult(
std::make_unique<BookManageMenuActivity>(renderer, mappedInput, epub->getPath(), isArchived),
[this](ActivityResult result) {
if (result.isCancelled) {
requestUpdate();
return;
}
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();
return;
case BookManageMenuActivity::Action::UNARCHIVE:
BookManager::unarchiveBook(epub->getPath());
activityManager.goHome();
return;
case BookManageMenuActivity::Action::DELETE:
BookManager::deleteBook(epub->getPath());
activityManager.goHome();
return;
case BookManageMenuActivity::Action::DELETE_CACHE: {
RenderLock lock(*this);
if (section) {
uint16_t backupSpine = currentSpineIndex;
uint16_t backupPage = section->currentPage;
uint16_t backupPageCount = section->pageCount;
section.reset();
epub->clearCache();
epub->setupCacheDir();
saveProgress(backupSpine, backupPage, backupPageCount);
}
activityManager.goHome();
return;
}
case BookManageMenuActivity::Action::REINDEX:
BookManager::reindexBook(epub->getPath(), false);
activityManager.goHome();
return;
case BookManageMenuActivity::Action::REINDEX_FULL:
BookManager::reindexBook(epub->getPath(), true);
activityManager.goHome();
return;
}
});
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;
}
});
break;
}
case EpubReaderMenuActivity::MenuAction::LETTERBOX_FILL:
break;
}