reading_progress 0.0.1
reading_progress: ^0.0.1 copied to clipboard
A pure Dart engine for verified reading progress — confident-word milestones, active-dwell tracking, and reading-speed calibration.
reading_progress #
A pure Dart engine for verified reading progress. Feed it a book's word-density map and a stream of reading events; it tells you what counts as real reading, when a milestone is reached, and when a book is genuinely complete.
Why #
Time-on-page lies — a page left open for an hour is not an hour of reading. This engine:
- counts active engaged time from a presence heartbeat — periodic
Ticks plus interactions credit time only while the reader is present (within an idle cutoff of the last page-open/interaction), so silent reading counts but walking away does not; - gates page completion on engaged dwell vs an expected read time from a per-book calibrated reading speed (EMA, seeded from the Brysbaert 2019 meta-analysis: 238 wpm non-fiction / 260 fiction, 240 unknown);
- rejects skim-scrolling, and requires a forward page-turn to complete;
- counts progress in confident words, not pages — format-agnostic and honest.
Confident-word model #
Progress is measured in words actually read:
- a page contributes its words only once it passes the dwell gate (deduped — re-reading never double-counts);
- every
wordUnitSizeconfident words banks a point; milestoneThresholdUnitspoints reaches a milestone.
A skipped page contributes 0 words, so it just delays the next milestone — never blocks it. Book completion is strict: every content page must be read.
Architecture #
Functional core, imperative shell. Everything here is the pure core — a single reducer:
final out = reduce(state, event, deps); // (state', effects)
ReadingEvent—PageOpened,Interaction,Tick,PageExited,AppBackgrounded,ScreenOffProgressEffect—PageCompleted,PointBanked,MilestoneReached,BookCompleted- Swappable strategies —
ReadingSpeedModel,EngagementPolicy,CompletionPolicy
Partial per-page dwell is stashed and resumed across revisits — glancing ahead and returning never resets a page.
Usage #
import 'package:reading_progress/reading_progress.dart';
final deps = ReadingDeps(
density: const BookDensity(
pageWords: [120, 600, 950, 30, 1100],
skippablePages: {3},
genre: Genre.nonFiction,
),
);
var state = ReadingState.initial(deps);
void onEvent(ReadingEvent event) {
final out = reduce(state, event, deps);
state = out.state;
for (final effect in out.effects) {
switch (effect) {
case PointBanked(:final point, :final wordsRead):
print('point $point ($wordsRead words)');
case MilestoneReached(:final index, :final wordsRead):
print('milestone $index at $wordsRead words');
case BookCompleted():
print('done: ${(state.progressByWords(deps.density) * 100).round()}%');
case PageCompleted(:final page):
print('page $page verified');
}
}
}
The shell feeds real signals as events: PageOpened/PageExited on navigation, Interaction on tap/scroll, a Tick ~every 10s while the reader is foreground + screen-on, and AppBackgrounded/ScreenOff on lifecycle changes.
ReadingState is JSON-friendly value data (wordsRead, pointsBanked, milestonesReached, sessionEngagedMs, wpm, bitmaps, partials) — persist it however you like and rehydrate to resume. Derived getters: progressByWords(density), isCalibrating(config), totalMilestones(density, config).
Tuning #
Every threshold lives in ProgressConfig: wordUnitSize, milestoneThresholdUnits, calibrationUnits, idleCutoffMs, maxStepMs, k, alpha, WPM seeds + clamp, skim thresholds. The host passes its own — the engine never reads a literal:
ReadingDeps(
density: density,
config: const ProgressConfig(
wordUnitSize: 250,
milestoneThresholdUnits: 5,
calibrationUnits: 2,
k: 0.5,
),
);
For full algorithm control, supply custom ReadingSpeedModel / EngagementPolicy / CompletionPolicy implementations to ReadingDeps instead of the defaults.
Example #
See example/ for an interactive Flutter harness — buttons for reading silently (ticks), tapping, slow-scroll, skimming, idling, backgrounding, and page navigation, with live ReadingState (words read, points, milestones, calibrated wpm, progress) and an effect log.
License #
MIT — see LICENSE.