Phase 6 - remaining time info
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user