The profile header's banner, avatar, and DID drifted to different
positions on different devices. SliverAppBar.expandedHeight was
hardcoded to 220 while the header content started below a SafeArea whose
top inset varies per device (~20pt on older iPhones, ~59pt on Dynamic
Island models), so the total height and the content's starting point
disagreed: the identity block landed lower and was clipped by a
different amount on every screen.
Rebuild the header around explicit Positioned layers whose geometry is
derived from the device inset and text scale, so the banner's bottom
edge always sits a fixed distance below the collapsed app bar and the
avatar always straddles that edge by exactly half its height.
Note that SliverAppBar computes maxExtent as topPadding + expandedHeight,
so expandedHeightFor() deliberately excludes the status-bar inset —
including it counts the inset twice and makes the banner grow at twice
the inset's rate.
Changes:
- Derive header geometry from MediaQuery insets and text scale via
ProfileHeader.expandedHeightFor / maxExtentFor / collapsedExtentFor
- Replace the SafeArea + UnconstrainedBox flow layout with explicit
Positioned layers: banner, scrim, avatar, identity
- Move bio, stats, and join date into a new ProfileDetails sliver so
they are never clipped by the collapsing header, dropping the 2-line
bio truncation that the clipping required
- Set the banner overhang to avatarSize / 2 so the avatar cannot rise
above the collapsed app bar and show through the frosted overlay
- Compute collapse progress from the sliver's real max extent, so the
frosted blur no longer stalls for the first topPadding px of scroll
- Replace the per-platform iOS/Android gradient hacks with one scrim
- Make the DID tappable to copy, since it ellipsizes on narrow screens
- Add profile_header_test.dart pinning the geometry at insets of
0/20/44/59pt; 3 of its 5 tests fail against the previous layout
Verified with flutter analyze, the full test suite (948 passing), and
pixel measurements on the Android emulator at two status-bar insets:
the banner edge now tracks the inset at 1x (63px between a 24dp and
48dp inset) rather than 2x (126px).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>