Skip to content

Let the reader pause a download, and draw its progress as a ring - #115

Merged
rbioteau merged 1 commit into
mainfrom
feat/download-controls
Sep 19, 2026
Merged

rbioteau merged 1 commit into
mainfrom
feat/download-controls

Conversation

@rbioteau

Copy link
Copy Markdown
Owner

What this is

The pill has never had a control a reader could use deliberately: the only way to stop a download was to tap a percentage, which silently cancelled and threw the pages away. Now that the queue resumes from the pages it kept, a copy on its way offers Pause, a copy that stopped offers Resume / Retry, and a copy that is here is a mark that cannot delete anything — removal is the row's trailing swipe, which now arms for a copy that stopped on its way as well as one that is here.

state ring glyph word row's own line
nobody asked — save Save —
queued its pages pause Waiting —
downloading, no page count yet turns pause Preparing —
downloading fills pause Downloading —
paused / pausedByUser frozen play Resume Paused
failed / interrupted frozen refresh Retry which stop it was
saved — check — —

The three decisions worth a reviewer's eye

  1. A pause the reader makes is a state of its own (pausedByUser). The app's paused is resumed without being asked whenever the app comes back to the foreground or a profile is entered, so a reader's pause written as the same value would be undone by the next app switch. Nothing but a tap resumes theirs; the launch strip does not count it; "not now" leaves it alone.
  2. A copy's progress is a ring; a bar on a cover is reading progress. CoverProgressBar pins the reading bar to a cover's bottom edge, so the download's own progress is a ring wherever it is drawn — inside the pill, around the glyph — and the two can never be read for one another on a row that carries both.
  3. A copy that is on the device belongs to the shelf, whatever its record is doing. A refresh runs with the copy it is replacing intact, so its record is both saved and in flight; that chapter was drawn twice, once in the shelf and once among the copies on their way, and a copy the reader can already read was announced as something that stopped. The tab's sections and the launch strip now list the same set.

Where it sits

Inside #97's area, and not the same thing as #102: that ticket removes the pill for a press-and-hold gesture and is blocked by #98. This keeps the pill and gives it the controls the queue now supports. #102's two rules that can be honoured today are honoured — the saved mark is not tappable, and a saved copy is removed from the trailing pane, which still confirms.

Verification

flutter analyze clean; 919 tests pass (flutter test), nine of them new. Two are the argument and each fails without its fix: a reader's pause survives the app going away, and a launch does not ask about a refresh that stopped. One more pins that the tab's rows fit a 390pt phone — it loads the real face, because the test environment's own font is twice as wide as Space Grotesk and would otherwise fail a width no phone has.

Not verified: the pixels. There is no image test in this repo, and the pill's ring around its glyph wants an eye on flutter run -d linux.

Known consequences, deliberately left

  • The same bin deletes a partial in one tap and asks before deleting a saved copy.
  • _RefreshCopy still wears a fill, where the pill no longer does: it appears only on a copy the server has stopped agreeing with, where a bare word would not be noticed.
  • savedChapterIdsProvider hands out a fresh List on every state change, so the shelf rebuilds more than it needs to — pre-existing, untouched here.

A pause was the app's own thing until now: `_pause` writes `paused` when the
app leaves the foreground or a profile is left, and `_resumePaused` sends every
one of those on again the moment the app comes back — so a pill that wrote the
same value would have had its pause undone by the next app switch. A pause the
reader makes is written as `pausedByUser`, and nothing but a tap resumes it:
`_pause` takes only what is in flight, the launch hold is filled from records
that carry neither a saved chapter nor a reader's pause, and "not now" leaves it
alone. The pause takes effect by cancelling the request, so `_run`'s catch now
reads the status it finds before it decides — without that, a copy the reader
stopped came back as `interrupted`, in danger, offering a retry.

The pill carries the state in words and the action in its glyph — pause,
resume, retry — with the copy's own progress drawn as a ring around it. The
percentage is gone, and so are the two blue bars: a bar on a cover is reading
progress (`CoverProgressBar`), and a copy's own progress is a ring wherever it
is drawn, so the two are never read for one another even on a row that carries
both. The ring has three drawings, and the one worth naming is the one that
turns while the request is out and the page count has not come back — a ring
stuck at zero for those seconds said the fetch had stalled. What the pill no
longer does is delete: a saved copy is a mark, and removal is the row's
trailing swipe, which now arms for a copy that stopped on its way as well as
one that is here.

The tab draws one row for every copy that is not on the device, under three
headings — the ones on their way, Paused in the offline blue, and Needs
attention in danger — and the shelf closes it under a heading of its own,
because a section that is not a state read as belonging to the one above it.
A copy that *is* on the device belongs to the shelf whatever its record is
doing: a refresh runs with the copy it is replacing still intact, and that
chapter was drawn twice, once in the shelf and once among the copies on their
way. The launch question follows the same rule, so the strip counts what the
tab lists and no more.

Nine tests hold it. Two of them are the argument: a reader's pause survives the
app going away — which fails if the two pauses are one value — and a launch
does not ask about a refresh that stopped. The rest pin the ring each state
draws, the copy that is on the device being listed once, under Saved, and the
tab's rows fitting a 390pt phone, which loads the real face because the test
environment's own font is twice as wide as Space Grotesk and would fail a
width no phone has.
@rbioteau
rbioteau merged commit 819b8ca into main Sep 19, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant