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

534
lib/I18n/I18nKeys.h Normal file
View File

@@ -0,0 +1,534 @@
#pragma once
#include <cstdint>
// THIS FILE IS AUTO-GENERATED BY gen_i18n.py. DO NOT EDIT.
// Forward declaration for string arrays
namespace i18n_strings {
extern const char* const STRINGS_EN[];
extern const char* const STRINGS_ES[];
extern const char* const STRINGS_FR[];
extern const char* const STRINGS_DE[];
extern const char* const STRINGS_CS[];
extern const char* const STRINGS_PO[];
extern const char* const STRINGS_RU[];
extern const char* const STRINGS_SV[];
extern const char* const STRINGS_RO[];
extern const char* const STRINGS_CA[];
extern const char* const STRINGS_UK[];
extern const char* const STRINGS_BE[];
extern const char* const STRINGS_IT[];
extern const char* const STRINGS_PL[];
extern const char* const STRINGS_FI[];
extern const char* const STRINGS_DA[];
extern const char* const STRINGS_NL[];
extern const char* const STRINGS_TR[];
} // namespace i18n_strings
// Language enum
enum class Language : uint8_t {
EN = 0,
ES = 1,
FR = 2,
DE = 3,
CS = 4,
PT = 5,
RU = 6,
SV = 7,
RO = 8,
CA = 9,
UK = 10,
BE = 11,
IT = 12,
PL = 13,
FI = 14,
DA = 15,
NL = 16,
TR = 17,
_COUNT
};
// Language display names (defined in I18nStrings.cpp)
extern const char* const LANGUAGE_NAMES[];
// Character sets for each language (defined in I18nStrings.cpp)
extern const char* const CHARACTER_SETS[];
// String IDs
enum class StrId : uint16_t {
STR_CROSSPOINT,
STR_BOOTING,
STR_SLEEPING,
STR_ENTERING_SLEEP,
STR_BROWSE_FILES,
STR_FILE_TRANSFER,
STR_SETTINGS_TITLE,
STR_CALIBRE_LIBRARY,
STR_CONTINUE_READING,
STR_NO_OPEN_BOOK,
STR_START_READING,
STR_BOOKS,
STR_NO_FILES_FOUND,
STR_SELECT_CHAPTER,
STR_NO_CHAPTERS,
STR_END_OF_BOOK,
STR_EMPTY_CHAPTER,
STR_INDEXING,
STR_MEMORY_ERROR,
STR_PAGE_LOAD_ERROR,
STR_EMPTY_FILE,
STR_OUT_OF_BOUNDS,
STR_LOADING,
STR_LOADING_POPUP,
STR_LOAD_XTC_FAILED,
STR_LOAD_TXT_FAILED,
STR_LOAD_EPUB_FAILED,
STR_SD_CARD_ERROR,
STR_WIFI_NETWORKS,
STR_NO_NETWORKS,
STR_NETWORKS_FOUND,
STR_SCANNING,
STR_CONNECTING,
STR_CONNECTED,
STR_CONNECTION_FAILED,
STR_CONNECTION_TIMEOUT,
STR_FORGET_NETWORK,
STR_SAVE_PASSWORD,
STR_REMOVE_PASSWORD,
STR_PRESS_OK_SCAN,
STR_PRESS_ANY_CONTINUE,
STR_SELECT_HINT,
STR_HOW_CONNECT,
STR_JOIN_NETWORK,
STR_CREATE_HOTSPOT,
STR_JOIN_DESC,
STR_HOTSPOT_DESC,
STR_STARTING_HOTSPOT,
STR_HOTSPOT_MODE,
STR_CONNECT_WIFI_HINT,
STR_OPEN_URL_HINT,
STR_OR_HTTP_PREFIX,
STR_SCAN_QR_HINT,
STR_CALIBRE_WIRELESS,
STR_CALIBRE_WEB_URL,
STR_CONNECT_WIRELESS,
STR_NETWORK_LEGEND,
STR_MAC_ADDRESS,
STR_CHECKING_WIFI,
STR_ENTER_WIFI_PASSWORD,
STR_ENTER_TEXT,
STR_TO_PREFIX,
STR_CALIBRE_DISCOVERING,
STR_CALIBRE_CONNECTING_TO,
STR_CALIBRE_CONNECTED_TO,
STR_CALIBRE_WAITING_COMMANDS,
STR_CONNECTION_FAILED_RETRYING,
STR_CALIBRE_DISCONNECTED,
STR_CALIBRE_WAITING_TRANSFER,
STR_CALIBRE_TRANSFER_HINT,
STR_CALIBRE_RECEIVING,
STR_CALIBRE_RECEIVED,
STR_CALIBRE_WAITING_MORE,
STR_CALIBRE_FAILED_CREATE_FILE,
STR_CALIBRE_PASSWORD_REQUIRED,
STR_CALIBRE_TRANSFER_INTERRUPTED,
STR_CALIBRE_INSTRUCTION_1,
STR_CALIBRE_INSTRUCTION_2,
STR_CALIBRE_INSTRUCTION_3,
STR_CALIBRE_INSTRUCTION_4,
STR_CAT_DISPLAY,
STR_CAT_READER,
STR_CAT_CONTROLS,
STR_CAT_SYSTEM,
STR_SLEEP_SCREEN,
STR_SLEEP_COVER_MODE,
STR_STATUS_BAR,
STR_HIDE_BATTERY,
STR_EXTRA_SPACING,
STR_TEXT_AA,
STR_IMAGES,
STR_IMAGES_DISPLAY,
STR_IMAGES_PLACEHOLDER,
STR_IMAGES_SUPPRESS,
STR_SHORT_PWR_BTN,
STR_ORIENTATION,
STR_FRONT_BTN_LAYOUT,
STR_SIDE_BTN_LAYOUT,
STR_LONG_PRESS_SKIP,
STR_FONT_FAMILY,
STR_EXT_READER_FONT,
STR_EXT_CHINESE_FONT,
STR_EXT_UI_FONT,
STR_FONT_SIZE,
STR_LINE_SPACING,
STR_ASCII_LETTER_SPACING,
STR_ASCII_DIGIT_SPACING,
STR_CJK_SPACING,
STR_COLOR_MODE,
STR_SCREEN_MARGIN,
STR_PARA_ALIGNMENT,
STR_HYPHENATION,
STR_TIME_TO_SLEEP,
STR_REFRESH_FREQ,
STR_CALIBRE_SETTINGS,
STR_KOREADER_SYNC,
STR_CHECK_UPDATES,
STR_LANGUAGE,
STR_SELECT_WALLPAPER,
STR_CLEAR_READING_CACHE,
STR_CALIBRE,
STR_USERNAME,
STR_PASSWORD,
STR_SYNC_SERVER_URL,
STR_DOCUMENT_MATCHING,
STR_AUTHENTICATE,
STR_KOREADER_USERNAME,
STR_KOREADER_PASSWORD,
STR_FILENAME,
STR_BINARY,
STR_SET_CREDENTIALS_FIRST,
STR_WIFI_CONN_FAILED,
STR_AUTHENTICATING,
STR_AUTH_SUCCESS,
STR_KOREADER_AUTH,
STR_SYNC_READY,
STR_AUTH_FAILED,
STR_DONE,
STR_CLEAR_CACHE_WARNING_1,
STR_CLEAR_CACHE_WARNING_2,
STR_CLEAR_CACHE_WARNING_3,
STR_CLEAR_CACHE_WARNING_4,
STR_CLEARING_CACHE,
STR_CACHE_CLEARED,
STR_ITEMS_REMOVED,
STR_FAILED_LOWER,
STR_CLEAR_CACHE_FAILED,
STR_CHECK_SERIAL_OUTPUT,
STR_DARK,
STR_LIGHT,
STR_CUSTOM,
STR_COVER,
STR_NONE_OPT,
STR_FIT,
STR_CROP,
STR_NO_PROGRESS,
STR_FULL_OPT,
STR_NEVER,
STR_IN_READER,
STR_ALWAYS,
STR_IGNORE,
STR_SLEEP,
STR_PAGE_TURN,
STR_PORTRAIT,
STR_LANDSCAPE_CW,
STR_INVERTED,
STR_LANDSCAPE_CCW,
STR_FRONT_LAYOUT_BCLR,
STR_FRONT_LAYOUT_LRBC,
STR_FRONT_LAYOUT_LBCR,
STR_PREV_NEXT,
STR_NEXT_PREV,
STR_BOOKERLY,
STR_NOTO_SANS,
STR_OPEN_DYSLEXIC,
STR_SMALL,
STR_MEDIUM,
STR_LARGE,
STR_X_LARGE,
STR_TIGHT,
STR_NORMAL,
STR_WIDE,
STR_JUSTIFY,
STR_ALIGN_LEFT,
STR_CENTER,
STR_ALIGN_RIGHT,
STR_MIN_1,
STR_MIN_5,
STR_MIN_10,
STR_MIN_15,
STR_MIN_30,
STR_PAGES_1,
STR_PAGES_5,
STR_PAGES_10,
STR_PAGES_15,
STR_PAGES_30,
STR_UPDATE,
STR_CHECKING_UPDATE,
STR_NEW_UPDATE,
STR_CURRENT_VERSION,
STR_NEW_VERSION,
STR_UPDATING,
STR_NO_UPDATE,
STR_UPDATE_FAILED,
STR_UPDATE_COMPLETE,
STR_POWER_ON_HINT,
STR_EXTERNAL_FONT,
STR_BUILTIN_DISABLED,
STR_NO_ENTRIES,
STR_DOWNLOADING,
STR_DOWNLOAD_FAILED,
STR_ERROR_MSG,
STR_UNNAMED,
STR_NO_SERVER_URL,
STR_FETCH_FEED_FAILED,
STR_PARSE_FEED_FAILED,
STR_NETWORK_PREFIX,
STR_IP_ADDRESS_PREFIX,
STR_SCAN_QR_WIFI_HINT,
STR_ERROR_GENERAL_FAILURE,
STR_ERROR_NETWORK_NOT_FOUND,
STR_ERROR_CONNECTION_TIMEOUT,
STR_SD_CARD,
STR_BACK,
STR_EXIT,
STR_HOME,
STR_SAVE,
STR_SELECT,
STR_SELECTED,
STR_TOGGLE,
STR_CONFIRM,
STR_CANCEL,
STR_CONNECT,
STR_OPEN,
STR_DOWNLOAD,
STR_RETRY,
STR_YES,
STR_NO,
STR_SHOW,
STR_HIDE,
STR_STATE_ON,
STR_STATE_OFF,
STR_NOT_SET,
STR_DIR_LEFT,
STR_DIR_RIGHT,
STR_DIR_UP,
STR_DIR_DOWN,
STR_CAPS_ON,
STR_CAPS_OFF,
STR_OK_BUTTON,
STR_SLEEP_COVER_FILTER,
STR_FILTER_CONTRAST,
STR_CUSTOMISE_STATUS_BAR,
STR_CHAPTER_PAGE_COUNT,
STR_BOOK_PROGRESS_PERCENTAGE,
STR_PROGRESS_BAR,
STR_PROGRESS_BAR_THICKNESS,
STR_PROGRESS_BAR_THIN,
STR_PROGRESS_BAR_MEDIUM,
STR_PROGRESS_BAR_THICK,
STR_BOOK,
STR_CHAPTER,
STR_EXAMPLE_CHAPTER,
STR_EXAMPLE_BOOK,
STR_PREVIEW,
STR_TITLE,
STR_BATTERY,
STR_UI_THEME,
STR_THEME_CLASSIC,
STR_THEME_LYRA,
STR_THEME_LYRA_EXTENDED,
STR_SUNLIGHT_FADING_FIX,
STR_REMAP_FRONT_BUTTONS,
STR_OPDS_BROWSER,
STR_COVER_CUSTOM,
STR_RECENTS,
STR_MENU_RECENT_BOOKS,
STR_NO_RECENT_BOOKS,
STR_CALIBRE_DESC,
STR_FORGET_AND_REMOVE,
STR_FORGET_BUTTON,
STR_CALIBRE_STARTING,
STR_CALIBRE_SETUP,
STR_CALIBRE_STATUS,
STR_CLEAR_BUTTON,
STR_DEFAULT_VALUE,
STR_REMAP_PROMPT,
STR_UNASSIGNED,
STR_ALREADY_ASSIGNED,
STR_REMAP_RESET_HINT,
STR_REMAP_CANCEL_HINT,
STR_HW_BACK_LABEL,
STR_HW_CONFIRM_LABEL,
STR_HW_LEFT_LABEL,
STR_HW_RIGHT_LABEL,
STR_GO_TO_PERCENT,
STR_GO_HOME_BUTTON,
STR_SYNC_PROGRESS,
STR_PUSH_AND_SLEEP,
STR_DELETE_CACHE,
STR_DELETE,
STR_DISPLAY_QR,
STR_CHAPTER_PREFIX,
STR_PAGES_SEPARATOR,
STR_BOOK_PREFIX,
STR_KBD_SHIFT,
STR_KBD_SHIFT_CAPS,
STR_KBD_LOCK,
STR_CALIBRE_URL_HINT,
STR_PERCENT_STEP_HINT,
STR_SYNCING_TIME,
STR_CALC_HASH,
STR_HASH_FAILED,
STR_FETCH_PROGRESS,
STR_UPLOAD_PROGRESS,
STR_NO_CREDENTIALS_MSG,
STR_KOREADER_SETUP_HINT,
STR_PROGRESS_FOUND,
STR_REMOTE_LABEL,
STR_LOCAL_LABEL,
STR_PAGE_OVERALL_FORMAT,
STR_PAGE_TOTAL_OVERALL_FORMAT,
STR_DEVICE_FROM_FORMAT,
STR_APPLY_REMOTE,
STR_UPLOAD_LOCAL,
STR_NO_REMOTE_MSG,
STR_UPLOAD_PROMPT,
STR_UPLOAD_SUCCESS,
STR_SYNC_FAILED_MSG,
STR_SECTION_PREFIX,
STR_UPLOAD,
STR_BOOK_S_STYLE,
STR_EMBEDDED_STYLE,
STR_OPDS_SERVER_URL,
STR_FOOTNOTES,
STR_NO_FOOTNOTES,
STR_LINK,
STR_SCREENSHOT_BUTTON,
STR_AUTO_TURN_ENABLED,
STR_AUTO_TURN_PAGES_PER_MIN,
STR_CAT_CLOCK,
STR_CLOCK,
STR_OFF,
STR_CLOCK_AMPM,
STR_CLOCK_24H,
STR_SET_TIME,
STR_CLOCK_SIZE,
STR_CLOCK_SIZE_SMALL,
STR_CLOCK_SIZE_MEDIUM,
STR_CLOCK_SIZE_LARGE,
STR_TIMEZONE,
STR_TZ_UTC,
STR_TZ_EASTERN,
STR_TZ_CENTRAL,
STR_TZ_MOUNTAIN,
STR_TZ_PACIFIC,
STR_TZ_ALASKA,
STR_TZ_HAWAII,
STR_TZ_CUSTOM,
STR_SET_UTC_OFFSET,
STR_SYNC_CLOCK,
STR_TIME_SYNCED,
STR_AUTO_NTP_SYNC,
STR_LETTERBOX_FILL,
STR_DITHERED,
STR_SOLID,
STR_ADD_BOOKMARK,
STR_REMOVE_BOOKMARK,
STR_LOOKUP_WORD,
STR_LOOKUP_HISTORY,
STR_GO_TO_BOOKMARK,
STR_CLOSE_BOOK,
STR_DELETE_DICT_CACHE,
STR_DEFAULT_OPTION,
STR_BOOKMARK_ADDED,
STR_BOOKMARK_REMOVED,
STR_DICT_CACHE_DELETED,
STR_NO_CACHE_TO_DELETE,
STR_TABLE_OF_CONTENTS,
STR_TOGGLE_ORIENTATION,
STR_TOGGLE_FONT_SIZE,
STR_OVERRIDE_LETTERBOX_FILL,
STR_PREFERRED_PORTRAIT,
STR_PREFERRED_LANDSCAPE,
STR_CHOOSE_SOMETHING,
STR_INDEXING_DISPLAY,
STR_INDEXING_POPUP,
STR_INDEXING_STATUS_TEXT,
STR_INDEXING_STATUS_ICON,
STR_DICTIONARY,
STR_MANAGE_BOOK,
STR_ARCHIVE_BOOK,
STR_UNARCHIVE_BOOK,
STR_DELETE_BOOK,
STR_DELETE_CACHE_ONLY,
STR_REINDEX_BOOK,
STR_BROWSE_ARCHIVE,
STR_BOOK_ARCHIVED,
STR_BOOK_UNARCHIVED,
STR_BOOK_DELETED,
STR_CACHE_DELETED,
STR_BOOK_REINDEXED,
STR_ACTION_FAILED,
STR_BACK_TO_BEGINNING,
STR_CLOSE_MENU,
STR_ADD_SERVER,
STR_SERVER_NAME,
STR_NO_SERVERS,
STR_DELETE_SERVER,
STR_DELETE_CONFIRM,
STR_OPDS_SERVERS,
STR_SAVE_HERE,
STR_SELECT_FOLDER,
STR_DOWNLOAD_PATH,
STR_POSITION,
STR_DOWNLOAD_COMPLETE,
STR_OPEN_BOOK,
STR_BACK_TO_LISTING,
STR_AFTER_DOWNLOAD,
// Sentinel - must be last
_COUNT
};
// Helper function to get string array for a language
inline const char* const* getStringArray(Language lang) {
switch (lang) {
case Language::EN:
return i18n_strings::STRINGS_EN;
case Language::ES:
return i18n_strings::STRINGS_ES;
case Language::FR:
return i18n_strings::STRINGS_FR;
case Language::DE:
return i18n_strings::STRINGS_DE;
case Language::CS:
return i18n_strings::STRINGS_CS;
case Language::PT:
return i18n_strings::STRINGS_PO;
case Language::RU:
return i18n_strings::STRINGS_RU;
case Language::SV:
return i18n_strings::STRINGS_SV;
case Language::RO:
return i18n_strings::STRINGS_RO;
case Language::CA:
return i18n_strings::STRINGS_CA;
case Language::UK:
return i18n_strings::STRINGS_UK;
case Language::BE:
return i18n_strings::STRINGS_BE;
case Language::IT:
return i18n_strings::STRINGS_IT;
case Language::PL:
return i18n_strings::STRINGS_PL;
case Language::FI:
return i18n_strings::STRINGS_FI;
case Language::DA:
return i18n_strings::STRINGS_DA;
case Language::NL:
return i18n_strings::STRINGS_NL;
case Language::TR:
return i18n_strings::STRINGS_TR;
default:
return i18n_strings::STRINGS_EN;
}
}
// Helper function to get language count
constexpr uint8_t getLanguageCount() { return static_cast<uint8_t>(Language::_COUNT); }
// Sorted language indices by code (auto-generated by gen_i18n.py)
// Order: English, Беларуская, Català, Čeština, Dansk, Deutsch, Español, Suomi, Français, Italiano, Nederlands, Polski, Português (Brasil), Română, Русский, Svenska, Türkçe, Українська
constexpr uint8_t SORTED_LANGUAGE_INDICES[] = {0, 11, 9, 4, 15, 3, 1, 14, 2, 12, 16, 13, 5, 8, 6, 7, 17, 10};
static_assert(sizeof(SORTED_LANGUAGE_INDICES) / sizeof(SORTED_LANGUAGE_INDICES[0]) == getLanguageCount(),
"SORTED_LANGUAGE_INDICES size mismatch");

29
lib/I18n/I18nStrings.h Normal file
View File

@@ -0,0 +1,29 @@
#pragma once
#include <string>
#include "I18nKeys.h"
// THIS FILE IS AUTO-GENERATED BY gen_i18n.py. DO NOT EDIT.
namespace i18n_strings {
extern const char* const STRINGS_EN[];
extern const char* const STRINGS_ES[];
extern const char* const STRINGS_FR[];
extern const char* const STRINGS_DE[];
extern const char* const STRINGS_CS[];
extern const char* const STRINGS_PO[];
extern const char* const STRINGS_RU[];
extern const char* const STRINGS_SV[];
extern const char* const STRINGS_RO[];
extern const char* const STRINGS_CA[];
extern const char* const STRINGS_UK[];
extern const char* const STRINGS_BE[];
extern const char* const STRINGS_IT[];
extern const char* const STRINGS_PL[];
extern const char* const STRINGS_FI[];
extern const char* const STRINGS_DA[];
extern const char* const STRINGS_NL[];
extern const char* const STRINGS_TR[];
} // namespace i18n_strings

View File

@@ -392,6 +392,7 @@ STR_INDEXING_DISPLAY: "Indexing Display"
STR_INDEXING_POPUP: "Popup"
STR_INDEXING_STATUS_TEXT: "Status Bar Text"
STR_INDEXING_STATUS_ICON: "Status Bar Icon"
STR_DICTIONARY: "Dictionary"
STR_MANAGE_BOOK: "Manage Book"
STR_ARCHIVE_BOOK: "Archive Book"
STR_UNARCHIVE_BOOK: "Unarchive Book"

View File

@@ -0,0 +1,94 @@
#include "DictionaryMenuActivity.h"
#include <GfxRenderer.h>
#include <I18n.h>
#include "activities/ActivityResult.h"
#include "MappedInputManager.h"
#include "components/UITheme.h"
#include "fontIds.h"
void DictionaryMenuActivity::buildMenuItems() {
menuItems.clear();
menuItems.reserve(3);
menuItems.push_back({Action::LOOKUP_WORD, StrId::STR_LOOKUP_WORD});
menuItems.push_back({Action::LOOKUP_HISTORY, StrId::STR_LOOKUP_HISTORY});
menuItems.push_back({Action::DELETE_DICT_CACHE, StrId::STR_DELETE_DICT_CACHE});
}
void DictionaryMenuActivity::onEnter() {
Activity::onEnter();
selectedIndex = 0;
requestUpdate();
}
void DictionaryMenuActivity::onExit() { Activity::onExit(); }
void DictionaryMenuActivity::loop() {
buttonNavigator.onNext([this] {
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, static_cast<int>(menuItems.size()));
requestUpdate();
});
buttonNavigator.onPrevious([this] {
selectedIndex = ButtonNavigator::previousIndex(selectedIndex, static_cast<int>(menuItems.size()));
requestUpdate();
});
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
if (selectedIndex < static_cast<int>(menuItems.size())) {
setResult(MenuResult{.action = static_cast<int>(menuItems[selectedIndex].action)});
finish();
return;
}
}
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
ActivityResult r;
r.isCancelled = true;
setResult(std::move(r));
finish();
return;
}
}
void DictionaryMenuActivity::render(RenderLock&&) {
renderer.clearScreen();
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
constexpr int popupMargin = 20;
constexpr int lineHeight = 30;
constexpr int titleHeight = 40;
const int optionCount = static_cast<int>(menuItems.size());
const int popupH = titleHeight + popupMargin + lineHeight * optionCount + popupMargin;
const int popupW = pageWidth - 60;
const int popupX = (pageWidth - popupW) / 2;
const int popupY = (pageHeight - popupH) / 2;
renderer.fillRect(popupX - 2, popupY - 2, popupW + 4, popupH + 4, true);
renderer.fillRect(popupX, popupY, popupW, popupH, false);
renderer.drawText(UI_12_FONT_ID, popupX + popupMargin, popupY + 8, tr(STR_DICTIONARY), true, EpdFontFamily::BOLD);
const int dividerY = popupY + titleHeight;
renderer.fillRect(popupX + 4, dividerY, popupW - 8, 1, true);
const int startY = dividerY + popupMargin / 2;
for (int i = 0; i < optionCount; ++i) {
const int itemY = startY + i * lineHeight;
const bool isSelected = (i == selectedIndex);
if (isSelected) {
renderer.fillRect(popupX + 2, itemY, popupW - 4, lineHeight, true);
}
renderer.drawText(UI_10_FONT_ID, popupX + popupMargin, itemY, I18N.get(menuItems[i].labelId), !isSelected);
}
const auto labels = mappedInput.mapLabels(tr(STR_CANCEL), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();
}

View File

@@ -0,0 +1,40 @@
#pragma once
#include <I18n.h>
#include <string>
#include <vector>
#include "../Activity.h"
#include "util/ButtonNavigator.h"
class DictionaryMenuActivity final : public Activity {
public:
enum class Action {
LOOKUP_WORD,
LOOKUP_HISTORY,
DELETE_DICT_CACHE,
};
explicit DictionaryMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
: Activity("DictionaryMenu", renderer, mappedInput) {
buildMenuItems();
}
void onEnter() override;
void onExit() override;
void loop() override;
void render(RenderLock&&) override;
private:
struct MenuItem {
Action action;
StrId labelId;
};
std::vector<MenuItem> menuItems;
int selectedIndex = 0;
ButtonNavigator buttonNavigator;
void buildMenuItems();
};

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

View File

@@ -26,6 +26,7 @@ class EpubReaderActivity final : public Activity {
float pendingSpineProgress = 0.0f;
bool pendingScreenshot = false;
bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit
bool ignoreNextConfirmRelease = false;
bool automaticPageTurnActive = false;
bool pendingEndOfBookMenu = false;
bool endOfBookMenuOpened = false;

View File

@@ -38,22 +38,17 @@ std::vector<EpubReaderMenuActivity::MenuItem> EpubReaderMenuActivity::buildMenuI
} else {
items.push_back({MenuAction::ADD_BOOKMARK, StrId::STR_ADD_BOOKMARK});
}
items.push_back({MenuAction::LOOKUP_WORD, StrId::STR_LOOKUP_WORD});
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::DICTIONARY, StrId::STR_DICTIONARY});
items.push_back({MenuAction::TOGGLE_ORIENTATION, StrId::STR_TOGGLE_ORIENTATION});
items.push_back({MenuAction::TOGGLE_FONT_SIZE, StrId::STR_TOGGLE_FONT_SIZE});
items.push_back({MenuAction::LETTERBOX_FILL, StrId::STR_OVERRIDE_LETTERBOX_FILL});
items.push_back({MenuAction::TABLE_OF_CONTENTS, StrId::STR_TABLE_OF_CONTENTS});
items.push_back({MenuAction::GO_TO_BOOKMARK, StrId::STR_GO_TO_BOOKMARK});
items.push_back({MenuAction::GO_TO_PERCENT, StrId::STR_GO_TO_PERCENT});
items.push_back({MenuAction::CLOSE_BOOK, StrId::STR_CLOSE_BOOK});
items.push_back({MenuAction::SYNC, StrId::STR_SYNC_PROGRESS});
items.push_back({MenuAction::PUSH_AND_SLEEP, StrId::STR_PUSH_AND_SLEEP});
items.push_back({MenuAction::CLOSE_BOOK, StrId::STR_CLOSE_BOOK});
items.push_back({MenuAction::ARCHIVE_BOOK, StrId::STR_ARCHIVE_BOOK});
items.push_back({MenuAction::DELETE_BOOK, StrId::STR_DELETE_BOOK});
items.push_back({MenuAction::REINDEX_BOOK, StrId::STR_REINDEX_BOOK});
items.push_back({MenuAction::DELETE_CACHE, StrId::STR_DELETE_CACHE});
items.push_back({MenuAction::DELETE_DICT_CACHE, StrId::STR_DELETE_DICT_CACHE});
items.push_back({MenuAction::MANAGE_BOOK, StrId::STR_MANAGE_BOOK});
return items;
}

View File

@@ -38,7 +38,9 @@ class EpubReaderMenuActivity final : public Activity {
ARCHIVE_BOOK,
DELETE_BOOK,
REINDEX_BOOK,
LETTERBOX_FILL
LETTERBOX_FILL,
MANAGE_BOOK,
DICTIONARY
};
explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title,