Phase 6 - remaining time info

This commit is contained in:
jpirnay
2026-05-20 09:55:49 +02:00
parent b4738fee0c
commit 375c73b03b
16 changed files with 216 additions and 14 deletions
@@ -505,6 +505,9 @@ void EpubReaderActivity::loop() {
requestUpdate();
return;
}
// User confirmed they're done with this book — credit a finish
// to the in-flight session before any tear-down side effects.
globalReadingSessionTracker().markFinished();
const auto& menuResult = std::get<MenuResult>(result.data);
if (menuResult.action == static_cast<int>(BookFinished::FinishedBookAction::GoHome)) {
if (SETTINGS.moveFinishedBooksToCompleted) {
@@ -765,6 +768,7 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
requestUpdate();
return;
}
globalReadingSessionTracker().markFinished();
const auto& menuResult = std::get<MenuResult>(result.data);
if (menuResult.action == static_cast<int>(BookFinished::FinishedBookAction::GoHome)) {
if (SETTINGS.moveFinishedBooksToCompleted) {
@@ -1618,6 +1622,7 @@ void EpubReaderActivity::render(RenderLock&& lock) {
requestUpdate();
return;
}
globalReadingSessionTracker().markFinished();
const auto& menuResult = std::get<MenuResult>(result.data);
if (menuResult.action == static_cast<int>(BookFinished::FinishedBookAction::GoHome)) {
if (SETTINGS.moveFinishedBooksToCompleted) {
@@ -302,6 +302,7 @@ void MdReaderActivity::loop() {
requestUpdate();
return;
}
globalReadingSessionTracker().markFinished();
const auto& menuResult = std::get<MenuResult>(result.data);
if (menuResult.action == static_cast<int>(BookFinished::FinishedBookAction::GoHome)) {
if (SETTINGS.moveFinishedBooksToCompleted) {
@@ -257,6 +257,7 @@ void TxtReaderActivity::launchFinishedBookFlow() {
requestUpdate();
return;
}
globalReadingSessionTracker().markFinished();
const auto& menuResult = std::get<MenuResult>(result.data);
if (menuResult.action == static_cast<int>(BookFinished::FinishedBookAction::GoHome)) {
if (SETTINGS.moveFinishedBooksToCompleted) {
@@ -156,6 +156,7 @@ void XtcReaderActivity::loop() {
requestUpdate();
return;
}
globalReadingSessionTracker().markFinished();
const auto& menuResult = std::get<MenuResult>(result.data);
if (menuResult.action == static_cast<int>(BookFinished::FinishedBookAction::GoHome)) {
if (SETTINGS.moveFinishedBooksToCompleted) {
@@ -129,6 +129,11 @@ void ReadingStatsActivity::render(RenderLock&&) {
b.rowLR(tr(STR_READING_STATS_PAGES), std::to_string(store.getGlobalTotalPagesTurned()));
b.rowLR(tr(STR_READING_STATS_PAGES_PER_MIN),
formatPagesPerMin(store.getGlobalTotalPagesTurned(), store.getGlobalTotalSeconds()));
// Only show the finished row once the user has actually finished a book —
// otherwise it's just clutter saying "0".
if (store.getFinishedBookCount() > 0) {
b.rowLR(tr(STR_READING_STATS_FINISHED), std::to_string(store.getFinishedBookCount()));
}
});
// ---- 30-day sparkline card ----
@@ -143,12 +143,32 @@ void ReadingStatsBookDetailActivity::render(RenderLock&&) {
layout.card(tr(STR_READING_STATS_TOTAL_TIME), [&](CardLayout::Body& b) {
b.rowLR(tr(STR_READING_STATS_TOTAL_TIME), formatDuration(book->totalSeconds));
b.rowLR(tr(STR_READING_STATS_PAGES_PER_MIN), formatPagesPerMin(book->pagesTurned, book->totalSeconds));
// Time-to-finish estimate based on the user's pace. We display it on the
// detail screen so the reader sees the projection even when not actively
// reading the book; the reader's status bar can pick this up live.
if (book->progress < 100) {
const float remainingPercent = 100.0f - static_cast<float>(book->progress);
const uint32_t etaSeconds = store.estimateRemainingSeconds(book->docId, remainingPercent);
b.rowLR(tr(STR_READING_STATS_ETA),
etaSeconds > 0 ? formatDuration(etaSeconds) : std::string(tr(STR_READING_STATS_UNKNOWN)));
}
});
// ---- History card ----
layout.card(tr(STR_READING_STATS_HISTORY), [&](CardLayout::Body& b) {
b.rowLR(tr(STR_READING_STATS_FIRST_READ), formatDateOrRelative(book->firstReadEpoch));
b.rowLR(tr(STR_READING_STATS_LAST_READ), formatDateOrRelative(book->lastReadEpoch));
if (book->finishedCount > 0) {
// Pretty-print as just the date for a single finish, or "Nx — date"
// for re-reads so the count and recency are both visible.
std::string val = formatDateOrRelative(book->lastFinishedEpoch);
if (book->finishedCount > 1) {
char buf[16];
snprintf(buf, sizeof(buf), "%ux — ", book->finishedCount);
val = std::string(buf) + val;
}
b.rowLR(tr(STR_READING_STATS_FINISHED), val);
}
});
// ---- Per-book sparkline (only when clock-anchored data exists) ----
@@ -94,8 +94,11 @@ void ReadingStatsBookListActivity::render(RenderLock&&) {
[this](int index) {
const auto* b = sortedBooks[index];
// Title is the primary label; fall back to docId so a row without
// metadata is still recognizable.
return b->title.empty() ? b->docId : b->title;
// metadata is still recognizable. Finished books get a leading
// checkmark so the user can spot completions at a glance.
std::string label = b->title.empty() ? b->docId : b->title;
if (b->finishedCount > 0) label = "" + label;
return label;
},
[this](int index) {
// Subtitle row: author, when known. Empty string is treated by the