diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index df91c1a1..bb24371d 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -290,3 +290,4 @@ STR_LINK: "[link]" STR_SCREENSHOT_BUTTON: "Take screenshot" STR_AUTO_TURN_ENABLED: "Auto Turn Enabled: " STR_AUTO_TURN_PAGES_PER_MIN: "Auto Turn (Pages Per Minute)" +STR_SLEEP_COVER_OVERLAY: "Sleep Screen Info Overlay" diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 9a0e298b..9c66f56c 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -143,6 +143,8 @@ class CrossPointSettings { uint8_t sleepScreenCoverMode = FIT; // Sleep screen cover filter uint8_t sleepScreenCoverFilter = NO_FILTER; + // Apply information overlay with reading progress on sleep cover + uint8_t sleepCoverOverlay = 1; // Status bar settings (statusBar retained for migration only) uint8_t statusBar = FULL; uint8_t statusBarChapterPageCount = 1; diff --git a/src/SettingsList.h b/src/SettingsList.h index cdbee372..910cb095 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -23,6 +23,8 @@ inline const std::vector& getSettingsList() { SettingInfo::Enum(StrId::STR_SLEEP_COVER_FILTER, &CrossPointSettings::sleepScreenCoverFilter, {StrId::STR_NONE_OPT, StrId::STR_FILTER_CONTRAST, StrId::STR_INVERTED}, "sleepScreenCoverFilter", StrId::STR_CAT_DISPLAY), + SettingInfo::Toggle(StrId::STR_SLEEP_COVER_OVERLAY, &CrossPointSettings::sleepCoverOverlay, "sleepCoverOverlay", + StrId::STR_CAT_DISPLAY), SettingInfo::Enum(StrId::STR_HIDE_BATTERY, &CrossPointSettings::hideBatteryPercentage, {StrId::STR_NEVER, StrId::STR_IN_READER, StrId::STR_ALWAYS}, "hideBatteryPercentage", StrId::STR_CAT_DISPLAY), diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index d6a566df..22c68d7e 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -137,6 +137,133 @@ void SleepActivity::renderDefaultSleepScreen() const { renderer.displayBuffer(HalDisplay::HALF_REFRESH); } +std::string SleepActivity::getBookOverlayText(const std::string& bookPath) const { + std::string title; + std::string author; + std::string progressLine = "Reading..."; // Default progress text + + // Extract metadata based on file type + if (StringUtils::checkFileExtension(bookPath, ".xtc") || StringUtils::checkFileExtension(bookPath, ".xtch")) { + // Handle XTC file + Xtc xtc(bookPath, "/.crosspoint"); + if (xtc.load()) { + title = xtc.getTitle(); + author = xtc.getAuthor(); + + // Load progress - XTC stores current page + FsFile f; + if (Storage.openFileForRead("SLP", xtc.getCachePath() + "/progress.bin", f)) { + uint8_t data[4]; + if (f.read(data, 4) == 4) { + uint32_t currentPage = data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24); + uint32_t totalPages = xtc.getPageCount(); + float progress = xtc.calculateProgress(currentPage) * 100.0f; + char progressStr[32]; + snprintf(progressStr, sizeof(progressStr), "%lu/%u %.0f%%", (unsigned long)currentPage + 1, totalPages, + progress); + progressLine = progressStr; + } + f.close(); + } + } + } else if (StringUtils::checkFileExtension(bookPath, ".txt")) { + // Handle TXT file + Txt txt(bookPath, "/.crosspoint"); + if (txt.load()) { + title = txt.getTitle(); + // TXT files don't have author metadata + + // Load progress - TXT stores current page + FsFile f; + if (Storage.openFileForRead("SLP", txt.getCachePath() + "/progress.bin", f)) { + uint8_t data[4]; + if (f.read(data, 4) == 4) { + uint32_t currentPage = data[0] + (data[1] << 8); + + // Load total pages from page index cache + uint32_t totalPages = 0; + FsFile indexFile; + if (Storage.openFileForRead("SLP", txt.getCachePath() + "/index.bin", indexFile)) { + // Read numPages directly (it's at a fixed offset after headers) + // Skip: magic(4) + version(4) + fileSize(4) + cachedWidth(4) + cachedLines(4) + fontId(4) + margin(4) + + // alignment(4) = 32 bytes + indexFile.seek(32); + if (indexFile.read(reinterpret_cast(&totalPages), sizeof(totalPages)) == sizeof(totalPages)) { + // Successfully read totalPages + } + indexFile.close(); + } + + if (totalPages > 0) { + float progress = (currentPage + 1) * 100.0f / totalPages; + char progressStr[32]; + snprintf(progressStr, sizeof(progressStr), "%lu/%u %.0f%%", (unsigned long)currentPage + 1, totalPages, + progress); + progressLine = progressStr; + } else { + char progressStr[16]; + snprintf(progressStr, sizeof(progressStr), "Page %lu", (unsigned long)currentPage + 1); + progressLine = progressStr; + } + } + f.close(); + } + } + } else if (StringUtils::checkFileExtension(bookPath, ".epub")) { + // Handle EPUB file + Epub epub(bookPath, "/.crosspoint"); + if (epub.load(true, true)) { // Skip CSS loading for metadata only + title = epub.getTitle(); + author = epub.getAuthor(); + + // Load progress and get chapter information + FsFile f; + if (Storage.openFileForRead("SLP", epub.getCachePath() + "/progress.bin", f)) { + uint8_t data[6]; + if (f.read(data, 6) == 6) { + int currentSpineIndex = data[0] + (data[1] << 8); + int currentPage = data[2] + (data[3] << 8); + int pageCount = data[4] + (data[5] << 8); + if (pageCount > 0) { + float chapterProgress = static_cast(currentPage) / static_cast(pageCount); + float bookProgress = epub.calculateProgress(currentSpineIndex, chapterProgress) * 100.0f; + + // Get chapter name from TOC + const int tocIndex = epub.getTocIndexForSpineIndex(currentSpineIndex); + std::string chapterName; + if (tocIndex == -1) { + chapterName = "Unnamed Chapter"; + } else { + const auto tocItem = epub.getTocItem(tocIndex); + chapterName = tocItem.title; + } + + // Create chapter info line with chapter name and progress + char chapterProgressStr[128]; + snprintf(chapterProgressStr, sizeof(chapterProgressStr), "%s: %d/%d %.0f%%", chapterName.c_str(), + currentPage + 1, pageCount, bookProgress); + progressLine = chapterProgressStr; + } + } + f.close(); + } + } + } + + // Format overlay text (two lines) + std::string overlayText; + if (!title.empty()) { + overlayText = title; + if (!author.empty()) { + overlayText += " - " + author; + } + overlayText += "\n"; // New line + overlayText += progressLine; + } + + return overlayText; +} + void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { int x, y; const auto pageWidth = renderer.getScreenWidth(); @@ -189,6 +316,38 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { renderer.invertScreen(); } + // Draw overlay text if provided + const std::string overlayText = SETTINGS.sleepCoverOverlay ? getBookOverlayText(APP_STATE.openEpubPath) : ""; + if (!overlayText.empty()) { + // Split text into lines + size_t newlinePos = overlayText.find('\n'); + std::string line1 = (newlinePos != std::string::npos) ? overlayText.substr(0, newlinePos) : overlayText; + std::string line2 = (newlinePos != std::string::npos && newlinePos + 1 < overlayText.length()) + ? overlayText.substr(newlinePos + 1) + : ""; + + // Truncate lines if too long for the screen + line1 = renderer.truncatedText(UI_10_FONT_ID, line1.c_str(), pageWidth - 20); + line2 = renderer.truncatedText(UI_10_FONT_ID, line2.c_str(), pageWidth - 20); + + // Draw semi-transparent background rectangle at the bottom (taller for two lines) + const int overlayHeight = 50; + const int overlayY = pageHeight - overlayHeight - 20; // Position 20 pixels higher from bottom + renderer.fillRect(0, overlayY, pageWidth, overlayHeight, false); // White background + + // First line - positioned higher in the overlay + const int line1Y = overlayY + 18; // Position higher up + const int textX1 = (pageWidth - renderer.getTextWidth(UI_10_FONT_ID, line1.c_str())) / 2; + renderer.drawText(UI_10_FONT_ID, textX1, line1Y, line1.c_str(), true); // Black text + + // Second line - positioned lower in the overlay + if (!line2.empty()) { + const int line2Y = overlayY + 38; // Position lower down with more space + const int textX2 = (pageWidth - renderer.getTextWidth(UI_10_FONT_ID, line2.c_str())) / 2; + renderer.drawText(UI_10_FONT_ID, textX2, line2Y, line2.c_str(), true); // Black text + } + } + renderer.displayBuffer(HalDisplay::HALF_REFRESH); if (hasGreyscale) { diff --git a/src/activities/boot_sleep/SleepActivity.h b/src/activities/boot_sleep/SleepActivity.h index 87df8ba1..904740fb 100644 --- a/src/activities/boot_sleep/SleepActivity.h +++ b/src/activities/boot_sleep/SleepActivity.h @@ -15,4 +15,5 @@ class SleepActivity final : public Activity { void renderCoverSleepScreen() const; void renderBitmapSleepScreen(const Bitmap& bitmap) const; void renderBlankSleepScreen() const; + std::string getBookOverlayText(const std::string& bookPath) const; };