From 0493f300be4a06afa28e6e20b666d4587eae9b49 Mon Sep 17 00:00:00 2001 From: cottongin Date: Sat, 7 Mar 2026 21:12:09 -0500 Subject: [PATCH] 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 --- lib/I18n/I18nKeys.h | 534 ++++++++++++++++++ lib/I18n/I18nStrings.h | 29 + lib/I18n/translations/english.yaml | 1 + .../reader/DictionaryMenuActivity.cpp | 94 +++ .../reader/DictionaryMenuActivity.h | 40 ++ src/activities/reader/EpubReaderActivity.cpp | 93 ++- src/activities/reader/EpubReaderActivity.h | 1 + .../reader/EpubReaderMenuActivity.cpp | 17 +- .../reader/EpubReaderMenuActivity.h | 4 +- 9 files changed, 800 insertions(+), 13 deletions(-) create mode 100644 lib/I18n/I18nKeys.h create mode 100644 lib/I18n/I18nStrings.h create mode 100644 src/activities/reader/DictionaryMenuActivity.cpp create mode 100644 src/activities/reader/DictionaryMenuActivity.h diff --git a/lib/I18n/I18nKeys.h b/lib/I18n/I18nKeys.h new file mode 100644 index 00000000..ae215c0b --- /dev/null +++ b/lib/I18n/I18nKeys.h @@ -0,0 +1,534 @@ +#pragma once +#include + +// 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(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"); diff --git a/lib/I18n/I18nStrings.h b/lib/I18n/I18nStrings.h new file mode 100644 index 00000000..0d44a460 --- /dev/null +++ b/lib/I18n/I18nStrings.h @@ -0,0 +1,29 @@ +#pragma once +#include + +#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 diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index f04ba181..de2dff7c 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -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" diff --git a/src/activities/reader/DictionaryMenuActivity.cpp b/src/activities/reader/DictionaryMenuActivity.cpp new file mode 100644 index 00000000..c7408821 --- /dev/null +++ b/src/activities/reader/DictionaryMenuActivity.cpp @@ -0,0 +1,94 @@ +#include "DictionaryMenuActivity.h" + +#include +#include + +#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(menuItems.size())); + requestUpdate(); + }); + + buttonNavigator.onPrevious([this] { + selectedIndex = ButtonNavigator::previousIndex(selectedIndex, static_cast(menuItems.size())); + requestUpdate(); + }); + + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + if (selectedIndex < static_cast(menuItems.size())) { + setResult(MenuResult{.action = static_cast(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(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(); +} diff --git a/src/activities/reader/DictionaryMenuActivity.h b/src/activities/reader/DictionaryMenuActivity.h new file mode 100644 index 00000000..7398064a --- /dev/null +++ b/src/activities/reader/DictionaryMenuActivity.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include +#include + +#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 menuItems; + int selectedIndex = 0; + ButtonNavigator buttonNavigator; + + void buildMenuItems(); +}; diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index cdb3f3ad..22946418 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -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 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(renderer, mappedInput, epub->getPath(), isArchived), + [this](ActivityResult result) { + if (result.isCancelled) { + requestUpdate(); + return; + } + const auto& menu = std::get(result.data); + const auto bookAction = static_cast(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(renderer, mappedInput), + [this](ActivityResult result) { + if (result.isCancelled) { + requestUpdate(); + return; + } + const auto& menu = std::get(result.data); + const auto dictAction = static_cast(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; } diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index dc8d0b4b..b366e27b 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -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; diff --git a/src/activities/reader/EpubReaderMenuActivity.cpp b/src/activities/reader/EpubReaderMenuActivity.cpp index 6cb44b93..1f04429a 100644 --- a/src/activities/reader/EpubReaderMenuActivity.cpp +++ b/src/activities/reader/EpubReaderMenuActivity.cpp @@ -38,22 +38,17 @@ std::vector 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; } diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h index 467d8069..4b93fb55 100644 --- a/src/activities/reader/EpubReaderMenuActivity.h +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -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,