feat: Integrate PR #857 dictionary intelligence and sub-activity refactor

Pull in the full feature update from PR #857 while preserving fork
advantages (HTML parsing, custom drawHints, PageForward/PageBack,
cache management, stardictCmp, /.dictionary/ paths).

- Add morphological stemming (getStemVariants), Levenshtein edit
  distance, and fuzzy matching (findSimilar) to Dictionary
- Create DictionarySuggestionsActivity for "Did you mean?" flow
- Add onDone callback to DictionaryDefinitionActivity for direct
  exit-to-reader via "Done" button
- Refactor DictionaryWordSelectActivity to ActivityWithSubactivity
  with cascading lookup (exact → stems → suggestions → not found),
  en-dash/em-dash splitting, and cross-page hyphenation
- Refactor LookedUpWordsActivity with reverse-chronological order,
  inline cascading lookup, UITheme-aware rendering, and sub-activities
- Simplify EpubReaderActivity LOOKUP/LOOKED_UP_WORDS handlers

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
cottongin
2026-02-14 20:50:03 -05:00
parent c1dfe92ea3
commit 5dc9d21bdb
12 changed files with 746 additions and 105 deletions

View File

@@ -19,7 +19,6 @@
#include "fontIds.h"
#include "util/BookmarkStore.h"
#include "util/Dictionary.h"
#include "util/LookupHistory.h"
namespace {
// pagesPerRefresh now comes from SETTINGS.getRefreshFrequency()
@@ -665,24 +664,27 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
const std::string bookCachePath = epub->getCachePath();
const uint8_t currentOrientation = SETTINGS.orientation;
// Get first word of next page for cross-page hyphenation
std::string nextPageFirstWord;
if (section && section->currentPage < section->pageCount - 1) {
int savedPage = section->currentPage;
section->currentPage = savedPage + 1;
auto nextPage = section->loadPageFromSectionFile();
section->currentPage = savedPage;
if (nextPage && !nextPage->elements.empty()) {
const auto* firstLine = static_cast<const PageLine*>(nextPage->elements[0].get());
if (firstLine->getBlock() && !firstLine->getBlock()->getWords().empty()) {
nextPageFirstWord = firstLine->getBlock()->getWords().front();
}
}
}
exitActivity();
if (pageForLookup) {
enterNewActivity(new DictionaryWordSelectActivity(
renderer, mappedInput, std::move(pageForLookup), readerFontId, orientedMarginLeft, orientedMarginTop,
bookCachePath, currentOrientation,
[this]() {
// On back from word select
pendingSubactivityExit = true;
},
[this, bookCachePath, readerFontId, currentOrientation](const std::string& headword,
const std::string& definition) {
// On successful lookup - show definition
exitActivity();
enterNewActivity(new DictionaryDefinitionActivity(renderer, mappedInput, headword, definition,
readerFontId, currentOrientation,
[this]() { pendingSubactivityExit = true; }));
}));
bookCachePath, currentOrientation, [this]() { pendingSubactivityExit = true; }, nextPageFirstWord));
}
xSemaphoreGive(renderingMutex);
@@ -690,36 +692,11 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
}
case EpubReaderMenuActivity::MenuAction::LOOKED_UP_WORDS: {
xSemaphoreTake(renderingMutex, portMAX_DELAY);
const std::string bookCachePath = epub->getCachePath();
const int readerFontId = SETTINGS.getReaderFontId();
const uint8_t currentOrientation = SETTINGS.orientation;
exitActivity();
enterNewActivity(new LookedUpWordsActivity(
renderer, mappedInput, bookCachePath,
[this]() {
// On back from looked up words
pendingSubactivityExit = true;
},
[this, bookCachePath, readerFontId, currentOrientation](const std::string& headword) {
// Look up the word and show definition with progress bar
Rect popupLayout = GUI.drawPopup(renderer, "Looking up...");
std::string definition = Dictionary::lookup(
headword, [this, &popupLayout](int percent) { GUI.fillPopupProgress(renderer, popupLayout, percent); });
if (definition.empty()) {
GUI.drawPopup(renderer, "Not found");
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
vTaskDelay(1500 / portTICK_PERIOD_MS);
return;
}
exitActivity();
enterNewActivity(new DictionaryDefinitionActivity(renderer, mappedInput, headword, definition, readerFontId,
currentOrientation,
[this]() { pendingSubactivityExit = true; }));
}));
renderer, mappedInput, epub->getCachePath(), SETTINGS.getReaderFontId(), SETTINGS.orientation,
[this]() { pendingSubactivityExit = true; }, [this]() { pendingSubactivityExit = true; }));
xSemaphoreGive(renderingMutex);
break;
}