# 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) ```mermaid 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 1. Load per-manga chapter read durations from history. 2. Filter to trusted session range; cap each sample at *max chapter duration for stats*. 3. Take the last *N* chapters (`etaSampleChapterLimit`), trim top/bottom 10% (when enough samples), average. 4. Multiply by unread count (or `1` when `totalChapters == 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**: 1. **Page-based** (`calculatePageBasedEtaMillis`): ms/page from recent chapters in this series + optional blend with cross-manga priors (`ReadingEtaPageRateEstimator`). Projects `remainingPages × ms/page` plus whole chapters ahead using average pages/chapter. 2. **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`, `ReadingEtaPreferences` default getters, and the settings slider ranges if needed. - Keep library and reader on `readingEtaPreferences.currentLimits()` / `limitsFlow()` — do not reintroduce local `MIN_TRACKED_*` constants in ViewModels. - Page caps affect **stats only**; do not use them to limit `UpsertHistory` unless product intent changes.