Implement dynamic text scaling layout
epic-no-access-screen-ui-task-010 — Test and fix the no-access-screen-widget layout at system font scale factors 1.0, 1.5, 2.0, and 3.2 (accessibility large text). Use flexible layout primitives (Expanded, FittedBox, SingleChildScrollView) so the layout does not overflow or clip at any scale. Ensure the vertical scroll container allows scrolling if content overflows at largest scales.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
The most common failure mode for this widget will be a Column inside a fixed-height container — remove any fixed height constraints from the Column's parent and replace with SingleChildScrollView > Column(mainAxisSize: MainAxisSize.min). For the logo, use Image.asset with a BoxFit.contain inside a ConstrainedBox(constraints: BoxConstraints(maxHeight: 80)) so it shrinks proportionally but never grows to fill available space at large text sizes. For body text, avoid wrapping Text in a SizedBox with a fixed height — let the Text widget self-size. The denial message paragraph may need a flexible container: use Padding rather than SizedBox for spacing between elements.
Test with MediaQuery.textScalerOf override in tests rather than setting device font size, for deterministic results. The 3.2 scale factor maps to the Android 'Largest' font size setting and the iOS 'AX5' Dynamic Type size — these are real user settings and must work correctly per the workshop requirements for users with cognitive and visual accessibility needs.
Testing Requirements
Write parameterised widget tests using @TestVariants or a loop over textScaleFactors [1.0, 1.5, 2.0, 3.2]. For each scale: (1) wrap the widget with MediaQuery(data: MediaQueryData(textScaler: TextScaler.linear(scale)), child: widget), (2) pump and settle, (3) assert no RenderFlex overflow exceptions via expectLater with throwsA matcher negation, (4) assert that find.byKey(Key('no-access-logout-button')) exists in the widget tree (not clipped). Also run the AccessibilityGuideline checks at scale 3.2 specifically, as this is the most likely failure point.
Flutter's live region (SemanticsProperties.liveRegion) announcement may be delayed or swallowed by the OS accessibility engine if the Semantics tree is not fully built when the screen mounts, causing screen-reader users to miss the denial announcement.
Mitigation & Contingency
Mitigation: Trigger the live region announcement from a post-frame callback (WidgetsBinding.addPostFrameCallback) to ensure the Semantics tree is committed before the announcement fires. Test on both VoiceOver (iOS) and TalkBack (Android) physical devices.
Contingency: If live region timing is unreliable, fall back to using SemanticsService.announce() directly in the initState post-frame callback, which provides more deterministic announcement timing.
The organisation logo may fail to load (network error, missing asset) leaving a broken image in an otherwise functional screen, degrading the professional appearance and potentially confusing users.
Mitigation & Contingency
Mitigation: Wrap the logo widget in an error builder that renders a styled fallback (organisation name text or a generic icon) when the logo asset or network image fails to load.
Contingency: If logo loading is persistently unreliable across organisations, remove the logo from the no-access screen entirely in favour of a text-only header using the organisation's display name from the design token system.