diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 55b20ee..28734ee 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -186,11 +186,13 @@ void EpubReaderActivity::loop() { if (skipChapter) { // We don't want to delete the section mid-render, so grab the semaphore xSemaphoreTake(renderingMutex, portMAX_DELAY); - nextPageNumber = 0; - currentSpineIndex = nextReleased ? currentSpineIndex + 1 : currentSpineIndex - 1; - section.reset(); + // Show immediate feedback for long-press skip, then schedule delayed action (500ms) + showSkipPopup("Skipping"); + delayedSkipPending = true; + delayedSkipDir = nextReleased ? +1 : -1; + delayedSkipExecuteAtMs = millis() + 500; xSemaphoreGive(renderingMutex); - updateRequired = true; + // Do not perform the skip immediately; it will be executed in display loop after delay return; } @@ -229,11 +231,21 @@ void EpubReaderActivity::loop() { void EpubReaderActivity::displayTaskLoop() { while (true) { + const uint32_t now = millis(); if (updateRequired) { updateRequired = false; xSemaphoreTake(renderingMutex, portMAX_DELAY); renderScreen(); xSemaphoreGive(renderingMutex); + } else if (delayedSkipPending && now >= delayedSkipExecuteAtMs) { + // Execute the delayed chapter skip now + xSemaphoreTake(renderingMutex, portMAX_DELAY); + nextPageNumber = 0; + currentSpineIndex += delayedSkipDir; + section.reset(); + delayedSkipPending = false; + xSemaphoreGive(renderingMutex); + updateRequired = true; } vTaskDelay(10 / portTICK_PERIOD_MS); } @@ -385,6 +397,19 @@ void EpubReaderActivity::renderScreen() { } } +void EpubReaderActivity::showSkipPopup(const char* text) { + constexpr int boxMargin = 20; + const int textWidth = renderer.getTextWidth(UI_12_FONT_ID, text); + const int boxWidth = textWidth + boxMargin * 2; + const int boxHeight = renderer.getLineHeight(UI_12_FONT_ID) + boxMargin * 2; + const int boxX = (renderer.getScreenWidth() - boxWidth) / 2; + constexpr int boxY = 50; + renderer.fillRect(boxX, boxY, boxWidth, boxHeight, false); + renderer.drawText(UI_12_FONT_ID, boxX + boxMargin, boxY + boxMargin, text); + renderer.drawRect(boxX + 5, boxY + 5, boxWidth - 10, boxHeight - 10); + renderer.displayBuffer(EInkDisplay::FAST_REFRESH); +} + void EpubReaderActivity::renderContents(std::unique_ptr page, const int orientedMarginTop, const int orientedMarginRight, const int orientedMarginBottom, const int orientedMarginLeft) { diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index 63d4887..2f3d6db 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -16,6 +16,9 @@ class EpubReaderActivity final : public ActivityWithSubactivity { int nextPageNumber = 0; int pagesUntilFullRefresh = 0; bool updateRequired = false; + bool delayedSkipPending = false; + int delayedSkipDir = 0; + uint32_t delayedSkipExecuteAtMs = 0; const std::function onGoBack; const std::function onGoHome; @@ -25,6 +28,7 @@ class EpubReaderActivity final : public ActivityWithSubactivity { void renderContents(std::unique_ptr page, int orientedMarginTop, int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft); void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const; + void showSkipPopup(const char* text); public: explicit EpubReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr epub, diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index fe444da..21c2ef3 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -132,6 +132,16 @@ void XtcReaderActivity::loop() { const int skipAmount = skipPages ? 10 : 1; if (prevReleased) { + if (skipPages) { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + showSkipPopup("Skipping"); + delayedSkipPending = true; + delayedSkipDir = -1; + delayedSkipAmount = skipAmount; + delayedSkipExecuteAtMs = millis() + 500; + xSemaphoreGive(renderingMutex); + return; + } if (currentPage >= static_cast(skipAmount)) { currentPage -= skipAmount; } else { @@ -139,6 +149,16 @@ void XtcReaderActivity::loop() { } updateRequired = true; } else if (nextReleased) { + if (skipPages) { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + showSkipPopup("Skipping"); + delayedSkipPending = true; + delayedSkipDir = +1; + delayedSkipAmount = skipAmount; + delayedSkipExecuteAtMs = millis() + 500; + xSemaphoreGive(renderingMutex); + return; + } currentPage += skipAmount; if (currentPage >= xtc->getPageCount()) { currentPage = xtc->getPageCount(); // Allow showing "End of book" @@ -149,11 +169,29 @@ void XtcReaderActivity::loop() { void XtcReaderActivity::displayTaskLoop() { while (true) { + const uint32_t now = millis(); if (updateRequired) { updateRequired = false; xSemaphoreTake(renderingMutex, portMAX_DELAY); renderScreen(); xSemaphoreGive(renderingMutex); + } else if (delayedSkipPending && now >= delayedSkipExecuteAtMs) { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + if (delayedSkipDir < 0) { + if (currentPage >= delayedSkipAmount) { + currentPage -= delayedSkipAmount; + } else { + currentPage = 0; + } + } else { + currentPage += delayedSkipAmount; + if (currentPage >= xtc->getPageCount()) { + currentPage = xtc->getPageCount(); + } + } + delayedSkipPending = false; + xSemaphoreGive(renderingMutex); + updateRequired = true; } vTaskDelay(10 / portTICK_PERIOD_MS); } @@ -177,6 +215,19 @@ void XtcReaderActivity::renderScreen() { saveProgress(); } +void XtcReaderActivity::showSkipPopup(const char* text) { + constexpr int boxMargin = 20; + const int textWidth = renderer.getTextWidth(UI_12_FONT_ID, text); + const int boxWidth = textWidth + boxMargin * 2; + const int boxHeight = renderer.getLineHeight(UI_12_FONT_ID) + boxMargin * 2; + const int boxX = (renderer.getScreenWidth() - boxWidth) / 2; + constexpr int boxY = 50; + renderer.fillRect(boxX, boxY, boxWidth, boxHeight, false); + renderer.drawText(UI_12_FONT_ID, boxX + boxMargin, boxY + boxMargin, text); + renderer.drawRect(boxX + 5, boxY + 5, boxWidth - 10, boxHeight - 10); + renderer.displayBuffer(EInkDisplay::FAST_REFRESH); +} + void XtcReaderActivity::renderPage() { const uint16_t pageWidth = xtc->getPageWidth(); const uint16_t pageHeight = xtc->getPageHeight(); @@ -359,6 +410,8 @@ void XtcReaderActivity::renderPage() { bitDepth); } +// scheduleSkipMessage removed: delayed skip now handled via delayedSkip* fields + void XtcReaderActivity::saveProgress() const { FsFile f; if (SdMan.openFileForWrite("XTR", xtc->getCachePath() + "/progress.bin", f)) { diff --git a/src/activities/reader/XtcReaderActivity.h b/src/activities/reader/XtcReaderActivity.h index 579e177..6db673f 100644 --- a/src/activities/reader/XtcReaderActivity.h +++ b/src/activities/reader/XtcReaderActivity.h @@ -21,6 +21,10 @@ class XtcReaderActivity final : public ActivityWithSubactivity { uint32_t currentPage = 0; int pagesUntilFullRefresh = 0; bool updateRequired = false; + bool delayedSkipPending = false; + int delayedSkipDir = 0; + uint32_t delayedSkipExecuteAtMs = 0; + uint32_t delayedSkipAmount = 0; const std::function onGoBack; const std::function onGoHome; @@ -30,6 +34,7 @@ class XtcReaderActivity final : public ActivityWithSubactivity { void renderPage(); void saveProgress() const; void loadProgress(); + void showSkipPopup(const char* text); public: explicit XtcReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr xtc,