#include "RecentBooksActivity.h" #include #include #include #include #include #include "MappedInputManager.h" #include "RecentBooksStore.h" #include "activities/util/ConfirmationActivity.h" #include "components/UITheme.h" #include "fontIds.h" namespace { // Hold threshold for the long-press "remove from list" action (firmware convention). constexpr unsigned long LONG_PRESS_MS = 1000; } // namespace void RecentBooksActivity::loadRecentBooks() { recentBooks = RECENT_BOOKS.getBooks(); } void RecentBooksActivity::onEnter() { Activity::onEnter(); // Prune entries whose backing files are gone; this is one of two interaction // points where the persistent store gets cleaned (the other is addBook). if (RECENT_BOOKS.pruneMissing()) { RECENT_BOOKS.saveToFile(); } // Load data loadRecentBooks(); selectorIndex = 0; requestUpdate(); } void RecentBooksActivity::onExit() { Activity::onExit(); recentBooks.clear(); } void RecentBooksActivity::loop() { const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, true); // Vertical swipe page-scrolls the list (touch nav without the side buttons). int scrollIdx = static_cast(selectorIndex); if (mappedInput.wasListScroll(scrollIdx, static_cast(recentBooks.size()), pageItems)) { selectorIndex = scrollIdx; requestUpdate(); return; } // After a long-press has fired, swallow input until Confirm is physically released // (so the release doesn't also open the book; re-arm only once the button is up). if (longPressFired) { if (!mappedInput.isPressed(MappedInputManager::Button::Confirm)) { longPressFired = false; } return; } // Long-press Confirm on the selected book: prompt to remove it from the list. // Fires when the hold times out while still held (firmware hold-to-act pattern, // cf. FileBrowserActivity BACK long-press). if (!recentBooks.empty() && selectorIndex < recentBooks.size() && mappedInput.isPressed(MappedInputManager::Button::Confirm) && mappedInput.getHeldTime() >= LONG_PRESS_MS) { longPressFired = true; promptRemoveBook(recentBooks[selectorIndex].path, recentBooks[selectorIndex].title); return; } int downId = -1; if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast(recentBooks.size())) { selectorIndex = downId; requestUpdate(); } int tappedId = -1; const bool tapped = mappedInput.wasItemTapped(tappedId); if (tapped && tappedId >= 0 && tappedId < static_cast(recentBooks.size())) selectorIndex = tappedId; if (tapped || mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (!recentBooks.empty() && selectorIndex < static_cast(recentBooks.size())) { LOG_DBG("RBA", "Selected recent book: %s", recentBooks[selectorIndex].path.c_str()); onSelectBook(recentBooks[selectorIndex].path); return; } } if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { onGoHome(); } int listSize = static_cast(recentBooks.size()); buttonNavigator.onNextRelease([this, listSize] { selectorIndex = ButtonNavigator::nextIndex(static_cast(selectorIndex), listSize); requestUpdate(); }); buttonNavigator.onPreviousRelease([this, listSize] { selectorIndex = ButtonNavigator::previousIndex(static_cast(selectorIndex), listSize); requestUpdate(); }); buttonNavigator.onNextContinuous([this, listSize, pageItems] { selectorIndex = ButtonNavigator::nextPageIndex(static_cast(selectorIndex), listSize, pageItems); requestUpdate(); }); buttonNavigator.onPreviousContinuous([this, listSize, pageItems] { selectorIndex = ButtonNavigator::previousPageIndex(static_cast(selectorIndex), listSize, pageItems); requestUpdate(); }); } void RecentBooksActivity::promptRemoveBook(const std::string& path, const std::string& title) { auto handler = [this, path](const ActivityResult& res) { if (res.isCancelled) { LOG_DBG("RBA", "Remove from recents cancelled"); return; } if (RECENT_BOOKS.removeByPath(path)) { LOG_DBG("RBA", "Removed from recents: %s", path.c_str()); loadRecentBooks(); if (recentBooks.empty()) { selectorIndex = 0; } else if (selectorIndex >= recentBooks.size()) { selectorIndex = recentBooks.size() - 1; } requestUpdate(true); } }; startActivityForResult( std::make_unique(renderer, mappedInput, tr(STR_REMOVE_FROM_RECENTS), title), std::move(handler)); } void RecentBooksActivity::render(RenderLock&&) { renderer.clearScreen(); const auto pageWidth = renderer.getScreenWidth(); const auto pageHeight = renderer.getScreenHeight(); const auto& metrics = UITheme::getInstance().getMetrics(); GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_MENU_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, tr(STR_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; }, [this](int index) { return UITheme::getFileIcon(recentBooks[index].path); }); } // Help text const auto labels = mappedInput.mapLabels(tr(STR_HOME), tr(STR_OPEN), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); }