VoiceOver and TalkBack announcements for all auth screens
epic-bankid-vipps-login-ui-task-011 — Implement screen-reader live region announcements for all five authentication screens. Each screen state transition (loading, error, success, waiting) must emit a Semantics announcement readable by VoiceOver (iOS) and TalkBack (Android). Focus must move to the primary action element on screen load. Error messages must be announced immediately without requiring user interaction. Use Flutter Semantics widgets and AccessibilityLiveRegion equivalent patterns.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 5 - 253 tasks
Can start after Tier 4 completes
Implementation Notes
Create a reusable LiveRegionAnnouncer widget that takes a String message and wraps it in Semantics(liveRegion: true, child: Text(message)). Whenever the message changes (driven by BLoC state), the screen reader automatically announces the new text. Use this for loading, error, and success states across all five screens. For initial screen title announcements use SemanticsService.announce(screenTitle, TextDirection.ltr) inside a postFrameCallback in the screen's State.initState().
For focus management, attach a FocusNode to the primary action widget and call focusNode.requestFocus() in a postFrameCallback after state changes that land on an actionable state. Pay particular attention to the Personnummer bottom sheet — it opens over an existing screen so its focus must be trapped within the sheet (use FocusScope). The Blindeforbundet requirement for 'warning on reading sensitive fields' is addressed here by adding Semantics(hint: 'This field contains sensitive personal information') to the masked personnummer display.
Testing Requirements
Widget tests using flutter_test: use tester.pumpAndSettle() after BLoC state emission, then query SemanticsNode tree with tester.getSemantics() to verify labels, hints, and liveRegion flags are set correctly. Test that the primary action widget has autofocus: true or that FocusNode.hasFocus returns true after pump. Test that error state widgets include Semantics(liveRegion: true). Manual testing checklist: enable VoiceOver on iOS Simulator (Cmd+F5), navigate each screen and verify all announcements are spoken correctly; repeat with TalkBack on Android Emulator (triple-tap volume).
Document manual test results in the sign-off checklist produced in task-013.
BankID on mobile uses a WebView or external app redirect that has known compatibility issues with Flutter's WebView package on certain Android versions. BankID's JavaScript-heavy broker pages may also trigger CSP or mixed-content errors in a Flutter WebView, preventing the authentication flow from completing.
Mitigation & Contingency
Mitigation: Use the flutter_inappwebview package (more mature than webview_flutter for complex OAuth pages) and validate BankID WebView rendering on the broker's test environment before integrating with the service layer. Prefer external browser redirect where the broker supports it.
Contingency: If WebView approach fails for certain BankID brokers, implement the full external browser redirect + deep link callback pattern as the primary flow and treat WebView as a fallback only.
The OAuth redirect flows (both Vipps and BankID) temporarily move the user outside the Flutter app into an external browser or the Vipps/BankID app. Screen reader users may lose focus context during this transition and become disoriented when the app callback returns them to the loading state, failing the WCAG 2.2 AA mandate.
Mitigation & Contingency
Mitigation: Implement explicit accessibility announcements (live region announcements) at each transition point: when launching the external flow ('Opening Vipps'), during the loading wait state ('Waiting for Vipps confirmation'), and on return ('Login successful' or 'Login failed — please try again'). Test with VoiceOver on iOS and TalkBack on Android during development.
Contingency: If OAuth transition accessibility is unresolvable on a specific platform, add an explicit accessibility user guide in the onboarding flow explaining the external app redirect behavior to set user expectations.
Biometric UI varies significantly across devices — Face ID (iPhone), fingerprint sensor (most Android), front-facing camera biometrics (some Android), and devices with no biometrics at all. Flutter's local_auth handles the OS dialog but the surrounding UI must gracefully handle all these cases, and testing coverage for all permutations is difficult.
Mitigation & Contingency
Mitigation: Use local_auth's getAvailableBiometrics() to detect the exact biometric type and render appropriate iconography (Face ID icon vs. fingerprint icon). For devices with no biometrics, skip the biometric screen entirely and route directly to full re-authentication.
Contingency: If a specific device configuration produces unexpected local_auth behavior in production, implement a user-accessible toggle in Settings to disable biometric login entirely, routing those users to the standard BankID/Vipps flow without biometrics.