Capture library vs reader ETA behaviour, historical duplicate caps, tunable preferences, and code map from the reading-eta work. Link from README. Co-authored-by: Cursor <cursoragent@cursor.com>
6.1 KiB
Reading time estimates (ETA)
Komikku shows estimated reading time in two places:
| Surface | What it shows |
|---|---|
| Library | Time-left badge per entry (unread chapters × average chapter duration). |
| Reader | Series progress ETA (remaining time for the current series, including the active chapter). |
Both surfaces share the same limits and defaults via ReadingEtaPreferences → ReadingEtaLimits. Tunable in Settings → Reader → Time estimate tuning (SettingsReaderScreen, group getReadingEtaTuningGroup).
Very long chapters (one entry = one huge chapter)
Reading itself has no artificial page cap. The reader uses the real totalPages from the loaded chapter. If a source exposes an entire volume as a single chapter, that is one DB chapter with many pages — normal behaviour.
Limits apply only to statistics and ETA, not to loading or turning pages:
- Sessions shorter than ~2 s or longer than the configured max trusted session are ignored for speed stats.
- When averaging read times, each chapter sample counts at most max minutes per chapter sample (default 45 min) so an idle tab does not inflate estimates.
- History rows with more than max pages per history sample (default 2000) are skipped for page-rate priors (cross-series hints and ms/page blending).
For very long chapters, the reader ETA path that scales by remaining pages × ms/page can still show a long ETA if your real pace is slow — that is intentional. If estimates look too short, raise the stats/session caps in settings (e.g. 180 min sample cap, 1440 min trusted session, 5000+ pages sample).
Why there used to be two different logics
Before f178a7919, library and reader each duplicated constants in their ViewModels:
| Constant | Library (LibraryScreenModel) |
Reader (ReaderViewModel) |
|---|---|---|
| Max trusted session | 1 h | 4 h |
| Chapters sampled | 60 | 20 |
Same idea (trim outliers, cap per-chapter duration for averages), but different numbers → inconsistent badges vs reader ETA on the same manga.
The reader additionally had a page-rate path (ReadingEtaPageRateEstimator + cross-manga history); the library used chapter-duration averages only. That asymmetry is by design (library has no “current page”), but the shared caps are now unified.
Architecture (current)
flowchart TB
Prefs[ReadingEtaPreferences]
Limits[ReadingEtaLimits]
Lib[LibraryScreenModel]
Reader[ReaderViewModel]
Cross[GetCrossMangaPageRateHistorySamples]
Est[ReadingEtaPageRateEstimator]
Prefs --> Limits
Limits --> Lib
Limits --> Reader
Limits --> Cross
Cross --> Reader
Est --> Reader
Prefs -->|limitsFlow| Lib
Key types and files
| Piece | Path |
|---|---|
| Defaults (code constants) | domain/.../reading/ReadingEtaDefaults.kt |
| Effective limits snapshot | domain/.../reading/ReadingEtaLimits.kt |
| User preferences | domain/.../reading/service/ReadingEtaPreferences.kt |
| Page-rate blending | domain/.../reading/ReadingEtaPageRateEstimator.kt |
| Cross-series history samples | domain/.../history/interactor/GetCrossMangaPageRateHistorySamples.kt |
| Library ETA | app/.../library/LibraryScreenModel.kt |
| Reader series ETA | app/.../reader/ReaderViewModel.kt |
| Settings UI | app/.../settings/screen/SettingsReaderScreen.kt |
ReadingEtaPreferences is registered in PreferenceModule; GetCrossMangaPageRateHistorySamples takes it via Injekt (DomainModule).
User settings
Preference keys (defaults in parentheses):
| Setting | Key | Default | Slider range (UI) |
|---|---|---|---|
| Max minutes per chapter sample | pref_reading_eta_max_stats_min |
45 | 5–180 |
| Max trusted session length | pref_reading_eta_max_trusted_session_min |
240 (4 h) | 60–1440 |
| Chapters sampled for averages | pref_reading_eta_sample_chapter_limit |
60 | 5–100 |
| Max pages per history sample | pref_reading_eta_max_pages_sample |
2000 | 1000–50000 (slider 10–500 × 100) |
ReadingEtaLimits.fromRaw enforces:
- Trusted session ≥ stats cap (minutes).
- Sample chapter count ∈ [5, 200].
- Max pages ∈ [100, 50_000].
Strings: i18n-kmk/.../base/strings.xml and de/strings.xml (pref_reading_eta_*).
Reader-only toggles (unchanged): Show series ETA, Persist series ETA (ReaderPreferences).
How estimates are computed
Library badge
- Load per-manga chapter read durations from history.
- Filter to trusted session range; cap each sample at max chapter duration for stats.
- Take the last N chapters (
etaSampleChapterLimit), trim top/bottom 10% (when enough samples), average. - Multiply by unread count (or
1whentotalChapters == 1).
Falls back to a global average across the visible library when a title has no usable samples.
Reader series ETA
Two paths; the UI uses page-based ETA when available, else chapter-based:
- Page-based (
calculatePageBasedEtaMillis): ms/page from recent chapters in this series + optional blend with cross-manga priors (ReadingEtaPageRateEstimator). ProjectsremainingPages × ms/pageplus whole chapters ahead using average pages/chapter. - Chapter-based: average/projected chapter duration × remaining chapters, with projection from current session or progress.
Cross-manga priors: up to CROSS_MANGA_PAGE_RATE_SAMPLE_LIMIT (500) recent completed chapters from other series, preferring similar page counts (±15%, then ±30% band).
Release
Shipped in tag v1.13.6-forgejo.38 — commit feat(reading-eta): unify library and reader limits; make caps tunable.
Maintainer notes
- When changing defaults, update
ReadingEtaDefaults,ReadingEtaPreferencesdefault getters, and the settings slider ranges if needed. - Keep library and reader on
readingEtaPreferences.currentLimits()/limitsFlow()— do not reintroduce localMIN_TRACKED_*constants in ViewModels. - Page caps affect stats only; do not use them to limit
UpsertHistoryunless product intent changes.