Merge remote-tracking branch 'origin/master' into feat-autosync
This commit is contained in:
@@ -155,6 +155,15 @@ void EpubReaderActivity::onEnter() {
|
||||
}
|
||||
// We may want a better condition to detect if we are opening for the first time.
|
||||
// This will trigger if the book is re-opened at Chapter 0.
|
||||
if (currentSpineIndex < 0 || currentSpineIndex >= epub->getSpineItemsCount()) {
|
||||
LOG_ERR("ERS", "Invalid saved spine index %d (valid 0..%d), resetting to start", currentSpineIndex,
|
||||
epub->getSpineItemsCount() > 0 ? epub->getSpineItemsCount() - 1 : 0);
|
||||
currentSpineIndex = 0;
|
||||
nextPageNumber = 0;
|
||||
cachedSpineIndex = 0;
|
||||
cachedChapterTotalPageCount = 0;
|
||||
}
|
||||
|
||||
if (currentSpineIndex == 0) {
|
||||
int textSpineIndex = epub->getSpineIndexForTextReference();
|
||||
if (textSpineIndex != 0) {
|
||||
@@ -219,12 +228,14 @@ void EpubReaderActivity::loop() {
|
||||
}
|
||||
|
||||
if (inputDrainGuard.shouldDrain(mappedInput)) {
|
||||
buttonEvents.drain();
|
||||
return;
|
||||
}
|
||||
|
||||
if (automaticPageTurnActive) {
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) ||
|
||||
mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||
buttonEvents.drain();
|
||||
automaticPageTurnActive = false;
|
||||
// updates chapter title space to indicate page turn disabled
|
||||
requestUpdate();
|
||||
@@ -248,71 +259,68 @@ void EpubReaderActivity::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
// Long press CONFIRM (1s+) goes directly to KOReader sync when credentials are configured.
|
||||
// We intentionally keep long-press on the richer compare flow so advanced
|
||||
// conflict-resolution behavior stays available even after simplifying menu UX.
|
||||
// Without credentials, fall through to the regular menu on release.
|
||||
if (mappedInput.isPressed(MappedInputManager::Button::Confirm) &&
|
||||
mappedInput.getHeldTime() >= ReaderUtils::GO_HOME_MS && KOREADER_STORE.hasCredentials()) {
|
||||
launchKOReaderSync(SyncLaunchMode::COMPARE);
|
||||
return;
|
||||
}
|
||||
bool delayedPrevTurn = false;
|
||||
bool delayedNextTurn = false;
|
||||
using BA = CrossPointSettings::BUTTON_ACTION;
|
||||
|
||||
// Short press CONFIRM enters reader menu activity.
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) &&
|
||||
mappedInput.getHeldTime() < ReaderUtils::GO_HOME_MS) {
|
||||
const int currentPage = section ? section->currentPage + 1 : 0;
|
||||
const int totalPages = section ? section->pageCount : 0;
|
||||
float bookProgress = 0.0f;
|
||||
if (epub->getBookSize() > 0 && section && section->pageCount > 0) {
|
||||
const float chapterProgress = static_cast<float>(section->currentPage) / static_cast<float>(section->pageCount);
|
||||
bookProgress = epub->calculateProgress(currentSpineIndex, chapterProgress) * 100.0f;
|
||||
ButtonEventManager::ButtonEvent ev;
|
||||
while (buttonEvents.consumeEvent(ev)) {
|
||||
if (ev.button == MappedInputManager::Button::Confirm) {
|
||||
if (ev.type == ButtonEventManager::PressType::Long && KOREADER_STORE.hasCredentials()) {
|
||||
launchKOReaderSync(SyncLaunchMode::COMPARE);
|
||||
return;
|
||||
}
|
||||
if (ev.type == ButtonEventManager::PressType::Short) {
|
||||
openReaderMenu();
|
||||
return;
|
||||
}
|
||||
}
|
||||
const int bookProgressPercent = clampPercent(static_cast<int>(bookProgress + 0.5f));
|
||||
const bool isCurrentPageStarred = section && bookmarkStore.has(static_cast<uint16_t>(currentSpineIndex),
|
||||
static_cast<uint16_t>(section->currentPage));
|
||||
ReaderUtils::enforceExitFullRefresh(renderer);
|
||||
startActivityForResult(std::make_unique<EpubReaderMenuActivity>(
|
||||
renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent,
|
||||
SETTINGS.orientation, !currentPageFootnotes.empty(), bookEmbeddedStyleOverride,
|
||||
bookImageRenderingOverride, bookFontFamilyOverride, bookFontSizeOverride,
|
||||
SETTINGS.textDarkness, !bookmarkStore.isEmpty(), isCurrentPageStarred),
|
||||
[this](const ActivityResult& result) {
|
||||
// Always apply orientation/darkness change even if the menu was cancelled
|
||||
const auto& menu = std::get<MenuResult>(result.data);
|
||||
applyOrientation(menu.orientation);
|
||||
applyTextDarkness(menu.textDarkness);
|
||||
toggleAutoPageTurn(menu.pageTurnOption);
|
||||
applyBookReaderOverrides(menu.embeddedStyleOverride, menu.imageRenderingOverride,
|
||||
menu.fontFamilyOverride, menu.fontSizeOverride);
|
||||
if (!result.isCancelled) {
|
||||
onReaderMenuConfirm(static_cast<EpubReaderMenuActivity::MenuAction>(menu.action));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Long press BACK (1s+) goes to home screen
|
||||
if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= ReaderUtils::GO_HOME_MS) {
|
||||
ReaderUtils::enforceExitFullRefresh(renderer);
|
||||
if (tryAutoPushOnClose()) return;
|
||||
onGoHome();
|
||||
return;
|
||||
}
|
||||
if (ev.button == MappedInputManager::Button::Back) {
|
||||
if (ev.type == ButtonEventManager::PressType::Long) {
|
||||
ReaderUtils::enforceExitFullRefresh(renderer);
|
||||
if (tryAutoPushOnClose()) return;
|
||||
onGoHome();
|
||||
return;
|
||||
}
|
||||
if (ev.type == ButtonEventManager::PressType::Short) {
|
||||
if (footnoteDepth > 0) {
|
||||
restoreSavedPosition();
|
||||
return;
|
||||
}
|
||||
ReaderUtils::enforceExitFullRefresh(renderer);
|
||||
if (tryAutoPushOnClose()) return;
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Short press BACK returns to the calling activity (or restores position if viewing footnote)
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Back) &&
|
||||
mappedInput.getHeldTime() < ReaderUtils::GO_HOME_MS) {
|
||||
if (footnoteDepth > 0) {
|
||||
restoreSavedPosition();
|
||||
return;
|
||||
if (ev.type == ButtonEventManager::PressType::Short) {
|
||||
if ((ev.button == MappedInputManager::Button::PageBack && SETTINGS.btnShortPageBack == BA::BTN_DEFAULT &&
|
||||
ButtonEventManager::hasDoubleAction(MappedInputManager::Button::PageBack)) ||
|
||||
(ev.button == MappedInputManager::Button::Left && SETTINGS.btnShortLeft == BA::BTN_DEFAULT &&
|
||||
ButtonEventManager::hasDoubleAction(MappedInputManager::Button::Left))) {
|
||||
delayedPrevTurn = true;
|
||||
continue;
|
||||
}
|
||||
if ((ev.button == MappedInputManager::Button::PageForward && SETTINGS.btnShortPageForward == BA::BTN_DEFAULT &&
|
||||
ButtonEventManager::hasDoubleAction(MappedInputManager::Button::PageForward)) ||
|
||||
(ev.button == MappedInputManager::Button::Right && SETTINGS.btnShortRight == BA::BTN_DEFAULT &&
|
||||
ButtonEventManager::hasDoubleAction(MappedInputManager::Button::Right))) {
|
||||
delayedNextTurn = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ReaderUtils::enforceExitFullRefresh(renderer);
|
||||
if (tryAutoPushOnClose()) return;
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
auto [prevTriggered, nextTriggered] = ReaderUtils::detectPageTurn(mappedInput);
|
||||
if (!prevTriggered && !nextTriggered) {
|
||||
if (!delayedPrevTurn && !delayedNextTurn) {
|
||||
return;
|
||||
}
|
||||
prevTriggered = delayedPrevTurn;
|
||||
nextTriggered = delayedNextTurn;
|
||||
}
|
||||
if (!prevTriggered && !nextTriggered) {
|
||||
return;
|
||||
}
|
||||
@@ -330,65 +338,6 @@ void EpubReaderActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool skipChapter = mappedInput.getHeldTime() > skipChapterMs;
|
||||
|
||||
// Chapter skip navigates by TOC entries, not spine boundaries.
|
||||
// Spine items without their own TOC entry inherit the previous spine's tocIndex
|
||||
// (see BookMetadataCache), so they're treated as continuations of the last chapter.
|
||||
// At the boundaries: skipping forward past the last TOC entry jumps to end-of-book
|
||||
// (clamped in render()); skipping backward before the first TOC entry jumps to the
|
||||
// spine before the current chapter's first spine (clamped to 0 in render()).
|
||||
if (skipChapter) {
|
||||
lastPageTurnTime = millis();
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
|
||||
if (section && section->pageCount > 0) {
|
||||
const int curTocIndex = section->getTocIndexForPage(section->currentPage);
|
||||
const int nextTocIndex = nextTriggered ? curTocIndex + 1 : curTocIndex - 1;
|
||||
|
||||
if (curTocIndex < 0) {
|
||||
// No TOC entry for this spine, fall back to spine-level skip
|
||||
nextPageNumber = 0;
|
||||
currentSpineIndex = nextTriggered ? currentSpineIndex + 1 : currentSpineIndex - 1;
|
||||
section.reset();
|
||||
} else if (nextTocIndex >= 0 && nextTocIndex < epub->getTocItemsCount()) {
|
||||
const int newSpineIndex = epub->getSpineIndexForTocIndex(nextTocIndex);
|
||||
|
||||
if (newSpineIndex == currentSpineIndex) {
|
||||
if (const auto resolvedPage = section->getPageForTocIndex(nextTocIndex)) {
|
||||
section->currentPage = *resolvedPage;
|
||||
} else {
|
||||
LOG_DBG("ERS", "No page boundary for TOC %d in spine %d, staying on current page", nextTocIndex,
|
||||
currentSpineIndex);
|
||||
}
|
||||
} else {
|
||||
pendingTocIndex = nextTocIndex;
|
||||
nextPageNumber = 0;
|
||||
currentSpineIndex = newSpineIndex;
|
||||
section.reset();
|
||||
}
|
||||
} else if (nextTriggered) {
|
||||
// Beyond last TOC entry, go to end of book
|
||||
nextPageNumber = 0;
|
||||
currentSpineIndex = epub->getSpineItemsCount();
|
||||
section.reset();
|
||||
} else {
|
||||
// Before first TOC entry, skip to spine before the current chapter
|
||||
nextPageNumber = 0;
|
||||
currentSpineIndex = epub->getTocItem(curTocIndex).spineIndex - 1;
|
||||
section.reset();
|
||||
}
|
||||
} else {
|
||||
nextPageNumber = 0;
|
||||
currentSpineIndex = nextTriggered ? currentSpineIndex + 1 : currentSpineIndex - 1;
|
||||
section.reset();
|
||||
}
|
||||
}
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
// No current section, attempt to rerender the book
|
||||
if (!section) {
|
||||
requestUpdate();
|
||||
@@ -946,6 +895,14 @@ void EpubReaderActivity::applyPendingSyncSession() {
|
||||
pendingParagraphLookup = sync.hasParagraphIndex;
|
||||
pendingParagraphIndex = sync.paragraphIndex;
|
||||
|
||||
if (restoreSpineIndex < 0 || restoreSpineIndex >= epub->getSpineItemsCount()) {
|
||||
LOG_ERR("ERS", "Invalid sync restore spine index %d, resetting to 0", restoreSpineIndex);
|
||||
restoreSpineIndex = 0;
|
||||
restorePage = 0;
|
||||
pendingParagraphLookup = false;
|
||||
pendingParagraphIndex = 0;
|
||||
}
|
||||
|
||||
if (sync.outcome == KOReaderSyncOutcomeState::APPLIED_REMOTE) {
|
||||
restoreSpineIndex = sync.resultSpineIndex;
|
||||
restorePage = sync.resultPage;
|
||||
@@ -991,6 +948,11 @@ void EpubReaderActivity::applyPendingBookmarkJump() {
|
||||
return;
|
||||
}
|
||||
LOG_DBG("ERS", "Applying pending bookmark jump: spine=%u page=%u", jump.spineIndex, jump.pageNumber);
|
||||
if (jump.spineIndex >= static_cast<uint16_t>(epub->getSpineItemsCount())) {
|
||||
LOG_ERR("ERS", "Invalid bookmark jump spine index %u, resetting to 0", jump.spineIndex);
|
||||
jump.spineIndex = 0;
|
||||
jump.pageNumber = 0;
|
||||
}
|
||||
// Transient write before initializeReader; saveProgress() overwrites with the real percent.
|
||||
if (writeReaderProgressCache(epub->getCachePath(), jump.spineIndex, jump.pageNumber, 0, 0)) {
|
||||
cachedSpineIndex = jump.spineIndex;
|
||||
@@ -1827,6 +1789,36 @@ bool EpubReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gf
|
||||
return true;
|
||||
}
|
||||
|
||||
void EpubReaderActivity::openReaderMenu() {
|
||||
const int currentPage = section ? section->currentPage + 1 : 0;
|
||||
const int totalPages = section ? section->pageCount : 0;
|
||||
float bookProgress = 0.0f;
|
||||
if (epub->getBookSize() > 0 && section && section->pageCount > 0) {
|
||||
const float chapterProgress = static_cast<float>(section->currentPage) / static_cast<float>(section->pageCount);
|
||||
bookProgress = epub->calculateProgress(currentSpineIndex, chapterProgress) * 100.0f;
|
||||
}
|
||||
const int bookProgressPercent = clampPercent(static_cast<int>(bookProgress + 0.5f));
|
||||
const bool isCurrentPageStarred = section && bookmarkStore.has(static_cast<uint16_t>(currentSpineIndex),
|
||||
static_cast<uint16_t>(section->currentPage));
|
||||
ReaderUtils::enforceExitFullRefresh(renderer);
|
||||
startActivityForResult(
|
||||
std::make_unique<EpubReaderMenuActivity>(
|
||||
renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent, SETTINGS.orientation,
|
||||
!currentPageFootnotes.empty(), bookEmbeddedStyleOverride, bookImageRenderingOverride, bookFontFamilyOverride,
|
||||
bookFontSizeOverride, SETTINGS.textDarkness, !bookmarkStore.isEmpty(), isCurrentPageStarred),
|
||||
[this](const ActivityResult& result) {
|
||||
const auto& menu = std::get<MenuResult>(result.data);
|
||||
applyOrientation(menu.orientation);
|
||||
applyTextDarkness(menu.textDarkness);
|
||||
toggleAutoPageTurn(menu.pageTurnOption);
|
||||
applyBookReaderOverrides(menu.embeddedStyleOverride, menu.imageRenderingOverride, menu.fontFamilyOverride,
|
||||
menu.fontSizeOverride);
|
||||
if (!result.isCancelled) {
|
||||
onReaderMenuConfirm(static_cast<EpubReaderMenuActivity::MenuAction>(menu.action));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void EpubReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION action) {
|
||||
using BA = CrossPointSettings::BUTTON_ACTION;
|
||||
switch (action) {
|
||||
@@ -1946,35 +1938,7 @@ void EpubReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION
|
||||
break;
|
||||
case BA::BTN_READER_MENU:
|
||||
if (epub) {
|
||||
const int currentPage = section ? section->currentPage + 1 : 0;
|
||||
const int totalPages = section ? section->pageCount : 0;
|
||||
float bookProgress = 0.0f;
|
||||
if (epub->getBookSize() > 0 && section && section->pageCount > 0) {
|
||||
const float chapterProgress =
|
||||
static_cast<float>(section->currentPage) / static_cast<float>(section->pageCount);
|
||||
bookProgress = epub->calculateProgress(currentSpineIndex, chapterProgress) * 100.0f;
|
||||
}
|
||||
const int bookProgressPercent = clampPercent(static_cast<int>(bookProgress + 0.5f));
|
||||
const bool isCurrentPageStarred = section && bookmarkStore.has(static_cast<uint16_t>(currentSpineIndex),
|
||||
static_cast<uint16_t>(section->currentPage));
|
||||
ReaderUtils::enforceExitFullRefresh(renderer);
|
||||
startActivityForResult(
|
||||
std::make_unique<EpubReaderMenuActivity>(
|
||||
renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent,
|
||||
SETTINGS.orientation, !currentPageFootnotes.empty(), bookEmbeddedStyleOverride,
|
||||
bookImageRenderingOverride, bookFontFamilyOverride, bookFontSizeOverride, SETTINGS.textDarkness,
|
||||
!bookmarkStore.isEmpty(), isCurrentPageStarred),
|
||||
[this](const ActivityResult& result) {
|
||||
const auto& menu = std::get<MenuResult>(result.data);
|
||||
applyOrientation(menu.orientation);
|
||||
applyTextDarkness(menu.textDarkness);
|
||||
toggleAutoPageTurn(menu.pageTurnOption);
|
||||
applyBookReaderOverrides(menu.embeddedStyleOverride, menu.imageRenderingOverride, menu.fontFamilyOverride,
|
||||
menu.fontSizeOverride);
|
||||
if (!result.isCancelled) {
|
||||
onReaderMenuConfirm(static_cast<EpubReaderMenuActivity::MenuAction>(menu.action));
|
||||
}
|
||||
});
|
||||
openReaderMenu();
|
||||
}
|
||||
break;
|
||||
case BA::BTN_KOREADER_SYNC:
|
||||
|
||||
Reference in New Issue
Block a user