feat: Integrate bookmark support into reader activities
Adds bookmark add/remove functionality to EpubReaderActivity and base ReaderActivity, with visual indicator for bookmarked pages.
This commit is contained in:
parent
e991fb10a6
commit
245d5a7dd8
@ -8,6 +8,7 @@
|
|||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
#include "BookManager.h"
|
#include "BookManager.h"
|
||||||
|
#include "BookmarkStore.h"
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "CrossPointState.h"
|
#include "CrossPointState.h"
|
||||||
#include "EpubReaderChapterSelectionActivity.h"
|
#include "EpubReaderChapterSelectionActivity.h"
|
||||||
@ -17,6 +18,7 @@
|
|||||||
#include "activities/dictionary/DictionaryMenuActivity.h"
|
#include "activities/dictionary/DictionaryMenuActivity.h"
|
||||||
#include "activities/dictionary/DictionarySearchActivity.h"
|
#include "activities/dictionary/DictionarySearchActivity.h"
|
||||||
#include "activities/dictionary/EpubWordSelectionActivity.h"
|
#include "activities/dictionary/EpubWordSelectionActivity.h"
|
||||||
|
#include "activities/util/QuickMenuActivity.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -366,6 +368,149 @@ void EpubReaderActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Quick Menu power button press
|
||||||
|
if (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::QUICK_MENU &&
|
||||||
|
mappedInput.wasReleased(MappedInputManager::Button::Power)) {
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
|
||||||
|
// Check if current page is bookmarked
|
||||||
|
bool isBookmarked = false;
|
||||||
|
if (section) {
|
||||||
|
const uint32_t contentOffset = section->getContentOffsetForPage(section->currentPage);
|
||||||
|
isBookmarked = BookmarkStore::isPageBookmarked(epub->getPath(), currentSpineIndex, contentOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new QuickMenuActivity(
|
||||||
|
renderer, mappedInput,
|
||||||
|
[this](QuickMenuAction action) {
|
||||||
|
// Cache values before exitActivity
|
||||||
|
EpubReaderActivity* self = this;
|
||||||
|
GfxRenderer& cachedRenderer = renderer;
|
||||||
|
MappedInputManager& cachedMappedInput = mappedInput;
|
||||||
|
Section* cachedSection = section.get();
|
||||||
|
SemaphoreHandle_t cachedMutex = renderingMutex;
|
||||||
|
|
||||||
|
exitActivity();
|
||||||
|
|
||||||
|
if (action == QuickMenuAction::DICTIONARY) {
|
||||||
|
// Open dictionary menu
|
||||||
|
self->enterNewActivity(new DictionaryMenuActivity(
|
||||||
|
cachedRenderer, cachedMappedInput,
|
||||||
|
[self](DictionaryMode mode) {
|
||||||
|
GfxRenderer& r = self->renderer;
|
||||||
|
MappedInputManager& m = self->mappedInput;
|
||||||
|
Section* s = self->section.get();
|
||||||
|
SemaphoreHandle_t mtx = self->renderingMutex;
|
||||||
|
|
||||||
|
self->exitActivity();
|
||||||
|
|
||||||
|
if (mode == DictionaryMode::ENTER_WORD) {
|
||||||
|
self->enterNewActivity(new DictionarySearchActivity(r, m,
|
||||||
|
[self]() {
|
||||||
|
self->exitActivity();
|
||||||
|
self->updateRequired = true;
|
||||||
|
}, ""));
|
||||||
|
} else if (s) {
|
||||||
|
xSemaphoreTake(mtx, portMAX_DELAY);
|
||||||
|
auto page = s->loadPageFromSectionFile();
|
||||||
|
if (page) {
|
||||||
|
int mt, mr, mb, ml;
|
||||||
|
r.getOrientedViewableTRBL(&mt, &mr, &mb, &ml);
|
||||||
|
mt += SETTINGS.screenMargin;
|
||||||
|
ml += SETTINGS.screenMargin;
|
||||||
|
const int fontId = SETTINGS.getReaderFontId();
|
||||||
|
|
||||||
|
self->enterNewActivity(new EpubWordSelectionActivity(
|
||||||
|
r, m, std::move(page), fontId, ml, mt,
|
||||||
|
[self](const std::string& word) {
|
||||||
|
self->exitActivity();
|
||||||
|
self->enterNewActivity(new DictionarySearchActivity(
|
||||||
|
self->renderer, self->mappedInput,
|
||||||
|
[self]() {
|
||||||
|
self->exitActivity();
|
||||||
|
self->updateRequired = true;
|
||||||
|
}, word));
|
||||||
|
},
|
||||||
|
[self]() {
|
||||||
|
self->exitActivity();
|
||||||
|
self->updateRequired = true;
|
||||||
|
}));
|
||||||
|
xSemaphoreGive(mtx);
|
||||||
|
} else {
|
||||||
|
xSemaphoreGive(mtx);
|
||||||
|
self->updateRequired = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self->updateRequired = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[self]() {
|
||||||
|
self->exitActivity();
|
||||||
|
self->updateRequired = true;
|
||||||
|
},
|
||||||
|
self->section != nullptr));
|
||||||
|
} else if (action == QuickMenuAction::ADD_BOOKMARK) {
|
||||||
|
// Toggle bookmark on current page
|
||||||
|
if (self->section) {
|
||||||
|
const uint32_t contentOffset = self->section->getContentOffsetForPage(self->section->currentPage);
|
||||||
|
const std::string& bookPath = self->epub->getPath();
|
||||||
|
|
||||||
|
if (BookmarkStore::isPageBookmarked(bookPath, self->currentSpineIndex, contentOffset)) {
|
||||||
|
// Remove bookmark
|
||||||
|
BookmarkStore::removeBookmark(bookPath, self->currentSpineIndex, contentOffset);
|
||||||
|
} else {
|
||||||
|
// Add bookmark with auto-generated name
|
||||||
|
Bookmark bm;
|
||||||
|
bm.spineIndex = self->currentSpineIndex;
|
||||||
|
bm.contentOffset = contentOffset;
|
||||||
|
bm.pageNumber = self->section->currentPage;
|
||||||
|
bm.timestamp = millis() / 1000; // Approximate timestamp
|
||||||
|
|
||||||
|
// Generate name: "Chapter - Page X" or fallback
|
||||||
|
std::string chapterTitle;
|
||||||
|
const int tocIndex = self->epub->getTocIndexForSpineIndex(self->currentSpineIndex);
|
||||||
|
if (tocIndex >= 0) {
|
||||||
|
chapterTitle = self->epub->getTocItem(tocIndex).title;
|
||||||
|
}
|
||||||
|
if (!chapterTitle.empty()) {
|
||||||
|
bm.name = chapterTitle + " - Page " + std::to_string(self->section->currentPage + 1);
|
||||||
|
} else {
|
||||||
|
bm.name = "Page " + std::to_string(self->section->currentPage + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
BookmarkStore::addBookmark(bookPath, bm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self->updateRequired = true;
|
||||||
|
} else if (action == QuickMenuAction::CLEAR_CACHE) {
|
||||||
|
// Navigate to Clear Cache activity
|
||||||
|
if (self->onGoToClearCache) {
|
||||||
|
xSemaphoreGive(cachedMutex);
|
||||||
|
self->onGoToClearCache();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self->updateRequired = true;
|
||||||
|
} else if (action == QuickMenuAction::GO_TO_SETTINGS) {
|
||||||
|
// Navigate to Settings activity
|
||||||
|
if (self->onGoToSettings) {
|
||||||
|
xSemaphoreGive(cachedMutex);
|
||||||
|
self->onGoToSettings();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self->updateRequired = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[this]() {
|
||||||
|
EpubReaderActivity* self = this;
|
||||||
|
exitActivity();
|
||||||
|
self->updateRequired = true;
|
||||||
|
},
|
||||||
|
isBookmarked));
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::PageBack) ||
|
const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::PageBack) ||
|
||||||
mappedInput.wasReleased(MappedInputManager::Button::Left);
|
mappedInput.wasReleased(MappedInputManager::Button::Left);
|
||||||
const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::PageForward) ||
|
const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::PageForward) ||
|
||||||
@ -632,6 +777,24 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
|
|||||||
const int orientedMarginRight, const int orientedMarginBottom,
|
const int orientedMarginRight, const int orientedMarginBottom,
|
||||||
const int orientedMarginLeft) {
|
const int orientedMarginLeft) {
|
||||||
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop);
|
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop);
|
||||||
|
|
||||||
|
// Draw bookmark indicator (folded corner) if this page is bookmarked
|
||||||
|
if (section) {
|
||||||
|
const uint32_t contentOffset = section->getContentOffsetForPage(section->currentPage);
|
||||||
|
if (BookmarkStore::isPageBookmarked(epub->getPath(), currentSpineIndex, contentOffset)) {
|
||||||
|
// Draw folded corner in top-right
|
||||||
|
const int screenWidth = renderer.getScreenWidth();
|
||||||
|
constexpr int cornerSize = 20;
|
||||||
|
const int cornerX = screenWidth - orientedMarginRight - cornerSize;
|
||||||
|
const int cornerY = orientedMarginTop;
|
||||||
|
|
||||||
|
// Draw triangle (folded corner effect)
|
||||||
|
const int xPoints[3] = {cornerX, cornerX + cornerSize, cornerX + cornerSize};
|
||||||
|
const int yPoints[3] = {cornerY, cornerY, cornerY + cornerSize};
|
||||||
|
renderer.fillPolygon(xPoints, yPoints, 3, true); // Black triangle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||||
if (pagesUntilFullRefresh <= 1) {
|
if (pagesUntilFullRefresh <= 1) {
|
||||||
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
||||||
|
|||||||
@ -18,6 +18,8 @@ class EpubReaderActivity final : public ActivityWithSubactivity {
|
|||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
const std::function<void()> onGoBack;
|
const std::function<void()> onGoBack;
|
||||||
const std::function<void()> onGoHome;
|
const std::function<void()> onGoHome;
|
||||||
|
const std::function<void()> onGoToClearCache;
|
||||||
|
const std::function<void()> onGoToSettings;
|
||||||
|
|
||||||
// End-of-book prompt state
|
// End-of-book prompt state
|
||||||
bool showingEndOfBookPrompt = false;
|
bool showingEndOfBookPrompt = false;
|
||||||
@ -38,11 +40,15 @@ class EpubReaderActivity final : public ActivityWithSubactivity {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EpubReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr<Epub> epub,
|
explicit EpubReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr<Epub> epub,
|
||||||
const std::function<void()>& onGoBack, const std::function<void()>& onGoHome)
|
const std::function<void()>& onGoBack, const std::function<void()>& onGoHome,
|
||||||
|
const std::function<void()>& onGoToClearCache = nullptr,
|
||||||
|
const std::function<void()>& onGoToSettings = nullptr)
|
||||||
: ActivityWithSubactivity("EpubReader", renderer, mappedInput),
|
: ActivityWithSubactivity("EpubReader", renderer, mappedInput),
|
||||||
epub(std::move(epub)),
|
epub(std::move(epub)),
|
||||||
onGoBack(onGoBack),
|
onGoBack(onGoBack),
|
||||||
onGoHome(onGoHome) {}
|
onGoHome(onGoHome),
|
||||||
|
onGoToClearCache(onGoToClearCache),
|
||||||
|
onGoToSettings(onGoToSettings) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|||||||
@ -62,7 +62,11 @@ void ReaderActivity::onGoToEpubReader(std::unique_ptr<Epub> epub) {
|
|||||||
currentBookPath = epubPath;
|
currentBookPath = epubPath;
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new EpubReaderActivity(
|
enterNewActivity(new EpubReaderActivity(
|
||||||
renderer, mappedInput, std::move(epub), [this, epubPath] { goToLibrary(epubPath); }, [this] { onGoBack(); }));
|
renderer, mappedInput, std::move(epub),
|
||||||
|
[this, epubPath] { goToLibrary(epubPath); },
|
||||||
|
[this] { onGoBack(); },
|
||||||
|
onGoToClearCache,
|
||||||
|
onGoToSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReaderActivity::onGoToTxtReader(std::unique_ptr<Txt> txt) {
|
void ReaderActivity::onGoToTxtReader(std::unique_ptr<Txt> txt) {
|
||||||
|
|||||||
@ -13,6 +13,8 @@ class ReaderActivity final : public ActivityWithSubactivity {
|
|||||||
MyLibraryActivity::Tab libraryTab; // Track which tab to return to
|
MyLibraryActivity::Tab libraryTab; // Track which tab to return to
|
||||||
const std::function<void()> onGoBack;
|
const std::function<void()> onGoBack;
|
||||||
const std::function<void(const std::string&, MyLibraryActivity::Tab)> onGoToLibrary;
|
const std::function<void(const std::string&, MyLibraryActivity::Tab)> onGoToLibrary;
|
||||||
|
const std::function<void()> onGoToClearCache;
|
||||||
|
const std::function<void()> onGoToSettings;
|
||||||
static std::unique_ptr<Epub> loadEpub(const std::string& path);
|
static std::unique_ptr<Epub> loadEpub(const std::string& path);
|
||||||
static std::unique_ptr<Txt> loadTxt(const std::string& path);
|
static std::unique_ptr<Txt> loadTxt(const std::string& path);
|
||||||
static bool isTxtFile(const std::string& path);
|
static bool isTxtFile(const std::string& path);
|
||||||
@ -25,11 +27,15 @@ class ReaderActivity final : public ActivityWithSubactivity {
|
|||||||
public:
|
public:
|
||||||
explicit ReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string initialBookPath,
|
explicit ReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string initialBookPath,
|
||||||
MyLibraryActivity::Tab libraryTab, const std::function<void()>& onGoBack,
|
MyLibraryActivity::Tab libraryTab, const std::function<void()>& onGoBack,
|
||||||
const std::function<void(const std::string&, MyLibraryActivity::Tab)>& onGoToLibrary)
|
const std::function<void(const std::string&, MyLibraryActivity::Tab)>& onGoToLibrary,
|
||||||
|
const std::function<void()>& onGoToClearCache = nullptr,
|
||||||
|
const std::function<void()>& onGoToSettings = nullptr)
|
||||||
: ActivityWithSubactivity("Reader", renderer, mappedInput),
|
: ActivityWithSubactivity("Reader", renderer, mappedInput),
|
||||||
initialBookPath(std::move(initialBookPath)),
|
initialBookPath(std::move(initialBookPath)),
|
||||||
libraryTab(libraryTab),
|
libraryTab(libraryTab),
|
||||||
onGoBack(onGoBack),
|
onGoBack(onGoBack),
|
||||||
onGoToLibrary(onGoToLibrary) {}
|
onGoToLibrary(onGoToLibrary),
|
||||||
|
onGoToClearCache(onGoToClearCache),
|
||||||
|
onGoToSettings(onGoToSettings) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -20,6 +20,8 @@ class TxtReaderActivity final : public ActivityWithSubactivity {
|
|||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
const std::function<void()> onGoBack;
|
const std::function<void()> onGoBack;
|
||||||
const std::function<void()> onGoHome;
|
const std::function<void()> onGoHome;
|
||||||
|
const std::function<void()> onGoToClearCache;
|
||||||
|
const std::function<void()> onGoToSettings;
|
||||||
|
|
||||||
// End-of-book prompt state
|
// End-of-book prompt state
|
||||||
bool showingEndOfBookPrompt = false;
|
bool showingEndOfBookPrompt = false;
|
||||||
@ -56,11 +58,15 @@ class TxtReaderActivity final : public ActivityWithSubactivity {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TxtReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr<Txt> txt,
|
explicit TxtReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr<Txt> txt,
|
||||||
const std::function<void()>& onGoBack, const std::function<void()>& onGoHome)
|
const std::function<void()>& onGoBack, const std::function<void()>& onGoHome,
|
||||||
|
const std::function<void()>& onGoToClearCache = nullptr,
|
||||||
|
const std::function<void()>& onGoToSettings = nullptr)
|
||||||
: ActivityWithSubactivity("TxtReader", renderer, mappedInput),
|
: ActivityWithSubactivity("TxtReader", renderer, mappedInput),
|
||||||
txt(std::move(txt)),
|
txt(std::move(txt)),
|
||||||
onGoBack(onGoBack),
|
onGoBack(onGoBack),
|
||||||
onGoHome(onGoHome) {}
|
onGoHome(onGoHome),
|
||||||
|
onGoToClearCache(onGoToClearCache),
|
||||||
|
onGoToSettings(onGoToSettings) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user