Xteink-X4-crosspoint-reader/src/activities/home/RecentBooksActivity.cpp
2026-01-30 16:34:12 +07:00

149 lines
4.5 KiB
C++

#include "RecentBooksActivity.h"
#include <GfxRenderer.h>
#include <SDCardManager.h>
#include "MappedInputManager.h"
#include "RecentBooksStore.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "util/StringUtils.h"
namespace {
constexpr int SKIP_PAGE_MS = 700;
constexpr unsigned long GO_HOME_MS = 1000;
} // namespace
void RecentBooksActivity::taskTrampoline(void* param) {
auto* self = static_cast<RecentBooksActivity*>(param);
self->displayTaskLoop();
}
void RecentBooksActivity::loadRecentBooks() {
recentBooks.clear();
const auto& books = RECENT_BOOKS.getBooks();
recentBooks.reserve(books.size());
for (const auto& book : books) {
// Skip if file no longer exists
if (!SdMan.exists(book.path.c_str())) {
continue;
}
recentBooks.push_back(book);
}
}
void RecentBooksActivity::onEnter() {
Activity::onEnter();
renderingMutex = xSemaphoreCreateMutex();
// Load data
loadRecentBooks();
selectorIndex = 0;
updateRequired = true;
xTaskCreate(&RecentBooksActivity::taskTrampoline, "RecentBooksActivityTask",
4096, // Stack size
this, // Parameters
1, // Priority
&displayTaskHandle // Task handle
);
}
void RecentBooksActivity::onExit() {
Activity::onExit();
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr;
}
vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr;
recentBooks.clear();
}
void RecentBooksActivity::loop() {
const bool upReleased = mappedInput.wasReleased(MappedInputManager::Button::Left) ||
mappedInput.wasReleased(MappedInputManager::Button::Up);
;
const bool downReleased = mappedInput.wasReleased(MappedInputManager::Button::Right) ||
mappedInput.wasReleased(MappedInputManager::Button::Down);
const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS;
const int pageItems = UITheme::getNumberOfItemsPerPage(renderer, true, true, true);
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
if (!recentBooks.empty() && selectorIndex < static_cast<int>(recentBooks.size())) {
Serial.printf("Selected recent book: %s\n", recentBooks[selectorIndex].path.c_str());
onSelectBook(recentBooks[selectorIndex].path);
return;
}
}
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
onGoHome();
}
int listSize = static_cast<int>(recentBooks.size());
if (upReleased) {
if (skipPage) {
selectorIndex = ((selectorIndex / pageItems - 1) * pageItems + listSize) % listSize;
} else {
selectorIndex = (selectorIndex + listSize - 1) % listSize;
}
updateRequired = true;
} else if (downReleased) {
if (skipPage) {
selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % listSize;
} else {
selectorIndex = (selectorIndex + 1) % listSize;
}
updateRequired = true;
}
}
void RecentBooksActivity::displayTaskLoop() {
while (true) {
if (updateRequired) {
updateRequired = false;
xSemaphoreTake(renderingMutex, portMAX_DELAY);
render();
xSemaphoreGive(renderingMutex);
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void RecentBooksActivity::render() const {
renderer.clearScreen();
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
auto metrics = UITheme::getMetrics();
UITheme::drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, "Recent Books");
const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing;
const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing * 2;
// Recent tab
if (recentBooks.empty()) {
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, contentTop + 20, "No recent books");
} else {
UITheme::drawList(
renderer, Rect{0, contentTop, pageWidth, contentHeight}, recentBooks.size(), selectorIndex,
[this](int index) { return recentBooks[index].title; }, false, nullptr, false, nullptr);
}
// Help text
const auto labels = mappedInput.mapLabels("« Home", "Open", "Up", "Down");
UITheme::drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();
}