## Summary Fix for the page skip issue detected https://github.com/crosspoint-reader/crosspoint-reader/pull/700#issuecomment-3856374323 by user @whyte-j Skipping down on the last page now skips to the last item, and up on the first page to the first item, rather than wrapping around the list in a weird way. ## Additional Context The calculation was outdated after several changes were added afterwards --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_
152 lines
4.6 KiB
C++
152 lines
4.6 KiB
C++
#include "RecentBooksActivity.h"
|
|
|
|
#include <GfxRenderer.h>
|
|
#include <SDCardManager.h>
|
|
|
|
#include <algorithm>
|
|
|
|
#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::getInstance().getNumberOfItemsPerPage(renderer, true, false, 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 = std::max(static_cast<int>((selectorIndex / pageItems - 1) * pageItems), 0);
|
|
} else {
|
|
selectorIndex = (selectorIndex + listSize - 1) % listSize;
|
|
}
|
|
updateRequired = true;
|
|
} else if (downReleased) {
|
|
if (skipPage) {
|
|
selectorIndex = std::min(static_cast<int>((selectorIndex / pageItems + 1) * pageItems), listSize - 1);
|
|
} 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::getInstance().getMetrics();
|
|
|
|
GUI.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;
|
|
|
|
// Recent tab
|
|
if (recentBooks.empty()) {
|
|
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, contentTop + 20, "No recent books");
|
|
} else {
|
|
GUI.drawList(
|
|
renderer, Rect{0, contentTop, pageWidth, contentHeight}, recentBooks.size(), selectorIndex,
|
|
[this](int index) { return recentBooks[index].title; }, [this](int index) { return recentBooks[index].author; },
|
|
nullptr, nullptr);
|
|
}
|
|
|
|
// Help text
|
|
const auto labels = mappedInput.mapLabels("« Home", "Open", "Up", "Down");
|
|
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
|
|
|
renderer.displayBuffer();
|
|
}
|