From c5b8c55c0463d398d5b45f5afb1960adf26e38fb Mon Sep 17 00:00:00 2001 From: jpirnay Date: Wed, 20 May 2026 20:56:03 +0200 Subject: [PATCH] Some fixes --- src/ReadingSessionTracker.cpp | 10 ++++++++-- src/ReadingStats.cpp | 8 ++------ src/activities/reader/TxtReaderActivity.cpp | 4 ++++ src/activities/reader/XtcReaderActivity.cpp | 6 ++++++ .../settings/ReadingStatsActivity.cpp | 7 +++++-- .../settings/ReadingStatsBookDetailActivity.cpp | 17 ++++++++++++++--- .../settings/ReadingStatsBookListActivity.cpp | 7 ++++--- src/components/CardLayout.cpp | 2 +- src/components/CardLayout.h | 2 +- src/network/html/FilesPage.html | 1 + src/network/html/HomePage.html | 1 + src/network/html/SettingsPage.html | 1 + src/network/html/StatsPage.html | 6 ++++-- 13 files changed, 52 insertions(+), 20 deletions(-) diff --git a/src/ReadingSessionTracker.cpp b/src/ReadingSessionTracker.cpp index c5b3a236..f4f7770b 100644 --- a/src/ReadingSessionTracker.cpp +++ b/src/ReadingSessionTracker.cpp @@ -53,7 +53,10 @@ void ReadingSessionTracker::markFinished() { if (!active) return; const int64_t walltime = HalClock::isSynced() ? static_cast(HalClock::now()) : 0; READING_STATS.markFinished(docId, title, author, static_cast(walltime)); - READING_STATS.saveToFile(); + if (!READING_STATS.saveToFile()) { + LOG_ERR("RST", "saveToFile failed (markFinished) doc=%s title=%s author=%s wall=%lld", docId.c_str(), title.c_str(), + author.c_str(), (long long)walltime); + } LOG_DBG("RST", "Marked finished doc=%s wall=%lld", docId.c_str(), (long long)walltime); } @@ -77,7 +80,10 @@ void ReadingSessionTracker::end() { if (seconds > 0 && !docId.empty()) { READING_STATS.recordSession(docId, title, author, seconds, pagesTurnedThisSession, lastKnownProgress, static_cast(walltime)); - READING_STATS.saveToFile(); + if (!READING_STATS.saveToFile()) { + LOG_ERR("RST", "saveToFile failed (session end) doc=%s title=%s author=%s secs=%u pages=%u wall=%lld", + docId.c_str(), title.c_str(), author.c_str(), seconds, pagesTurnedThisSession, (long long)walltime); + } } active = false; diff --git a/src/ReadingStats.cpp b/src/ReadingStats.cpp index 9a187280..27444251 100644 --- a/src/ReadingStats.cpp +++ b/src/ReadingStats.cpp @@ -118,7 +118,6 @@ uint16_t ReadingStatsStore::computeCurrentStreak(uint16_t today) const { // yesterday. After that the chain is broken. uint16_t anchor = today; if (getSecondsForDay(anchor) == 0) { - if (anchor == 0) return 0; anchor -= 1; if (getSecondsForDay(anchor) == 0) return 0; } @@ -170,11 +169,8 @@ void ReadingStatsStore::markFinished(const std::string& docId, const std::string } size_t ReadingStatsStore::getFinishedBookCount() const { - size_t n = 0; - for (const auto& b : books) { - if (b.finishedCount > 0) ++n; - } - return n; + return static_cast( + std::count_if(books.begin(), books.end(), [](const BookReadingStats& b) { return b.finishedCount > 0; })); } const BookReadingStats* ReadingStatsStore::findBook(const std::string& docId) const { diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index fd301d65..e830d35d 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -905,23 +905,27 @@ void TxtReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION a case BA::BTN_PAGE_FORWARD: if (currentPage < totalPages - 1) { currentPage++; + globalReadingSessionTracker().onPageTurn(); requestUpdate(); } break; case BA::BTN_PAGE_BACK: if (currentPage > 0) { currentPage--; + globalReadingSessionTracker().onPageTurn(); requestUpdate(); } break; case BA::BTN_PAGE_FORWARD_10: currentPage += 10; clampPage(); + globalReadingSessionTracker().onPageTurn(); requestUpdate(); break; case BA::BTN_PAGE_BACK_10: currentPage -= 10; clampPage(); + globalReadingSessionTracker().onPageTurn(); requestUpdate(); break; case BA::BTN_STAR_PAGE: diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 0b22adf4..f9d36c66 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -523,21 +523,25 @@ void XtcReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION a case BA::BTN_PAGE_FORWARD: if (currentPage + 1 < pageCount) { currentPage++; + globalReadingSessionTracker().onPageTurn(); requestUpdate(); } break; case BA::BTN_PAGE_BACK: if (currentPage > 0) { currentPage--; + globalReadingSessionTracker().onPageTurn(); requestUpdate(); } break; case BA::BTN_PAGE_FORWARD_10: currentPage = (currentPage + 10 < pageCount) ? currentPage + 10 : pageCount - 1; + globalReadingSessionTracker().onPageTurn(); requestUpdate(); break; case BA::BTN_PAGE_BACK_10: currentPage = (currentPage >= 10) ? currentPage - 10 : 0; + globalReadingSessionTracker().onPageTurn(); requestUpdate(); break; case BA::BTN_NEXT_SECTION: @@ -547,6 +551,7 @@ void XtcReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION a [this](const auto& ch) { return ch.startPage > currentPage; }); if (it != chapters.end()) { currentPage = it->startPage; + globalReadingSessionTracker().onPageTurn(); requestUpdate(); } } @@ -559,6 +564,7 @@ void XtcReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION a if (prevChapter != chapters.rend()) { currentPage = prevChapter->startPage; + globalReadingSessionTracker().onPageTurn(); requestUpdate(); } } diff --git a/src/activities/settings/ReadingStatsActivity.cpp b/src/activities/settings/ReadingStatsActivity.cpp index fb04aab0..4a02706f 100644 --- a/src/activities/settings/ReadingStatsActivity.cpp +++ b/src/activities/settings/ReadingStatsActivity.cpp @@ -3,10 +3,12 @@ #include // millis() #include #include +#include #include #include #include +#include #include #include @@ -178,7 +180,8 @@ void ReadingStatsActivity::render(RenderLock&&) { if (!store.getBooks().empty()) { std::vector sorted; sorted.reserve(store.getBooks().size()); - for (const auto& b : store.getBooks()) sorted.push_back(&b); + std::transform(store.getBooks().begin(), store.getBooks().end(), std::back_inserter(sorted), + [](const BookReadingStats& b) { return &b; }); std::sort(sorted.begin(), sorted.end(), [](const BookReadingStats* a, const BookReadingStats* b) { return a->totalSeconds > b->totalSeconds; }); @@ -199,7 +202,7 @@ void ReadingStatsActivity::render(RenderLock&&) { if (maxLabelWidth > 0 && renderer.getTextWidth(UI_10_FONT_ID, label.c_str()) > maxLabelWidth) { while (!label.empty() && renderer.getTextWidth(UI_10_FONT_ID, label.c_str()) + ellipsisWidth > maxLabelWidth) { - label.pop_back(); + utf8RemoveLastChar(label); } label += "…"; } diff --git a/src/activities/settings/ReadingStatsBookDetailActivity.cpp b/src/activities/settings/ReadingStatsBookDetailActivity.cpp index 56a1a0de..d067d287 100644 --- a/src/activities/settings/ReadingStatsBookDetailActivity.cpp +++ b/src/activities/settings/ReadingStatsBookDetailActivity.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -107,9 +108,19 @@ void ReadingStatsBookDetailActivity::render(RenderLock&&) { // Header — title (truncated) and author. Title fallback to docId so we // still produce a usable screen if the book's metadata was never recorded. std::string headerTitle = (book && !book->title.empty()) ? book->title : docId; - if (headerTitle.size() > 28) { - headerTitle.resize(28); - headerTitle += "…"; + { + constexpr size_t kMaxChars = 28; + const auto* p = reinterpret_cast(headerTitle.c_str()); + const auto* start = p; + size_t chars = 0; + while (*p != 0 && chars < kMaxChars) { + utf8NextCodepoint(&p); + ++chars; + } + if (*p != 0) { + headerTitle.resize(static_cast(p - start)); + headerTitle += "…"; + } } GUI.drawHeader(renderer, Rect{contentRect.x, contentRect.y + metrics.topPadding, contentRect.width, metrics.headerHeight}, diff --git a/src/activities/settings/ReadingStatsBookListActivity.cpp b/src/activities/settings/ReadingStatsBookListActivity.cpp index 6b6fa103..c5eae91e 100644 --- a/src/activities/settings/ReadingStatsBookListActivity.cpp +++ b/src/activities/settings/ReadingStatsBookListActivity.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "MappedInputManager.h" #include "ReadingStatsBookDetailActivity.h" @@ -35,9 +36,9 @@ std::string formatDuration(uint32_t totalSeconds) { void ReadingStatsBookListActivity::rebuildSortedBooks() { sortedBooks.clear(); - for (const auto& b : READING_STATS.getBooks()) { - sortedBooks.push_back(&b); - } + sortedBooks.reserve(READING_STATS.getBooks().size()); + std::transform(READING_STATS.getBooks().begin(), READING_STATS.getBooks().end(), std::back_inserter(sortedBooks), + [](const BookReadingStats& b) { return &b; }); std::sort(sortedBooks.begin(), sortedBooks.end(), [](const BookReadingStats* a, const BookReadingStats* b) { return a->totalSeconds > b->totalSeconds; }); } diff --git a/src/components/CardLayout.cpp b/src/components/CardLayout.cpp index af393a09..3641486f 100644 --- a/src/components/CardLayout.cpp +++ b/src/components/CardLayout.cpp @@ -7,7 +7,7 @@ #include "fontIds.h" -CardLayout::CardLayout(GfxRenderer& renderer, Rect contentRect, int startY, CardLayoutConfig cfg) +CardLayout::CardLayout(GfxRenderer& renderer, Rect contentRect, int startY, const CardLayoutConfig& cfg) : renderer_(renderer), contentRect_(contentRect), cfg_(cfg), y_(startY) { cardLeft_ = contentRect.x + cfg_.outerMarginX; cardWidth_ = contentRect.width - cfg_.outerMarginX * 2; diff --git a/src/components/CardLayout.h b/src/components/CardLayout.h index faba6aa2..6afb40aa 100644 --- a/src/components/CardLayout.h +++ b/src/components/CardLayout.h @@ -85,7 +85,7 @@ class CardLayout { void centeredMessage(const char* msg); }; - CardLayout(GfxRenderer& renderer, Rect contentRect, int startY, CardLayoutConfig cfg = {}); + CardLayout(GfxRenderer& renderer, Rect contentRect, int startY, const CardLayoutConfig& cfg = {}); // Render a single card. `bodyFn` receives a `Body&` and may call its // helpers in any order; the card auto-sizes to whatever the body draws. diff --git a/src/network/html/FilesPage.html b/src/network/html/FilesPage.html index 104236a0..9ce673ff 100644 --- a/src/network/html/FilesPage.html +++ b/src/network/html/FilesPage.html @@ -108,6 +108,7 @@ .nav-links { margin: 20px 0; display: flex; + flex-wrap: wrap; gap: 10px; } diff --git a/src/network/html/HomePage.html b/src/network/html/HomePage.html index 1735bdd2..c88ee4ea 100644 --- a/src/network/html/HomePage.html +++ b/src/network/html/HomePage.html @@ -113,6 +113,7 @@ .nav-links { margin: 20px 0; display: flex; + flex-wrap: wrap; gap: 10px; } diff --git a/src/network/html/SettingsPage.html b/src/network/html/SettingsPage.html index f807de8b..dfa0fcde 100644 --- a/src/network/html/SettingsPage.html +++ b/src/network/html/SettingsPage.html @@ -59,6 +59,7 @@ .nav-links { margin: 20px 0; display: flex; + flex-wrap: wrap; gap: 10px; } .nav-links a { diff --git a/src/network/html/StatsPage.html b/src/network/html/StatsPage.html index 0bbe7bcc..aa2c4043 100644 --- a/src/network/html/StatsPage.html +++ b/src/network/html/StatsPage.html @@ -310,8 +310,10 @@ // seconds. function formatEta(seconds) { if (!seconds || seconds <= 0) return "—"; - const h = Math.floor(seconds / 3600); - const m = Math.round((seconds % 3600) / 60); + let h = Math.floor(seconds / 3600); + let m = Math.round((seconds % 3600) / 60); + // Rounding can push m to 60 (e.g. 3570s → 0h 60m); carry into hours. + if (m === 60) { h += 1; m = 0; } if (h > 0) return `${h}h ${String(m).padStart(2, "0")}m`; if (m > 0) return `${m}m`; return "<1m";