Merge branch 'feat-bookinfo' of https://github.com/jpirnay/crosspoint-reader into mybuild
This commit is contained in:
+39
-18
@@ -41,13 +41,14 @@ void RecentBooksStore::addBook(const std::string& path, const std::string& title
|
||||
}
|
||||
|
||||
void RecentBooksStore::updateBook(const std::string& path, const std::string& title, const std::string& author,
|
||||
const std::string& coverBmpPath) {
|
||||
const std::string& series, const std::string& coverBmpPath) {
|
||||
auto it =
|
||||
std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; });
|
||||
if (it != recentBooks.end()) {
|
||||
RecentBook& book = *it;
|
||||
book.title = title;
|
||||
book.author = author;
|
||||
book.series = series;
|
||||
book.coverBmpPath = coverBmpPath;
|
||||
saveToFile();
|
||||
}
|
||||
@@ -121,38 +122,57 @@ bool RecentBooksStore::loadFromBinaryFile() {
|
||||
// Old version, just read paths
|
||||
uint8_t count;
|
||||
serialization::readPod(inputFile, count);
|
||||
recentBooks.clear();
|
||||
recentBooks.reserve(count);
|
||||
std::vector<RecentBook> tmpRecentBooks;
|
||||
tmpRecentBooks.reserve(count);
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
std::string path;
|
||||
serialization::readString(inputFile, path);
|
||||
if (!serialization::readString(inputFile, path)) {
|
||||
LOG_ERR("RBS", "Corrupt recent.bin: string too long at entry %u", i);
|
||||
inputFile.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// load book to get missing data
|
||||
RecentBook book = getDataFromBook(path);
|
||||
if (book.title.empty() && book.author.empty() && version == 2) {
|
||||
// Fall back to loading what we can from the store
|
||||
std::string title, author;
|
||||
serialization::readString(inputFile, title);
|
||||
serialization::readString(inputFile, author);
|
||||
recentBooks.push_back({path, title, author, "", ""});
|
||||
if (version == 2) {
|
||||
// v2 always stores title and author after path; consume them regardless
|
||||
// of whether live metadata was found, to keep the stream aligned.
|
||||
std::string storedTitle, storedAuthor;
|
||||
if (!serialization::readString(inputFile, storedTitle) || !serialization::readString(inputFile, storedAuthor)) {
|
||||
LOG_ERR("RBS", "Corrupt recent.bin: string too long at entry %u", i);
|
||||
inputFile.close();
|
||||
return false;
|
||||
}
|
||||
// Prefer live metadata; fall back to stored when live is unavailable.
|
||||
const std::string& title = !book.title.empty() ? book.title : storedTitle;
|
||||
const std::string& author = !book.title.empty() ? book.author : storedAuthor;
|
||||
if (!title.empty()) {
|
||||
tmpRecentBooks.push_back({path, title, author, "", ""});
|
||||
}
|
||||
} else {
|
||||
recentBooks.push_back(book);
|
||||
// v1: no stored title/author bytes
|
||||
if (!book.title.empty()) {
|
||||
tmpRecentBooks.push_back(book);
|
||||
}
|
||||
}
|
||||
}
|
||||
recentBooks = std::move(tmpRecentBooks);
|
||||
} else if (version == 3) {
|
||||
uint8_t count;
|
||||
serialization::readPod(inputFile, count);
|
||||
|
||||
recentBooks.clear();
|
||||
recentBooks.reserve(count);
|
||||
std::vector<RecentBook> tmpRecentBooks;
|
||||
tmpRecentBooks.reserve(count);
|
||||
uint8_t omitted = 0;
|
||||
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
std::string path, title, author, coverBmpPath;
|
||||
serialization::readString(inputFile, path);
|
||||
serialization::readString(inputFile, title);
|
||||
serialization::readString(inputFile, author);
|
||||
serialization::readString(inputFile, coverBmpPath);
|
||||
if (!serialization::readString(inputFile, path) || !serialization::readString(inputFile, title) ||
|
||||
!serialization::readString(inputFile, author) || !serialization::readString(inputFile, coverBmpPath)) {
|
||||
LOG_ERR("RBS", "Corrupt recent.bin: string too long at entry %u", i);
|
||||
inputFile.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Omit books with missing title (e.g. saved before metadata was available)
|
||||
if (title.empty()) {
|
||||
@@ -160,8 +180,9 @@ bool RecentBooksStore::loadFromBinaryFile() {
|
||||
continue;
|
||||
}
|
||||
|
||||
recentBooks.push_back({path, title, author, "", coverBmpPath});
|
||||
tmpRecentBooks.push_back({path, title, author, "", coverBmpPath});
|
||||
}
|
||||
recentBooks = std::move(tmpRecentBooks);
|
||||
|
||||
if (omitted > 0) {
|
||||
inputFile.close();
|
||||
|
||||
@@ -36,7 +36,7 @@ class RecentBooksStore {
|
||||
const std::string& coverBmpPath);
|
||||
|
||||
void updateBook(const std::string& path, const std::string& title, const std::string& author,
|
||||
const std::string& coverBmpPath);
|
||||
const std::string& series, const std::string& coverBmpPath);
|
||||
|
||||
// Get the list of recent books (most recent first)
|
||||
const std::vector<RecentBook>& getBooks() const { return recentBooks; }
|
||||
|
||||
@@ -74,7 +74,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
|
||||
GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size()));
|
||||
bool success = epub.generateThumbBmp(coverHeight);
|
||||
if (!success) {
|
||||
RECENT_BOOKS.updateBook(book.path, book.title, book.author, "");
|
||||
RECENT_BOOKS.updateBook(book.path, book.title, book.author, book.series, "");
|
||||
book.coverBmpPath = "";
|
||||
}
|
||||
coverRendered = false;
|
||||
@@ -91,7 +91,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
|
||||
GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size()));
|
||||
bool success = xtc.generateThumbBmp(coverHeight);
|
||||
if (!success) {
|
||||
RECENT_BOOKS.updateBook(book.path, book.title, book.author, "");
|
||||
RECENT_BOOKS.updateBook(book.path, book.title, book.author, book.series, "");
|
||||
book.coverBmpPath = "";
|
||||
}
|
||||
coverRendered = false;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "RecentBooksActivity.h"
|
||||
|
||||
#include <FsHelpers.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalStorage.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "BookInfoActivity.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "RecentBooksStore.h"
|
||||
#include "components/UITheme.h"
|
||||
@@ -59,6 +61,17 @@ void RecentBooksActivity::loop() {
|
||||
onGoHome();
|
||||
}
|
||||
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Right)) {
|
||||
if (!recentBooks.empty() && selectorIndex < static_cast<int>(recentBooks.size())) {
|
||||
const std::string& path = recentBooks[selectorIndex].path;
|
||||
if (FsHelpers::hasEpubExtension(path) || FsHelpers::hasXtcExtension(path)) {
|
||||
startActivityForResult(std::make_unique<BookInfoActivity>(renderer, mappedInput, path),
|
||||
[this](const ActivityResult&) { requestUpdate(); });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int listSize = static_cast<int>(recentBooks.size());
|
||||
|
||||
buttonNavigator.onNextRelease([this, listSize] {
|
||||
@@ -103,7 +116,7 @@ void RecentBooksActivity::render(RenderLock&&) {
|
||||
[this](int index) { return recentBooks[index].title; },
|
||||
[this](int index) {
|
||||
const auto& book = recentBooks[index];
|
||||
if (!book.author.empty() && !book.series.empty()) return book.author + " \u2022 " + book.series;
|
||||
if (!book.author.empty() && !book.series.empty()) return book.author + "\n" + book.series;
|
||||
if (!book.series.empty()) return book.series;
|
||||
return book.author;
|
||||
},
|
||||
@@ -111,8 +124,14 @@ void RecentBooksActivity::render(RenderLock&&) {
|
||||
}
|
||||
|
||||
// Help text
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_HOME), tr(STR_OPEN), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
||||
const bool hasInfo = !recentBooks.empty() && selectorIndex < recentBooks.size() &&
|
||||
(FsHelpers::hasEpubExtension(recentBooks[selectorIndex].path) ||
|
||||
FsHelpers::hasXtcExtension(recentBooks[selectorIndex].path));
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_HOME), tr(STR_OPEN), "", hasInfo ? tr(STR_INFO) : "");
|
||||
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
// Side buttons (Up/Down) navigate; show their hints on the side
|
||||
GUI.drawSideButtonHints(renderer, tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
||||
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
|
||||
@@ -235,8 +235,12 @@ void BaseTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount,
|
||||
renderer.drawText(font, rect.x + BaseMetrics::values.contentSidePadding, itemY, item.c_str(), i != selectedIndex);
|
||||
|
||||
if (rowSubtitle != nullptr) {
|
||||
// Draw subtitle
|
||||
// Draw subtitle; if the text is newline-separated (author\nseries), join with • for single-line display
|
||||
std::string subtitleText = rowSubtitle(i);
|
||||
const auto nl = subtitleText.find('\n');
|
||||
if (nl != std::string::npos) {
|
||||
subtitleText = subtitleText.substr(0, nl) + " \u2022 " + subtitleText.substr(nl + 1);
|
||||
}
|
||||
auto subtitle = renderer.truncatedText(UI_10_FONT_ID, subtitleText.c_str(), textWidth);
|
||||
renderer.drawText(UI_10_FONT_ID, rect.x + BaseMetrics::values.contentSidePadding, itemY + 30, subtitle.c_str(),
|
||||
i != selectedIndex);
|
||||
|
||||
@@ -318,10 +318,18 @@ void LyraTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount,
|
||||
}
|
||||
|
||||
if (rowSubtitle != nullptr) {
|
||||
// Draw subtitle
|
||||
std::string subtitleText = rowSubtitle(i);
|
||||
auto subtitle = renderer.truncatedText(SMALL_FONT_ID, subtitleText.c_str(), rowTextWidth);
|
||||
renderer.drawText(SMALL_FONT_ID, textX, itemY + 30, subtitle.c_str(), true);
|
||||
const auto nl = subtitleText.find('\n');
|
||||
if (nl != std::string::npos) {
|
||||
// Two-line subtitle: first line (author) at +24, second line (series) at +40
|
||||
auto line1 = renderer.truncatedText(SMALL_FONT_ID, subtitleText.substr(0, nl).c_str(), rowTextWidth);
|
||||
renderer.drawText(SMALL_FONT_ID, textX, itemY + 24, line1.c_str(), true);
|
||||
auto line2 = renderer.truncatedText(SMALL_FONT_ID, subtitleText.substr(nl + 1).c_str(), rowTextWidth);
|
||||
renderer.drawText(SMALL_FONT_ID, textX, itemY + 40, line2.c_str(), true);
|
||||
} else {
|
||||
auto subtitle = renderer.truncatedText(SMALL_FONT_ID, subtitleText.c_str(), rowTextWidth);
|
||||
renderer.drawText(SMALL_FONT_ID, textX, itemY + 30, subtitle.c_str(), true);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw value
|
||||
|
||||
@@ -14,7 +14,7 @@ constexpr ThemeMetrics values = {.batteryWidth = 16,
|
||||
.verticalSpacing = 16,
|
||||
.contentSidePadding = 20,
|
||||
.listRowHeight = 40,
|
||||
.listWithSubtitleRowHeight = 60,
|
||||
.listWithSubtitleRowHeight = 68,
|
||||
.menuRowHeight = 64,
|
||||
.menuSpacing = 8,
|
||||
.tabSpacing = 8,
|
||||
|
||||
Reference in New Issue
Block a user