|
|
|
|
@@ -18,6 +18,12 @@
|
|
|
|
|
#include "fontIds.h"
|
|
|
|
|
#include "util/StringUtils.h"
|
|
|
|
|
|
|
|
|
|
// Static member definitions for persistent cover buffer
|
|
|
|
|
bool HomeActivity::coverRendered = false;
|
|
|
|
|
bool HomeActivity::coverBufferStored = false;
|
|
|
|
|
uint8_t* HomeActivity::coverBuffer = nullptr;
|
|
|
|
|
std::string HomeActivity::cachedCoverPath;
|
|
|
|
|
|
|
|
|
|
void HomeActivity::taskTrampoline(void* param) {
|
|
|
|
|
auto* self = static_cast<HomeActivity*>(param);
|
|
|
|
|
self->displayTaskLoop();
|
|
|
|
|
@@ -41,50 +47,94 @@ void HomeActivity::onEnter() {
|
|
|
|
|
// Check if OPDS browser URL is configured
|
|
|
|
|
hasOpdsUrl = strlen(SETTINGS.opdsServerUrl) > 0;
|
|
|
|
|
|
|
|
|
|
if (hasContinueReading) {
|
|
|
|
|
// Extract filename from path for display
|
|
|
|
|
lastBookTitle = APP_STATE.openEpubPath;
|
|
|
|
|
const size_t lastSlash = lastBookTitle.find_last_of('/');
|
|
|
|
|
if (!hasContinueReading) {
|
|
|
|
|
// No book to continue reading - clear any stale cover cache
|
|
|
|
|
if (coverBufferStored) {
|
|
|
|
|
freeCoverBuffer();
|
|
|
|
|
coverRendered = false;
|
|
|
|
|
cachedCoverPath.clear();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Extract filename from path for display (fallback if no cached title)
|
|
|
|
|
std::string filenameFromPath = APP_STATE.openEpubPath;
|
|
|
|
|
const size_t lastSlash = filenameFromPath.find_last_of('/');
|
|
|
|
|
if (lastSlash != std::string::npos) {
|
|
|
|
|
lastBookTitle = lastBookTitle.substr(lastSlash + 1);
|
|
|
|
|
filenameFromPath = filenameFromPath.substr(lastSlash + 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If epub, try to load the metadata for title/author and cover
|
|
|
|
|
if (StringUtils::checkFileExtension(lastBookTitle, ".epub")) {
|
|
|
|
|
// Check if we have cached title/author from CrossPointState
|
|
|
|
|
const bool hasCachedMetadata = !APP_STATE.openBookTitle.empty();
|
|
|
|
|
|
|
|
|
|
if (hasCachedMetadata) {
|
|
|
|
|
// Use cached title and author - no need to load the book file
|
|
|
|
|
lastBookTitle = APP_STATE.openBookTitle;
|
|
|
|
|
lastBookAuthor = APP_STATE.openBookAuthor;
|
|
|
|
|
} else {
|
|
|
|
|
// No cached metadata - use filename as fallback
|
|
|
|
|
lastBookTitle = filenameFromPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If epub, try to get cover image (and populate cache if needed)
|
|
|
|
|
if (StringUtils::checkFileExtension(filenameFromPath, ".epub")) {
|
|
|
|
|
Epub epub(APP_STATE.openEpubPath, "/.crosspoint");
|
|
|
|
|
epub.load(false);
|
|
|
|
|
if (!epub.getTitle().empty()) {
|
|
|
|
|
lastBookTitle = std::string(epub.getTitle());
|
|
|
|
|
}
|
|
|
|
|
if (!epub.getAuthor().empty()) {
|
|
|
|
|
lastBookAuthor = std::string(epub.getAuthor());
|
|
|
|
|
// Only load epub metadata if we don't have cached title/author
|
|
|
|
|
if (!hasCachedMetadata) {
|
|
|
|
|
epub.load(false);
|
|
|
|
|
if (!epub.getTitle().empty()) {
|
|
|
|
|
lastBookTitle = std::string(epub.getTitle());
|
|
|
|
|
APP_STATE.openBookTitle = lastBookTitle;
|
|
|
|
|
}
|
|
|
|
|
if (!epub.getAuthor().empty()) {
|
|
|
|
|
lastBookAuthor = std::string(epub.getAuthor());
|
|
|
|
|
APP_STATE.openBookAuthor = lastBookAuthor;
|
|
|
|
|
}
|
|
|
|
|
// Save updated cache to file
|
|
|
|
|
APP_STATE.saveToFile();
|
|
|
|
|
}
|
|
|
|
|
// Try to generate thumbnail image for Continue Reading card
|
|
|
|
|
if (epub.generateThumbBmp()) {
|
|
|
|
|
coverBmpPath = epub.getThumbBmpPath();
|
|
|
|
|
hasCoverImage = true;
|
|
|
|
|
}
|
|
|
|
|
} else if (StringUtils::checkFileExtension(lastBookTitle, ".xtch") ||
|
|
|
|
|
StringUtils::checkFileExtension(lastBookTitle, ".xtc")) {
|
|
|
|
|
} else if (StringUtils::checkFileExtension(filenameFromPath, ".xtch") ||
|
|
|
|
|
StringUtils::checkFileExtension(filenameFromPath, ".xtc")) {
|
|
|
|
|
// Handle XTC file
|
|
|
|
|
Xtc xtc(APP_STATE.openEpubPath, "/.crosspoint");
|
|
|
|
|
if (xtc.load()) {
|
|
|
|
|
if (!xtc.getTitle().empty()) {
|
|
|
|
|
lastBookTitle = std::string(xtc.getTitle());
|
|
|
|
|
if (!hasCachedMetadata) {
|
|
|
|
|
if (xtc.load()) {
|
|
|
|
|
if (!xtc.getTitle().empty()) {
|
|
|
|
|
lastBookTitle = std::string(xtc.getTitle());
|
|
|
|
|
APP_STATE.openBookTitle = lastBookTitle;
|
|
|
|
|
APP_STATE.saveToFile();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Try to generate thumbnail image for Continue Reading card
|
|
|
|
|
if (xtc.generateThumbBmp()) {
|
|
|
|
|
coverBmpPath = xtc.getThumbBmpPath();
|
|
|
|
|
hasCoverImage = true;
|
|
|
|
|
// Remove extension from title if we don't have metadata
|
|
|
|
|
if (StringUtils::checkFileExtension(lastBookTitle, ".xtch")) {
|
|
|
|
|
lastBookTitle.resize(lastBookTitle.length() - 5);
|
|
|
|
|
APP_STATE.openBookTitle = lastBookTitle;
|
|
|
|
|
APP_STATE.saveToFile();
|
|
|
|
|
} else if (StringUtils::checkFileExtension(lastBookTitle, ".xtc")) {
|
|
|
|
|
lastBookTitle.resize(lastBookTitle.length() - 4);
|
|
|
|
|
APP_STATE.openBookTitle = lastBookTitle;
|
|
|
|
|
APP_STATE.saveToFile();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Remove extension from title if we don't have metadata
|
|
|
|
|
if (StringUtils::checkFileExtension(lastBookTitle, ".xtch")) {
|
|
|
|
|
lastBookTitle.resize(lastBookTitle.length() - 5);
|
|
|
|
|
} else if (StringUtils::checkFileExtension(lastBookTitle, ".xtc")) {
|
|
|
|
|
lastBookTitle.resize(lastBookTitle.length() - 4);
|
|
|
|
|
// Try to generate thumbnail image for Continue Reading card
|
|
|
|
|
if (xtc.generateThumbBmp()) {
|
|
|
|
|
coverBmpPath = xtc.getThumbBmpPath();
|
|
|
|
|
hasCoverImage = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if cached cover buffer is still valid (same book)
|
|
|
|
|
if (hasCoverImage && coverBufferStored && cachedCoverPath == coverBmpPath) {
|
|
|
|
|
// Cover buffer is still valid, mark as rendered to skip reload
|
|
|
|
|
coverRendered = true;
|
|
|
|
|
} else if (hasCoverImage && coverBufferStored && cachedCoverPath != coverBmpPath) {
|
|
|
|
|
// Book changed, invalidate cached buffer
|
|
|
|
|
freeCoverBuffer();
|
|
|
|
|
coverRendered = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
selectorIndex = 0;
|
|
|
|
|
@@ -112,8 +162,8 @@ void HomeActivity::onExit() {
|
|
|
|
|
vSemaphoreDelete(renderingMutex);
|
|
|
|
|
renderingMutex = nullptr;
|
|
|
|
|
|
|
|
|
|
// Free the stored cover buffer if any
|
|
|
|
|
freeCoverBuffer();
|
|
|
|
|
// NOTE: Do NOT free cover buffer here - keep it cached for fast re-entry
|
|
|
|
|
// The buffer will be freed/replaced when the book changes
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool HomeActivity::storeCoverBuffer() {
|
|
|
|
|
@@ -291,6 +341,9 @@ void HomeActivity::render() {
|
|
|
|
|
|
|
|
|
|
// Store the buffer with cover image for fast navigation
|
|
|
|
|
coverBufferStored = storeCoverBuffer();
|
|
|
|
|
if (coverBufferStored) {
|
|
|
|
|
cachedCoverPath = coverBmpPath; // Remember which cover is cached
|
|
|
|
|
}
|
|
|
|
|
coverRendered = true;
|
|
|
|
|
|
|
|
|
|
// First render: if selected, draw selection indicators now
|
|
|
|
|
@@ -552,13 +605,12 @@ void HomeActivity::render() {
|
|
|
|
|
const auto labels = mappedInput.mapLabels("", "Select", "Up", "Down");
|
|
|
|
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
|
|
|
|
|
|
|
|
|
// Draw battery in bottom-left where the back button hint would normally be
|
|
|
|
|
const bool showBatteryPercentage =
|
|
|
|
|
SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS;
|
|
|
|
|
// get percentage so we can align text properly
|
|
|
|
|
const uint16_t percentage = battery.readPercentage();
|
|
|
|
|
const auto percentageText = showBatteryPercentage ? std::to_string(percentage) + "%" : "";
|
|
|
|
|
const auto batteryX = pageWidth - 25 - renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
|
|
|
|
|
ScreenComponents::drawBattery(renderer, batteryX, 10, showBatteryPercentage);
|
|
|
|
|
constexpr int batteryX = 25; // Align with first button hint position
|
|
|
|
|
const int batteryY = pageHeight - 34; // Vertically centered in button hint area
|
|
|
|
|
ScreenComponents::drawBatteryLarge(renderer, batteryX, batteryY, showBatteryPercentage);
|
|
|
|
|
|
|
|
|
|
renderer.displayBuffer();
|
|
|
|
|
}
|
|
|
|
|
|