adds delete and archive abilities

This commit is contained in:
cottongin
2026-01-22 15:45:07 -05:00
parent 6b533207e1
commit d5a9873bd7
16 changed files with 1389 additions and 41 deletions

View File

@@ -5,6 +5,7 @@
#include <GfxRenderer.h>
#include <SDCardManager.h>
#include "BookManager.h"
#include "CrossPointSettings.h"
#include "CrossPointState.h"
#include "EpubReaderChapterSelectionActivity.h"
@@ -119,6 +120,33 @@ void EpubReaderActivity::loop() {
return;
}
// Handle end-of-book prompt
if (showingEndOfBookPrompt) {
if (mappedInput.wasReleased(MappedInputManager::Button::Up)) {
endOfBookSelection = (endOfBookSelection + 2) % 3;
updateRequired = true;
return;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Down)) {
endOfBookSelection = (endOfBookSelection + 1) % 3;
updateRequired = true;
return;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
handleEndOfBookAction();
return;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
// Go back to last page instead
currentSpineIndex = epub->getSpineItemsCount() - 1;
nextPageNumber = UINT16_MAX;
showingEndOfBookPrompt = false;
updateRequired = true;
return;
}
return;
}
// Enter chapter selection activity
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
// Don't start activity transition while rendering
@@ -260,11 +288,9 @@ void EpubReaderActivity::loop() {
return;
}
// any botton press when at end of the book goes back to the last page
if (currentSpineIndex > 0 && currentSpineIndex >= epub->getSpineItemsCount()) {
currentSpineIndex = epub->getSpineItemsCount() - 1;
nextPageNumber = UINT16_MAX;
updateRequired = true;
// any button press when at end of the book - this is now handled by the prompt
// Just ensure we don't go past the end
if (currentSpineIndex >= epub->getSpineItemsCount()) {
return;
}
@@ -341,13 +367,13 @@ void EpubReaderActivity::renderScreen() {
currentSpineIndex = epub->getSpineItemsCount();
}
// Show end of book screen
// Show end of book prompt
if (currentSpineIndex == epub->getSpineItemsCount()) {
renderer.clearScreen();
renderer.drawCenteredText(UI_12_FONT_ID, 300, "End of book", true, EpdFontFamily::BOLD);
renderer.displayBuffer();
showingEndOfBookPrompt = true;
renderEndOfBookPrompt();
return;
}
showingEndOfBookPrompt = false;
// Apply screen viewable areas and additional padding
int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft;
@@ -586,3 +612,60 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in
title.c_str());
}
}
void EpubReaderActivity::renderEndOfBookPrompt() {
const int pageWidth = renderer.getScreenWidth();
const int pageHeight = renderer.getScreenHeight();
renderer.clearScreen();
// Title
renderer.drawCenteredText(UI_12_FONT_ID, 80, "Finished!", true, EpdFontFamily::BOLD);
// Book title (truncated if needed)
std::string bookTitle = epub->getTitle();
if (bookTitle.length() > 30) {
bookTitle = bookTitle.substr(0, 27) + "...";
}
renderer.drawCenteredText(UI_10_FONT_ID, 120, bookTitle.c_str());
// Menu options
const int menuStartY = pageHeight / 2 - 30;
constexpr int menuLineHeight = 45;
constexpr int menuItemWidth = 140;
const int menuX = (pageWidth - menuItemWidth) / 2;
const char* options[] = {"Archive", "Delete", "Keep"};
for (int i = 0; i < 3; i++) {
const int optionY = menuStartY + i * menuLineHeight;
if (endOfBookSelection == i) {
renderer.fillRect(menuX - 10, optionY - 5, menuItemWidth + 20, menuLineHeight - 5);
}
renderer.drawCenteredText(UI_10_FONT_ID, optionY, options[i], endOfBookSelection != i);
}
// Button hints
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();
}
void EpubReaderActivity::handleEndOfBookAction() {
const std::string bookPath = epub->getPath();
switch (endOfBookSelection) {
case 0: // Archive
BookManager::archiveBook(bookPath);
onGoHome();
break;
case 1: // Delete
BookManager::deleteBook(bookPath);
onGoHome();
break;
case 2: // Keep
default:
onGoHome();
break;
}
}

View File

@@ -19,12 +19,18 @@ class EpubReaderActivity final : public ActivityWithSubactivity {
const std::function<void()> onGoBack;
const std::function<void()> onGoHome;
// End-of-book prompt state
bool showingEndOfBookPrompt = false;
int endOfBookSelection = 2; // 0=Archive, 1=Delete, 2=Keep (default to safe option)
static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop();
void renderScreen();
void renderContents(std::unique_ptr<Page> page, int orientedMarginTop, int orientedMarginRight,
int orientedMarginBottom, int orientedMarginLeft);
void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const;
void renderEndOfBookPrompt();
void handleEndOfBookAction();
public:
explicit EpubReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr<Epub> epub,

View File

@@ -5,6 +5,7 @@
#include <Serialization.h>
#include <Utf8.h>
#include "BookManager.h"
#include "CrossPointSettings.h"
#include "CrossPointState.h"
#include "MappedInputManager.h"
@@ -95,6 +96,30 @@ void TxtReaderActivity::loop() {
return;
}
// Handle end-of-book prompt
if (showingEndOfBookPrompt) {
if (mappedInput.wasReleased(MappedInputManager::Button::Up)) {
endOfBookSelection = (endOfBookSelection + 2) % 3;
updateRequired = true;
return;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Down)) {
endOfBookSelection = (endOfBookSelection + 1) % 3;
updateRequired = true;
return;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
handleEndOfBookAction();
return;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
showingEndOfBookPrompt = false;
updateRequired = true;
return;
}
return;
}
// Long press BACK (1s+) goes directly to home
if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= goHomeMs) {
onGoHome();
@@ -121,9 +146,15 @@ void TxtReaderActivity::loop() {
if (prevReleased && currentPage > 0) {
currentPage--;
updateRequired = true;
} else if (nextReleased && currentPage < totalPages - 1) {
currentPage++;
updateRequired = true;
} else if (nextReleased) {
if (currentPage < totalPages - 1) {
currentPage++;
updateRequired = true;
} else {
// At last page, show end-of-book prompt
showingEndOfBookPrompt = true;
updateRequired = true;
}
}
}
@@ -381,6 +412,12 @@ void TxtReaderActivity::renderScreen() {
return;
}
// Show end-of-book prompt if active
if (showingEndOfBookPrompt) {
renderEndOfBookPrompt();
return;
}
// Initialize reader if not done
if (!initialized) {
renderer.clearScreen();
@@ -698,3 +735,64 @@ void TxtReaderActivity::savePageIndexCache() const {
f.close();
Serial.printf("[%lu] [TRS] Saved page index cache: %d pages\n", millis(), totalPages);
}
void TxtReaderActivity::renderEndOfBookPrompt() {
const int pageWidth = renderer.getScreenWidth();
const int pageHeight = renderer.getScreenHeight();
renderer.clearScreen();
// Title
renderer.drawCenteredText(UI_12_FONT_ID, 80, "Finished!", true, EpdFontFamily::BOLD);
// Filename (truncated if needed)
std::string filename = txt->getPath();
const size_t lastSlash = filename.find_last_of('/');
if (lastSlash != std::string::npos) {
filename = filename.substr(lastSlash + 1);
}
if (filename.length() > 30) {
filename = filename.substr(0, 27) + "...";
}
renderer.drawCenteredText(UI_10_FONT_ID, 120, filename.c_str());
// Menu options
const int menuStartY = pageHeight / 2 - 30;
constexpr int menuLineHeight = 45;
constexpr int menuItemWidth = 140;
const int menuX = (pageWidth - menuItemWidth) / 2;
const char* options[] = {"Archive", "Delete", "Keep"};
for (int i = 0; i < 3; i++) {
const int optionY = menuStartY + i * menuLineHeight;
if (endOfBookSelection == i) {
renderer.fillRect(menuX - 10, optionY - 5, menuItemWidth + 20, menuLineHeight - 5);
}
renderer.drawCenteredText(UI_10_FONT_ID, optionY, options[i], endOfBookSelection != i);
}
// Button hints
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();
}
void TxtReaderActivity::handleEndOfBookAction() {
const std::string bookPath = txt->getPath();
switch (endOfBookSelection) {
case 0: // Archive
BookManager::archiveBook(bookPath);
onGoHome();
break;
case 1: // Delete
BookManager::deleteBook(bookPath);
onGoHome();
break;
case 2: // Keep
default:
onGoHome();
break;
}
}

View File

@@ -21,6 +21,10 @@ class TxtReaderActivity final : public ActivityWithSubactivity {
const std::function<void()> onGoBack;
const std::function<void()> onGoHome;
// End-of-book prompt state
bool showingEndOfBookPrompt = false;
int endOfBookSelection = 2; // 0=Archive, 1=Delete, 2=Keep
// Streaming text reader - stores file offsets for each page
std::vector<size_t> pageOffsets; // File offset for start of each page
std::vector<std::string> currentPageLines;
@@ -38,6 +42,8 @@ class TxtReaderActivity final : public ActivityWithSubactivity {
void renderScreen();
void renderPage();
void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const;
void renderEndOfBookPrompt();
void handleEndOfBookAction();
void initializeReader();
bool loadPageAtOffset(size_t offset, std::vector<std::string>& outLines, size_t& nextOffset);

View File

@@ -11,6 +11,7 @@
#include <GfxRenderer.h>
#include <SDCardManager.h>
#include "BookManager.h"
#include "CrossPointSettings.h"
#include "CrossPointState.h"
#include "MappedInputManager.h"
@@ -79,6 +80,32 @@ void XtcReaderActivity::loop() {
return;
}
// Handle end-of-book prompt
if (showingEndOfBookPrompt) {
if (mappedInput.wasReleased(MappedInputManager::Button::Up)) {
endOfBookSelection = (endOfBookSelection + 2) % 3;
updateRequired = true;
return;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Down)) {
endOfBookSelection = (endOfBookSelection + 1) % 3;
updateRequired = true;
return;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
handleEndOfBookAction();
return;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
// Go back to last page
currentPage = xtc->getPageCount() - 1;
showingEndOfBookPrompt = false;
updateRequired = true;
return;
}
return;
}
// Enter chapter selection activity
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
if (xtc && xtc->hasChapters() && !xtc->getChapters().empty()) {
@@ -122,10 +149,8 @@ void XtcReaderActivity::loop() {
return;
}
// Handle end of book
// If at end of book prompt position, handle differently
if (currentPage >= xtc->getPageCount()) {
currentPage = xtc->getPageCount() - 1;
updateRequired = true;
return;
}
@@ -142,7 +167,7 @@ void XtcReaderActivity::loop() {
} else if (nextReleased) {
currentPage += skipAmount;
if (currentPage >= xtc->getPageCount()) {
currentPage = xtc->getPageCount(); // Allow showing "End of book"
currentPage = xtc->getPageCount(); // Will trigger end-of-book prompt
}
updateRequired = true;
}
@@ -165,14 +190,13 @@ void XtcReaderActivity::renderScreen() {
return;
}
// Bounds check
// Bounds check - show end-of-book prompt
if (currentPage >= xtc->getPageCount()) {
// Show end of book screen
renderer.clearScreen();
renderer.drawCenteredText(UI_12_FONT_ID, 300, "End of book", true, EpdFontFamily::BOLD);
renderer.displayBuffer();
showingEndOfBookPrompt = true;
renderEndOfBookPrompt();
return;
}
showingEndOfBookPrompt = false;
renderPage();
saveProgress();
@@ -389,3 +413,64 @@ void XtcReaderActivity::loadProgress() {
f.close();
}
}
void XtcReaderActivity::renderEndOfBookPrompt() {
const int pageWidth = renderer.getScreenWidth();
const int pageHeight = renderer.getScreenHeight();
renderer.clearScreen();
// Title
renderer.drawCenteredText(UI_12_FONT_ID, 80, "Finished!", true, EpdFontFamily::BOLD);
// Filename (truncated if needed)
std::string filename = xtc->getPath();
const size_t lastSlash = filename.find_last_of('/');
if (lastSlash != std::string::npos) {
filename = filename.substr(lastSlash + 1);
}
if (filename.length() > 30) {
filename = filename.substr(0, 27) + "...";
}
renderer.drawCenteredText(UI_10_FONT_ID, 120, filename.c_str());
// Menu options
const int menuStartY = pageHeight / 2 - 30;
constexpr int menuLineHeight = 45;
constexpr int menuItemWidth = 140;
const int menuX = (pageWidth - menuItemWidth) / 2;
const char* options[] = {"Archive", "Delete", "Keep"};
for (int i = 0; i < 3; i++) {
const int optionY = menuStartY + i * menuLineHeight;
if (endOfBookSelection == i) {
renderer.fillRect(menuX - 10, optionY - 5, menuItemWidth + 20, menuLineHeight - 5);
}
renderer.drawCenteredText(UI_10_FONT_ID, optionY, options[i], endOfBookSelection != i);
}
// Button hints
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();
}
void XtcReaderActivity::handleEndOfBookAction() {
const std::string bookPath = xtc->getPath();
switch (endOfBookSelection) {
case 0: // Archive
BookManager::archiveBook(bookPath);
onGoHome();
break;
case 1: // Delete
BookManager::deleteBook(bookPath);
onGoHome();
break;
case 2: // Keep
default:
onGoHome();
break;
}
}

View File

@@ -24,12 +24,18 @@ class XtcReaderActivity final : public ActivityWithSubactivity {
const std::function<void()> onGoBack;
const std::function<void()> onGoHome;
// End-of-book prompt state
bool showingEndOfBookPrompt = false;
int endOfBookSelection = 2; // 0=Archive, 1=Delete, 2=Keep
static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop();
void renderScreen();
void renderPage();
void saveProgress() const;
void loadProgress();
void renderEndOfBookPrompt();
void handleEndOfBookAction();
public:
explicit XtcReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr<Xtc> xtc,