feat: Display epub metadata on Recents (#511)

* **What is the goal of this PR?** Implement a metadata viewer for the
Recents screen
* **What changes are included?**

| Recents | Files |
| --- | --- |
| <img alt="image"
src="https://github.com/user-attachments/assets/e0f2d816-ddce-4a2e-bd4a-cd431d0e6532"
/> | <img alt="image"
src="https://github.com/user-attachments/assets/3225cdce-d501-4175-bc92-73cb8bfe7a41"
/> |

For the Files screen, I have not made any changes on purpose. For the
Recents screen, we now display the Book title and author. If it is a
file with no epub metadata like txt or md, we display the file name
without the file extension.

---

Did you use AI tools to help write this code? _**< YES  >**_

Although I went trough all the code manually and made changes as well,
please be aware the majority of the code is AI generated.

---------

Co-authored-by: Eliz Kilic <elizk@google.com>
This commit is contained in:
Eliz
2026-01-27 17:25:03 +00:00
committed by Dave Allie
parent ebcd813ff6
commit 172916afd4
7 changed files with 95 additions and 63 deletions

View File

@@ -7,22 +7,23 @@
#include <algorithm>
namespace {
constexpr uint8_t RECENT_BOOKS_FILE_VERSION = 1;
constexpr uint8_t RECENT_BOOKS_FILE_VERSION = 2;
constexpr char RECENT_BOOKS_FILE[] = "/.crosspoint/recent.bin";
constexpr int MAX_RECENT_BOOKS = 10;
} // namespace
RecentBooksStore RecentBooksStore::instance;
void RecentBooksStore::addBook(const std::string& path) {
void RecentBooksStore::addBook(const std::string& path, const std::string& title, const std::string& author) {
// Remove existing entry if present
auto it = std::find(recentBooks.begin(), recentBooks.end(), path);
auto it =
std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; });
if (it != recentBooks.end()) {
recentBooks.erase(it);
}
// Add to front
recentBooks.insert(recentBooks.begin(), path);
recentBooks.insert(recentBooks.begin(), {path, title, author});
// Trim to max size
if (recentBooks.size() > MAX_RECENT_BOOKS) {
@@ -46,7 +47,9 @@ bool RecentBooksStore::saveToFile() const {
serialization::writePod(outputFile, count);
for (const auto& book : recentBooks) {
serialization::writeString(outputFile, book);
serialization::writeString(outputFile, book.path);
serialization::writeString(outputFile, book.title);
serialization::writeString(outputFile, book.author);
}
outputFile.close();
@@ -63,24 +66,41 @@ bool RecentBooksStore::loadFromFile() {
uint8_t version;
serialization::readPod(inputFile, version);
if (version != RECENT_BOOKS_FILE_VERSION) {
Serial.printf("[%lu] [RBS] Deserialization failed: Unknown version %u\n", millis(), version);
inputFile.close();
return false;
}
if (version == 1) {
// Old version, just read paths
uint8_t count;
serialization::readPod(inputFile, count);
recentBooks.clear();
recentBooks.reserve(count);
for (uint8_t i = 0; i < count; i++) {
std::string path;
serialization::readString(inputFile, path);
// Title and author will be empty, they will be filled when the book is
// opened again
recentBooks.push_back({path, "", ""});
}
} else {
Serial.printf("[%lu] [RBS] Deserialization failed: Unknown version %u\n", millis(), version);
inputFile.close();
return false;
}
} else {
uint8_t count;
serialization::readPod(inputFile, count);
uint8_t count;
serialization::readPod(inputFile, count);
recentBooks.clear();
recentBooks.reserve(count);
recentBooks.clear();
recentBooks.reserve(count);
for (uint8_t i = 0; i < count; i++) {
std::string path;
serialization::readString(inputFile, path);
recentBooks.push_back(path);
for (uint8_t i = 0; i < count; i++) {
std::string path, title, author;
serialization::readString(inputFile, path);
serialization::readString(inputFile, title);
serialization::readString(inputFile, author);
recentBooks.push_back({path, title, author});
}
}
inputFile.close();
Serial.printf("[%lu] [RBS] Recent books loaded from file (%d entries)\n", millis(), count);
Serial.printf("[%lu] [RBS] Recent books loaded from file (%d entries)\n", millis(), recentBooks.size());
return true;
}