Skip to content

Stand the hero's button under its progress track, and stretch the cover to the card's height - #117

Merged
rbioteau merged 1 commit into
mainfrom
home-hero-cover-and-button
Sep 19, 2026
Merged

rbioteau merged 1 commit into
mainfrom
home-hero-cover-and-button

Conversation

@rbioteau

Copy link
Copy Markdown
Owner

The Continue card drew its button in a centred row of its own under everything, and its cover at a fixed 92x138. On a tablet the button had a card's worth of nothing above it and nothing left to be centred against; on a phone it was the one thing on the screen lined up with nothing, and the cover stopped short of the bottom of the words beside it (194pt deep), which reads as a cover that failed to load.

The button follows its progress track. It is drawn inside the column of words, directly under the bar and at the bar's own width and left edge: controlMaxWidth (280) where the column is wider, the column itself where it is narrower (246 on a 390pt phone). No size-conditional placement — the two agree by construction, which is what _Details taking the button as a widget is for.

The cover takes the height of those words, its width following by the 2:3 ratio — 92x138 becomes 129x194 on a 390pt phone — and never shrinks below the cover a card is drawn with, which leaves a tablet exactly where it was: there the words are the shorter of the two, so the cover is its own 160x240.

Two traps, both written into the code:

  • The words' height may not depend on their width. The row is an IntrinsicHeight, which measures the column at the band's full width before the cover takes its share back; a title that fits one line there and needs two where it is drawn leaves the card 26pt shorter than its own content — a RenderFlex overflow, and what the first attempt at this did for any title long enough to wrap. The title's block is therefore two lines of the title's own style, its height asked of the text engine rather than computed from the style, with a one-line title at the bottom of the block so it stays joined to the chapter named under it.
  • A picture may not answer the question. A decoded image reports its own pixels as its intrinsic size: a 1500px cover made the band 600pt tall the moment its picture landed in the image cache — and not before, which is a bug no widget test with an unloaded image can see. lib/src/widgets/no_intrinsic.dart is the box that keeps a picture's pixels out of a layout's height.

Tests. test/home_hero_test.dart pins the button's placement on both sizes, the fill (flush with the band's padding, width = height × 2/3, wider than 92) and that a long title does not make the card taller. test/no_intrinsic_test.dart loads a real decoded picture into an IntrinsicHeight row and asserts the row is the words' height, not the file's — the guard is only exercised once an image is really there.

flutter analyze clean, flutter test 922/922, verified by rendering the card at 320 / 390 / 820pt (throwaway goldens, not committed).

…er to the card's height

The Continue card drew its button in a row of its own under everything,
centred, capped at 280. On a tablet that left a card's worth of nothing above
the button and nothing left to be centred against; on a phone it was the one
thing on the screen lined up with nothing at all, and the cover — 92x138
beside a column of words that is 194 deep — stopped short of the bottom of
them, which reads as a cover that failed to load.

The button now follows the track it belongs to: drawn inside the column of
words, directly under the bar, at the bar's own width and left edge. It is
`controlMaxWidth` where the column is wider than that and the column itself
where it is narrower (246 on a 390pt phone), so the two agree on both sizes,
and `_Details` takes it as a widget rather than building it, because the
button was already one widget only by agreement between two call sites.

The cover takes the height of the words beside it, its width following from
that by the 2:3 ratio — 129x194 on a phone — and never shrinking below the
cover a card is drawn with, which is what keeps a tablet exactly where it was:
there the words are the shorter of the two, so the cover is its own 160x240.

Two things make that work and neither is obvious, so both are written where
they bite. **The words' height may not depend on their width.** The row is an
`IntrinsicHeight`, which measures the column at the band's full width before
the cover takes its share back, and a title that fits one line there and needs
two where it is drawn leaves the card 26pt shorter than its own content —
which is a RenderFlex overflow, and it is what the first attempt at this did,
for any title long enough to wrap. The title's block is therefore two lines of
the title's own style whatever the title says, its height asked of the text
engine rather than computed from the style, with a one-line title at the
bottom of the block so it stays joined to the chapter named under it; the
pages-left line is pinned to one line for the same reason. **And a picture may
not answer the question at all.** A decoded image reports its own pixels as
its intrinsic size, so a 1500px cover made the band 600pt tall the moment its
picture landed in the image cache — and not before, which is a bug no widget
test with an unloaded image can see. `NoIntrinsic` is what keeps a picture's
pixels out of the card's height.

Four tests hold it. The hero's tablet group pins both placements, the fill
(flush with the band's padding, width = height * 2/3) and that a long title
does not make the card taller, which is the overflow above. `NoIntrinsic`
carries its own test with a real decoded picture in an `IntrinsicHeight` row,
because the trap it closes only exists once a picture is loaded: the row is
the words' height, not the file's.
@rbioteau
rbioteau merged commit 6f29ff8 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