mod: restore missing mod features from resync audit
Re-add KOReaderSyncActivity PUSH_ONLY mode (PR #1090): - SyncMode enum with INTERACTIVE/PUSH_ONLY, deferFinish pattern - Push & Sleep menu action in EpubReaderMenuActivity - ActivityManager::requestSleep() for activity-initiated sleep - main.cpp checks isSleepRequested() each loop iteration Wire EndOfBookMenuActivity into EpubReaderActivity: - pendingEndOfBookMenu deferred flag avoids render-lock deadlock - Handles all 6 actions: ARCHIVE, DELETE, TABLE_OF_CONTENTS, BACK_TO_BEGINNING, CLOSE_BOOK, CLOSE_MENU Add book management to reader menu: - ARCHIVE_BOOK, DELETE_BOOK, REINDEX_BOOK actions with handlers Port silent next-chapter pre-indexing: - silentIndexNextChapterIfNeeded() proactively indexes next chapter when user is near end of current one, eliminating load screens Add per-book letterbox fill toggle in reader menu: - LETTERBOX_FILL cycles Default/Dithered/Solid/None - Loads/saves per-book override via BookSettings - bookCachePath constructor param added to EpubReaderMenuActivity Made-with: Cursor
This commit is contained in:
50
chat-summaries/2026-03-07_18-00-summary.md
Normal file
50
chat-summaries/2026-03-07_18-00-summary.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Fresh Replay: Sync mod/master with upstream/master (continued)
|
||||
|
||||
## Task
|
||||
Continue the "Fresh Replay" synchronization of mod/master with upstream/master (HEAD: 170cc25). This session picked up from Phase 2c (GfxRenderer/theme modifications) and completed through Phase 4 (verification).
|
||||
|
||||
## Changes Made
|
||||
|
||||
### Phase 2c-e: GfxRenderer, themes, SleepActivity, SettingsActivity, platformio
|
||||
- Added `drawPixelGray` to GfxRenderer for letterbox fill rendering
|
||||
- Added `PRERENDER_THUMB_HEIGHTS` to UITheme for placeholder cover generation
|
||||
- Added `[env:mod]` build environment to platformio.ini
|
||||
- Implemented sleep screen letterbox fill (solid/dithered) with edge caching in SleepActivity
|
||||
- Added placeholder cover fallback (PlaceholderCoverGenerator) for XTC/TXT/EPUB sleep screens
|
||||
- Added Clock settings category to SettingsActivity with timezone, NTP sync, set-time actions
|
||||
- Replaced CalibreSettingsActivity with OpdsServerListActivity for OPDS server management
|
||||
- Added DynamicEnum rendering support for settings
|
||||
- Added long-press book management to RecentBooksActivity
|
||||
|
||||
### Phase 3: Re-port unmerged upstream PRs
|
||||
- **#1055** (byte-level framebuffer writes): fillPhysicalHSpan*, optimized fillRect/drawLine/fillRectDither/fillPolygon
|
||||
- **#1027** (word-width cache): 128-entry FNV-1a cache, hyphenation early exit (7-9% layout speedup)
|
||||
- **#1068** (URL hyphenation): Already present in upstream
|
||||
- **#1019** (file extensions in browser): Already present in upstream
|
||||
- **#1090/#1185/#1217** (KOReader sync): Binary credential store, document hash caching, ChapterXPathIndexer
|
||||
- **#1209** (OPDS multi-server): OpdsBookBrowserActivity accepts OpdsServer, directory picker, download-complete prompt
|
||||
- **#857** (Dictionary): Activities already ported in Phase 1/2
|
||||
- **#1003** (Placeholder covers): Already integrated in Phase 2
|
||||
|
||||
### Fixes
|
||||
- Added `STR_OFF` i18n string for clock format setting
|
||||
- Fixed include paths (ActivityResult.h from subdirectories)
|
||||
- Replaced `Epub::isValidThumbnailBmp` with `Storage.exists()` (method doesn't exist in upstream)
|
||||
- Replaced `StringUtils::checkFileExtension` with `FsHelpers` equivalents
|
||||
|
||||
### Image pipeline decision
|
||||
- Kept upstream's JPEGDEC implementation — mod's picojpeg was a workaround for the older codebase
|
||||
- No mod-specific image pipeline changes needed
|
||||
|
||||
## Branch Status
|
||||
- **mod/master-resync**: 6 commits ahead of upstream/master (170cc25)
|
||||
- **mod/backup-pre-sync-2026-03-07**: Safety snapshot of original mod/master
|
||||
- 189 files changed, ~114,566 insertions, ~379 deletions vs upstream
|
||||
|
||||
## Follow-up Items
|
||||
- Run full PlatformIO build on hardware to verify compilation
|
||||
- Run clang-format on all modified files
|
||||
- Test on device: clock display, sleep screen letterbox fill, dictionary, OPDS browsing
|
||||
- KOReaderSyncActivity PUSH_ONLY mode (from PR #1090) not yet re-added to activity
|
||||
- Consider adding `StringUtils.h` if other mod code needs `checkFileExtension`
|
||||
- Update mod version string
|
||||
42
chat-summaries/2026-03-07_19-00-summary.md
Normal file
42
chat-summaries/2026-03-07_19-00-summary.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# KOReaderSyncActivity PUSH_ONLY Mode Re-addition
|
||||
|
||||
**Date**: 2026-03-07
|
||||
**Branch**: `mod/master-resync`
|
||||
|
||||
## Task
|
||||
|
||||
Re-add the `PUSH_ONLY` sync mode to `KOReaderSyncActivity` that was lost during the upstream resync/ActivityManager migration (originally from PR #1090). This mode allows the reader to silently push local progress to the KOReader sync server and then enter deep sleep — no interactive UI.
|
||||
|
||||
## Changes
|
||||
|
||||
### `src/activities/reader/KOReaderSyncActivity.h`
|
||||
- Added `enum class SyncMode { INTERACTIVE, PUSH_ONLY }` to the class
|
||||
- Added `syncMode` constructor parameter (defaults to `INTERACTIVE`)
|
||||
- Added `deferFinish(bool success)`, `pendingFinish`, `pendingFinishSuccess` for safe async finish from blocking calls
|
||||
|
||||
### `src/activities/reader/KOReaderSyncActivity.cpp`
|
||||
- Implemented `deferFinish()` — sets a flag that `loop()` picks up to call `setResult()`/`finish()`
|
||||
- `onEnter()`: In PUSH_ONLY mode, silently finish if no credentials (no error UI)
|
||||
- `performSync()`: In PUSH_ONLY mode, skip remote fetch entirely and go straight to `performUpload()`
|
||||
- `performUpload()`: In PUSH_ONLY mode, use `deferFinish()` instead of setting UI state on success/failure
|
||||
- `loop()`: Check `pendingFinish` first and perform deferred finish if set
|
||||
|
||||
### `src/activities/ActivityManager.h`
|
||||
- Added `requestSleep()` / `isSleepRequested()` — allows activities to signal that the device should enter deep sleep. Checked by the main loop.
|
||||
|
||||
### `src/main.cpp`
|
||||
- Added `activityManager.isSleepRequested()` check in the main loop, before the auto-sleep timeout check
|
||||
|
||||
### `src/activities/reader/EpubReaderMenuActivity.h` / `.cpp`
|
||||
- Added `PUSH_AND_SLEEP` to the `MenuAction` enum
|
||||
- Added menu item `{PUSH_AND_SLEEP, STR_PUSH_AND_SLEEP}` between SYNC and CLOSE_BOOK
|
||||
|
||||
### `src/activities/reader/EpubReaderActivity.cpp`
|
||||
- Added `#include "activities/ActivityManager.h"`
|
||||
- Added `PUSH_AND_SLEEP` case in `onReaderMenuConfirm`: launches `KOReaderSyncActivity` in `PUSH_ONLY` mode, then calls `activityManager.requestSleep()` on completion (regardless of success/failure)
|
||||
|
||||
### `lib/I18n/translations/english.yaml` / `lib/I18n/I18nKeys.h`
|
||||
- Added `STR_PUSH_AND_SLEEP: "Push & Sleep"` and regenerated I18n keys
|
||||
|
||||
## Follow-up Items
|
||||
- None
|
||||
61
chat-summaries/2026-03-07_20-00-summary.md
Normal file
61
chat-summaries/2026-03-07_20-00-summary.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Missing Mod Features Audit — Implementation
|
||||
|
||||
**Date**: 2026-03-07
|
||||
**Branch**: `mod/master-resync`
|
||||
|
||||
## Task
|
||||
|
||||
Comprehensive audit of `mod/master-resync` vs `mod/backup-pre-sync-2026-03-07` identified 4 mod features lost during the upstream resync. All 4 have been re-implemented.
|
||||
|
||||
## Changes
|
||||
|
||||
### 1. EndOfBookMenuActivity wired into EpubReaderActivity (HIGH)
|
||||
|
||||
**Files**: `EpubReaderActivity.h`, `EpubReaderActivity.cpp`
|
||||
|
||||
- Added `pendingEndOfBookMenu` and `endOfBookMenuOpened` flags
|
||||
- In `render()`: when reaching end-of-book, sets `pendingEndOfBookMenu = true` (deferred to avoid render-lock deadlock)
|
||||
- In `loop()`: checks flag and launches `EndOfBookMenuActivity` via `startActivityForResult`
|
||||
- Result handler covers all 6 actions: ARCHIVE (→ goHome), DELETE (→ goHome), TABLE_OF_CONTENTS (→ last chapter), BACK_TO_BEGINNING (→ spine 0), CLOSE_BOOK (→ goHome), CLOSE_MENU (→ stay at end)
|
||||
- Added `#include "EndOfBookMenuActivity.h"` and `#include "util/BookManager.h"`
|
||||
|
||||
### 2. Book management from reader menu (MEDIUM)
|
||||
|
||||
**Files**: `EpubReaderMenuActivity.h`, `EpubReaderMenuActivity.cpp`, `EpubReaderActivity.cpp`
|
||||
|
||||
- Added `ARCHIVE_BOOK`, `DELETE_BOOK`, `REINDEX_BOOK` to `MenuAction` enum
|
||||
- Added corresponding menu items between CLOSE_BOOK and DELETE_CACHE
|
||||
- Added handlers in `onReaderMenuConfirm`: each calls `BookManager::archiveBook/deleteBook/reindexBook` then `activityManager.goHome()`
|
||||
|
||||
### 3. Silent next-chapter pre-indexing (MEDIUM)
|
||||
|
||||
**Files**: `EpubReaderActivity.h`, `EpubReaderActivity.cpp`
|
||||
|
||||
- Added `preIndexedNextSpine` field and `silentIndexNextChapterIfNeeded()` method
|
||||
- Triggers when user is on second-to-last or last page of a chapter
|
||||
- Creates section file for `currentSpineIndex + 1` in advance
|
||||
- Called after every page turn in `loop()`
|
||||
- ~35 lines of self-contained implementation
|
||||
|
||||
### 4. Letterbox fill toggle in reader menu (LOW)
|
||||
|
||||
**Files**: `EpubReaderMenuActivity.h`, `EpubReaderMenuActivity.cpp`, `EpubReaderActivity.cpp`
|
||||
|
||||
- Added `LETTERBOX_FILL` to `MenuAction` enum
|
||||
- Added `bookCachePath` constructor parameter (with default `""` for backward compat)
|
||||
- Added per-book `pendingLetterboxFill`, `letterboxFillLabels`, `letterboxFillToIndex()`, `indexToLetterboxFill()`, `saveLetterboxFill()`
|
||||
- Cycles Default → Dithered → Solid → None → Default on Confirm
|
||||
- Renders current value on right edge of menu item
|
||||
- Loads/saves per-book setting via `BookSettings`
|
||||
- Updated call site in `EpubReaderActivity` to pass `epub->getCachePath()`
|
||||
|
||||
## Audit False Positives (confirmed NOT gaps)
|
||||
|
||||
- GfxRenderer kerning/ligatures/wrappedText — present on resync
|
||||
- HttpDownloader auth fallback — present with OPDS settings fallback
|
||||
- Lyra3CoversTheme — exists on resync
|
||||
- ActivityWithSubactivity → Activity migration — intentional upstream change
|
||||
- EndOfBookMenuActivity callbacks → setResult/finish — correctly migrated
|
||||
|
||||
## Follow-up Items
|
||||
- None
|
||||
@@ -300,6 +300,7 @@ STR_HW_RIGHT_LABEL: "Right (4th button)"
|
||||
STR_GO_TO_PERCENT: "Go to %"
|
||||
STR_GO_HOME_BUTTON: "Go Home"
|
||||
STR_SYNC_PROGRESS: "Sync Progress"
|
||||
STR_PUSH_AND_SLEEP: "Push & Sleep"
|
||||
STR_DELETE_CACHE: "Delete Book Cache"
|
||||
STR_DELETE: "Delete"
|
||||
STR_DISPLAY_QR: "Show page as QR"
|
||||
|
||||
@@ -63,6 +63,8 @@ class ActivityManager {
|
||||
// This variable must only be set by the main loop, to avoid race conditions
|
||||
bool requestedUpdate = false;
|
||||
|
||||
bool sleepRequested = false;
|
||||
|
||||
public:
|
||||
explicit ActivityManager(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||
: renderer(renderer), mappedInput(mappedInput), renderingMutex(xSemaphoreCreateMutex()) {
|
||||
@@ -101,6 +103,11 @@ class ActivityManager {
|
||||
bool isReaderActivity() const;
|
||||
bool skipLoopDelay() const;
|
||||
|
||||
// Activities can request sleep (e.g. PUSH_AND_SLEEP). The main loop checks
|
||||
// this flag after each loop() call and triggers enterDeepSleep() if set.
|
||||
void requestSleep() { sleepRequested = true; }
|
||||
bool isSleepRequested() const { return sleepRequested; }
|
||||
|
||||
// If immediate is true, the update will be triggered immediately.
|
||||
// Otherwise, it will be deferred until the end of the current loop iteration.
|
||||
void requestUpdate(bool immediate = false);
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "CrossPointState.h"
|
||||
#include "activities/ActivityManager.h"
|
||||
#include "DictionaryWordSelectActivity.h"
|
||||
#include "EndOfBookMenuActivity.h"
|
||||
#include "EpubReaderBookmarkSelectionActivity.h"
|
||||
#include "EpubReaderChapterSelectionActivity.h"
|
||||
#include "EpubReaderFootnotesActivity.h"
|
||||
@@ -23,6 +25,7 @@
|
||||
#include "RecentBooksStore.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
#include "util/BookManager.h"
|
||||
#include "util/BookmarkStore.h"
|
||||
#include "util/Dictionary.h"
|
||||
#include "util/ScreenshotUtil.h"
|
||||
@@ -133,6 +136,53 @@ void EpubReaderActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingEndOfBookMenu) {
|
||||
pendingEndOfBookMenu = false;
|
||||
endOfBookMenuOpened = true;
|
||||
startActivityForResult(
|
||||
std::make_unique<EndOfBookMenuActivity>(renderer, mappedInput, epub->getPath()),
|
||||
[this](const ActivityResult& result) {
|
||||
if (result.isCancelled) {
|
||||
return;
|
||||
}
|
||||
const auto action = static_cast<EndOfBookMenuActivity::Action>(std::get<MenuResult>(result.data).action);
|
||||
switch (action) {
|
||||
case EndOfBookMenuActivity::Action::ARCHIVE:
|
||||
BookManager::archiveBook(epub->getPath());
|
||||
activityManager.goHome();
|
||||
return;
|
||||
case EndOfBookMenuActivity::Action::DELETE:
|
||||
BookManager::deleteBook(epub->getPath());
|
||||
activityManager.goHome();
|
||||
return;
|
||||
case EndOfBookMenuActivity::Action::TABLE_OF_CONTENTS: {
|
||||
endOfBookMenuOpened = false;
|
||||
RenderLock lock(*this);
|
||||
currentSpineIndex = epub->getSpineItemsCount() - 1;
|
||||
nextPageNumber = UINT16_MAX;
|
||||
section.reset();
|
||||
break;
|
||||
}
|
||||
case EndOfBookMenuActivity::Action::BACK_TO_BEGINNING: {
|
||||
endOfBookMenuOpened = false;
|
||||
RenderLock lock(*this);
|
||||
currentSpineIndex = 0;
|
||||
nextPageNumber = 0;
|
||||
section.reset();
|
||||
break;
|
||||
}
|
||||
case EndOfBookMenuActivity::Action::CLOSE_BOOK:
|
||||
activityManager.goHome();
|
||||
return;
|
||||
case EndOfBookMenuActivity::Action::CLOSE_MENU:
|
||||
endOfBookMenuOpened = false;
|
||||
break;
|
||||
}
|
||||
requestUpdate();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (automaticPageTurnActive) {
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) ||
|
||||
mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||
@@ -173,7 +223,8 @@ void EpubReaderActivity::loop() {
|
||||
section ? BookmarkStore::hasBookmark(epub->getCachePath(), currentSpineIndex, section->currentPage) : false;
|
||||
startActivityForResult(std::make_unique<EpubReaderMenuActivity>(
|
||||
renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent,
|
||||
SETTINGS.orientation, !currentPageFootnotes.empty(), isBookmarked, SETTINGS.fontSize),
|
||||
SETTINGS.orientation, !currentPageFootnotes.empty(), isBookmarked, SETTINGS.fontSize,
|
||||
epub->getCachePath()),
|
||||
[this](const ActivityResult& result) {
|
||||
// Always apply orientation and font size even if the menu was cancelled
|
||||
const auto& menu = std::get<MenuResult>(result.data);
|
||||
@@ -254,6 +305,8 @@ void EpubReaderActivity::loop() {
|
||||
} else {
|
||||
pageTurn(true);
|
||||
}
|
||||
|
||||
silentIndexNextChapterIfNeeded();
|
||||
}
|
||||
|
||||
// Translate an absolute percent into a spine index plus a normalized position
|
||||
@@ -441,6 +494,19 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EpubReaderMenuActivity::MenuAction::PUSH_AND_SLEEP: {
|
||||
const int cp = section ? section->currentPage : 0;
|
||||
const int tp = section ? section->pageCount : 0;
|
||||
if (KOREADER_STORE.hasCredentials()) {
|
||||
startActivityForResult(
|
||||
std::make_unique<KOReaderSyncActivity>(renderer, mappedInput, epub, epub->getPath(), currentSpineIndex, cp,
|
||||
tp, KOReaderSyncActivity::SyncMode::PUSH_ONLY),
|
||||
[](const ActivityResult&) { activityManager.requestSleep(); });
|
||||
} else {
|
||||
activityManager.requestSleep();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EpubReaderMenuActivity::MenuAction::CLOSE_BOOK:
|
||||
onGoHome();
|
||||
return;
|
||||
@@ -514,6 +580,23 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
requestUpdate();
|
||||
break;
|
||||
}
|
||||
case EpubReaderMenuActivity::MenuAction::ARCHIVE_BOOK: {
|
||||
if (epub) BookManager::archiveBook(epub->getPath());
|
||||
activityManager.goHome();
|
||||
return;
|
||||
}
|
||||
case EpubReaderMenuActivity::MenuAction::DELETE_BOOK: {
|
||||
if (epub) BookManager::deleteBook(epub->getPath());
|
||||
activityManager.goHome();
|
||||
return;
|
||||
}
|
||||
case EpubReaderMenuActivity::MenuAction::REINDEX_BOOK: {
|
||||
if (epub) BookManager::reindexBook(epub->getPath(), false);
|
||||
activityManager.goHome();
|
||||
return;
|
||||
}
|
||||
case EpubReaderMenuActivity::MenuAction::LETTERBOX_FILL:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,6 +701,45 @@ void EpubReaderActivity::pageTurn(bool isForwardTurn) {
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
bool EpubReaderActivity::silentIndexNextChapterIfNeeded() {
|
||||
if (!epub || !section) return false;
|
||||
if (preIndexedNextSpine == currentSpineIndex + 1) return false;
|
||||
|
||||
const bool nearEnd = (section->pageCount == 1 && section->currentPage == 0) ||
|
||||
(section->pageCount >= 2 && section->currentPage == section->pageCount - 2);
|
||||
if (!nearEnd) return false;
|
||||
|
||||
const int nextSpine = currentSpineIndex + 1;
|
||||
if (nextSpine >= epub->getSpineItemsCount()) return false;
|
||||
|
||||
int marginTop, marginRight, marginBottom, marginLeft;
|
||||
renderer.getOrientedViewableTRBL(&marginTop, &marginRight, &marginBottom, &marginLeft);
|
||||
marginTop += SETTINGS.screenMargin;
|
||||
marginLeft += SETTINGS.screenMargin;
|
||||
marginRight += SETTINGS.screenMargin;
|
||||
marginBottom += std::max(SETTINGS.screenMargin, UITheme::getInstance().getStatusBarHeight());
|
||||
const uint16_t vpWidth = renderer.getScreenWidth() - marginLeft - marginRight;
|
||||
const uint16_t vpHeight = renderer.getScreenHeight() - marginTop - marginBottom;
|
||||
|
||||
Section nextSection(epub, nextSpine, renderer);
|
||||
if (nextSection.loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
|
||||
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, vpWidth, vpHeight,
|
||||
SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, SETTINGS.imageRendering)) {
|
||||
preIndexedNextSpine = nextSpine;
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_DBG("ERS", "Silently indexing next chapter: %d", nextSpine);
|
||||
if (!nextSection.createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
|
||||
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, vpWidth, vpHeight,
|
||||
SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, SETTINGS.imageRendering)) {
|
||||
LOG_ERR("ERS", "Failed silent indexing for chapter: %d", nextSpine);
|
||||
return false;
|
||||
}
|
||||
preIndexedNextSpine = nextSpine;
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Failure handling
|
||||
void EpubReaderActivity::render(RenderLock&& lock) {
|
||||
if (!epub) {
|
||||
@@ -633,12 +755,15 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
||||
currentSpineIndex = epub->getSpineItemsCount();
|
||||
}
|
||||
|
||||
// Show end of book screen
|
||||
// Show end of book screen (defer menu launch to loop() to avoid deadlock)
|
||||
if (currentSpineIndex == epub->getSpineItemsCount()) {
|
||||
renderer.clearScreen();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_END_OF_BOOK), true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
automaticPageTurnActive = false;
|
||||
if (!endOfBookMenuOpened) {
|
||||
pendingEndOfBookMenu = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,13 @@ class EpubReaderActivity final : public Activity {
|
||||
bool pendingScreenshot = false;
|
||||
bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit
|
||||
bool automaticPageTurnActive = false;
|
||||
bool pendingEndOfBookMenu = false;
|
||||
bool endOfBookMenuOpened = false;
|
||||
|
||||
// Silent pre-indexing: proactively creates section files for the next chapter
|
||||
// when the user is near the end of the current one, eliminating load times.
|
||||
int preIndexedNextSpine = -1;
|
||||
bool silentIndexNextChapterIfNeeded();
|
||||
|
||||
// Footnote support
|
||||
std::vector<FootnoteEntry> currentPageFootnotes;
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::string& title, const int currentPage, const int totalPages,
|
||||
const int bookProgressPercent, const uint8_t currentOrientation,
|
||||
const bool hasFootnotes, bool isBookmarked, uint8_t currentFontSize)
|
||||
const bool hasFootnotes, bool isBookmarked, uint8_t currentFontSize,
|
||||
const std::string& bookCachePath)
|
||||
: Activity("EpubReaderMenu", renderer, mappedInput),
|
||||
menuItems(buildMenuItems(hasFootnotes, isBookmarked)),
|
||||
title(title),
|
||||
@@ -19,12 +20,18 @@ EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInpu
|
||||
pendingFontSize(currentFontSize < CrossPointSettings::FONT_SIZE_COUNT ? currentFontSize : 0),
|
||||
currentPage(currentPage),
|
||||
totalPages(totalPages),
|
||||
bookProgressPercent(bookProgressPercent) {}
|
||||
bookProgressPercent(bookProgressPercent),
|
||||
bookCachePath(bookCachePath) {
|
||||
if (!bookCachePath.empty()) {
|
||||
auto bookSettings = BookSettings::load(bookCachePath);
|
||||
pendingLetterboxFill = bookSettings.letterboxFillOverride;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<EpubReaderMenuActivity::MenuItem> EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes,
|
||||
bool isBookmarked) {
|
||||
std::vector<MenuItem> items;
|
||||
items.reserve(13);
|
||||
items.reserve(16);
|
||||
// Mod menu order
|
||||
if (isBookmarked) {
|
||||
items.push_back({MenuAction::REMOVE_BOOKMARK, StrId::STR_REMOVE_BOOKMARK});
|
||||
@@ -38,8 +45,13 @@ std::vector<EpubReaderMenuActivity::MenuItem> EpubReaderMenuActivity::buildMenuI
|
||||
items.push_back({MenuAction::GO_TO_PERCENT, StrId::STR_GO_TO_PERCENT});
|
||||
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::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});
|
||||
return items;
|
||||
@@ -81,6 +93,14 @@ void EpubReaderMenuActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedAction == MenuAction::LETTERBOX_FILL) {
|
||||
int idx = (letterboxFillToIndex() + 1) % LETTERBOX_FILL_OPTION_COUNT;
|
||||
pendingLetterboxFill = indexToLetterboxFill(idx);
|
||||
if (!bookCachePath.empty()) saveLetterboxFill();
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
setResult(MenuResult{static_cast<int>(selectedAction), pendingOrientation, selectedPageTurnOption, pendingFontSize});
|
||||
finish();
|
||||
return;
|
||||
@@ -155,6 +175,12 @@ void EpubReaderMenuActivity::render(RenderLock&&) {
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, value);
|
||||
renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected);
|
||||
}
|
||||
|
||||
if (menuItems[i].action == MenuAction::LETTERBOX_FILL) {
|
||||
const char* value = I18N.get(letterboxFillLabels[letterboxFillToIndex()]);
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, value);
|
||||
renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected);
|
||||
}
|
||||
}
|
||||
|
||||
// Footer / Hints
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "../Activity.h"
|
||||
#include "util/BookSettings.h"
|
||||
#include "util/ButtonNavigator.h"
|
||||
|
||||
class EpubReaderMenuActivity final : public Activity {
|
||||
@@ -21,6 +22,7 @@ class EpubReaderMenuActivity final : public Activity {
|
||||
DISPLAY_QR,
|
||||
GO_HOME,
|
||||
SYNC,
|
||||
PUSH_AND_SLEEP,
|
||||
DELETE_CACHE,
|
||||
// Mod-specific actions
|
||||
ADD_BOOKMARK,
|
||||
@@ -32,13 +34,18 @@ class EpubReaderMenuActivity final : public Activity {
|
||||
TOGGLE_ORIENTATION,
|
||||
TOGGLE_FONT_SIZE,
|
||||
CLOSE_BOOK,
|
||||
DELETE_DICT_CACHE
|
||||
DELETE_DICT_CACHE,
|
||||
ARCHIVE_BOOK,
|
||||
DELETE_BOOK,
|
||||
REINDEX_BOOK,
|
||||
LETTERBOX_FILL
|
||||
};
|
||||
|
||||
explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title,
|
||||
const int currentPage, const int totalPages, const int bookProgressPercent,
|
||||
const uint8_t currentOrientation, const bool hasFootnotes,
|
||||
bool isBookmarked = false, uint8_t currentFontSize = 0);
|
||||
bool isBookmarked = false, uint8_t currentFontSize = 0,
|
||||
const std::string& bookCachePath = "");
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
@@ -70,4 +77,26 @@ class EpubReaderMenuActivity final : public Activity {
|
||||
int currentPage = 0;
|
||||
int totalPages = 0;
|
||||
int bookProgressPercent = 0;
|
||||
|
||||
std::string bookCachePath;
|
||||
uint8_t pendingLetterboxFill = BookSettings::USE_GLOBAL;
|
||||
static constexpr int LETTERBOX_FILL_OPTION_COUNT = 4;
|
||||
const std::vector<StrId> letterboxFillLabels = {StrId::STR_DEFAULT_OPTION, StrId::STR_DITHERED, StrId::STR_SOLID,
|
||||
StrId::STR_NONE_OPT};
|
||||
|
||||
int letterboxFillToIndex() const {
|
||||
if (pendingLetterboxFill == BookSettings::USE_GLOBAL) return 0;
|
||||
return pendingLetterboxFill + 1;
|
||||
}
|
||||
|
||||
static uint8_t indexToLetterboxFill(int index) {
|
||||
if (index == 0) return BookSettings::USE_GLOBAL;
|
||||
return static_cast<uint8_t>(index - 1);
|
||||
}
|
||||
|
||||
void saveLetterboxFill() const {
|
||||
auto settings = BookSettings::load(bookCachePath);
|
||||
settings.letterboxFillOverride = pendingLetterboxFill;
|
||||
BookSettings::save(bookCachePath, settings);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -50,6 +50,12 @@ void wifiOff() {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void KOReaderSyncActivity::deferFinish(bool success) {
|
||||
RenderLock lock(*this);
|
||||
pendingFinishSuccess = success;
|
||||
pendingFinish = true;
|
||||
}
|
||||
|
||||
void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) {
|
||||
if (!success) {
|
||||
LOG_DBG("KOSync", "WiFi connection failed, exiting");
|
||||
@@ -89,6 +95,10 @@ void KOReaderSyncActivity::performSync() {
|
||||
documentHash = KOReaderDocumentId::calculate(epubPath);
|
||||
}
|
||||
if (documentHash.empty()) {
|
||||
if (syncMode == SyncMode::PUSH_ONLY) {
|
||||
deferFinish(false);
|
||||
return;
|
||||
}
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
state = SYNC_FAILED;
|
||||
@@ -106,11 +116,16 @@ void KOReaderSyncActivity::performSync() {
|
||||
}
|
||||
requestUpdateAndWait();
|
||||
|
||||
if (syncMode == SyncMode::PUSH_ONLY) {
|
||||
// Skip fetching remote progress entirely -- just upload local position
|
||||
performUpload();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch remote progress
|
||||
const auto result = KOReaderSyncClient::getProgress(documentHash, remoteProgress);
|
||||
|
||||
if (result == KOReaderSyncClient::NOT_FOUND) {
|
||||
// No remote progress - offer to upload
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
state = NO_REMOTE_PROGRESS;
|
||||
@@ -174,6 +189,11 @@ void KOReaderSyncActivity::performUpload() {
|
||||
|
||||
if (result != KOReaderSyncClient::OK) {
|
||||
wifiOff();
|
||||
if (syncMode == SyncMode::PUSH_ONLY) {
|
||||
LOG_DBG("KOSync", "PUSH_ONLY upload failed: %s", KOReaderSyncClient::errorString(result));
|
||||
deferFinish(false);
|
||||
return;
|
||||
}
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
state = SYNC_FAILED;
|
||||
@@ -184,6 +204,11 @@ void KOReaderSyncActivity::performUpload() {
|
||||
}
|
||||
|
||||
wifiOff();
|
||||
if (syncMode == SyncMode::PUSH_ONLY) {
|
||||
LOG_DBG("KOSync", "PUSH_ONLY upload succeeded");
|
||||
deferFinish(true);
|
||||
return;
|
||||
}
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
state = UPLOAD_COMPLETE;
|
||||
@@ -196,6 +221,11 @@ void KOReaderSyncActivity::onEnter() {
|
||||
|
||||
// Check for credentials first
|
||||
if (!KOREADER_STORE.hasCredentials()) {
|
||||
if (syncMode == SyncMode::PUSH_ONLY) {
|
||||
LOG_DBG("KOSync", "PUSH_ONLY: no credentials, finishing silently");
|
||||
deferFinish(false);
|
||||
return;
|
||||
}
|
||||
state = NO_CREDENTIALS;
|
||||
requestUpdate();
|
||||
return;
|
||||
@@ -335,6 +365,15 @@ void KOReaderSyncActivity::render(RenderLock&&) {
|
||||
}
|
||||
|
||||
void KOReaderSyncActivity::loop() {
|
||||
if (pendingFinish) {
|
||||
pendingFinish = false;
|
||||
ActivityResult result;
|
||||
result.isCancelled = !pendingFinishSuccess;
|
||||
setResult(std::move(result));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == NO_CREDENTIALS || state == SYNC_FAILED || state == UPLOAD_COMPLETE) {
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||
ActivityResult result;
|
||||
|
||||
@@ -11,18 +11,27 @@
|
||||
/**
|
||||
* Activity for syncing reading progress with KOReader sync server.
|
||||
*
|
||||
* Flow:
|
||||
* Flow (INTERACTIVE):
|
||||
* 1. Connect to WiFi (if not connected)
|
||||
* 2. Calculate document hash
|
||||
* 3. Fetch remote progress
|
||||
* 4. Show comparison and options (Apply/Upload)
|
||||
* 5. Apply or upload progress
|
||||
*
|
||||
* Flow (PUSH_ONLY):
|
||||
* 1. Connect to WiFi (if not connected)
|
||||
* 2. Calculate document hash
|
||||
* 3. Upload local progress (no UI interaction)
|
||||
* 4. Finish silently
|
||||
*/
|
||||
class KOReaderSyncActivity final : public Activity {
|
||||
public:
|
||||
enum class SyncMode { INTERACTIVE, PUSH_ONLY };
|
||||
|
||||
explicit KOReaderSyncActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::shared_ptr<Epub>& epub, const std::string& epubPath, int currentSpineIndex,
|
||||
int currentPage, int totalPagesInSpine)
|
||||
int currentPage, int totalPagesInSpine,
|
||||
SyncMode syncMode = SyncMode::INTERACTIVE)
|
||||
: Activity("KOReaderSync", renderer, mappedInput),
|
||||
epub(epub),
|
||||
epubPath(epubPath),
|
||||
@@ -31,7 +40,8 @@ class KOReaderSyncActivity final : public Activity {
|
||||
totalPagesInSpine(totalPagesInSpine),
|
||||
remoteProgress{},
|
||||
remotePosition{},
|
||||
localProgress{} {}
|
||||
localProgress{},
|
||||
syncMode(syncMode) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
@@ -73,6 +83,14 @@ class KOReaderSyncActivity final : public Activity {
|
||||
// Selection in result screen (0=Apply, 1=Upload)
|
||||
int selectedOption = 0;
|
||||
|
||||
SyncMode syncMode;
|
||||
|
||||
// Deferred finish for PUSH_ONLY (async upload completes on a blocking call,
|
||||
// so the actual finish() must happen in loop() where it's safe).
|
||||
bool pendingFinish = false;
|
||||
bool pendingFinishSuccess = false;
|
||||
|
||||
void deferFinish(bool success);
|
||||
void onWifiSelectionComplete(bool success);
|
||||
void performSync();
|
||||
void performUpload();
|
||||
|
||||
@@ -381,6 +381,12 @@ void loop() {
|
||||
screenshotButtonsReleased = true;
|
||||
}
|
||||
|
||||
if (activityManager.isSleepRequested()) {
|
||||
LOG_DBG("SLP", "Activity requested sleep (push-and-sleep)");
|
||||
enterDeepSleep();
|
||||
return;
|
||||
}
|
||||
|
||||
const unsigned long sleepTimeoutMs = SETTINGS.getSleepTimeoutMs();
|
||||
if (millis() - lastActivityTime >= sleepTimeoutMs) {
|
||||
LOG_DBG("SLP", "Auto-sleep triggered after %lu ms of inactivity", sleepTimeoutMs);
|
||||
|
||||
Reference in New Issue
Block a user