Add WCAG 2.2 AA live region to RecordCountBanner
epic-bufdir-report-period-selection-ui-integration-task-006 — Wrap RecordCountBanner content in a Semantics widget configured as a live region (liveRegion: true) so screen readers announce count updates automatically. Add a polite announcement for the incomplete-period warning. Verify announcements fire correctly with VoiceOver and TalkBack. Ensure contrast ratios meet WCAG 2.2 AA.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 4 - 323 tasks
Can start after Tier 3 completes
Handles integration between different epics or system components. Requires coordination across multiple development streams.
Implementation Notes
In Flutter, live regions are implemented via the Semantics widget with `liveRegion: true`. When the child of a live region widget changes, the accessibility framework automatically announces the new semanticsLabel to the screen reader. Structure the code as: `Semantics(liveRegion: true, label: _buildCountLabel(state), child: _buildCountDisplay(state))`. Use a private method `_buildCountLabel` that returns the full descriptive string — this keeps the announcement logic separate from the visual rendering.
For the loading state, wrap the spinner in `Semantics(liveRegion: true, label: 'Loading activity count', child: CircularProgressIndicator(...))`. Use `ExcludeSemantics` on purely decorative icons within the banner to prevent duplicate announcements. Important: on Android, TalkBack may not honor liveRegion for widgets that are not in the current focus path — test carefully and consider using `SemanticsService.announce()` as a fallback if TalkBack does not announce automatically. This is critical for Blindeforbundet users who rely on screen readers as their primary interface.
Testing Requirements
Widget tests using flutter_test with tester.ensureSemantics(). Required test cases: (1) live region flag — find the semantics node containing the count text, verify liveRegion: true is set; (2) label format — verify semanticsLabel contains both the count number and 'activities' context string; (3) warning live region — pump widget with showIncompleteWarning: true, find semantics node for warning, verify liveRegion: true; (4) loading label — pump with isCountLoading: true, verify semantics label reads 'Loading activity count'; (5) contrast check — document the design token color values used and include a comment in the test file verifying the contrast ratio was checked manually against WCAG 2.2 AA. Manual testing checklist must be completed on iOS (VoiceOver) and Android (TalkBack) before task is marked done.
The record count query is asynchronous and may take up to 1 second on a slow connection. If the BLoC does not manage loading states carefully, the UI may show a stale count or the confirm button may be briefly enabled during the loading transition, allowing premature submission.
Mitigation & Contingency
Mitigation: Define explicit BLoC states: PeriodSelectionLoading, PeriodSelectionCountLoaded, PeriodSelectionValidationError, and PeriodSelectionReady. The confirm button must only be enabled in PeriodSelectionReady. Add a debounce of 300ms on period-change events before triggering the count query to prevent excessive calls.
Contingency: If debouncing is insufficient to prevent UX degradation on slow connections, add an optimistic loading skeleton to the RecordCountBanner that clearly communicates a pending state, and keep the confirm button disabled until the count resolves.
The confirmedReportPeriodProvider Riverpod contract between this feature and the Bufdir Export feature may not be defined yet, causing integration failures when the downstream export screen attempts to read the provider.
Mitigation & Contingency
Mitigation: Define the confirmedReportPeriodProvider as a StateProvider<DateTimeRange?> in a shared providers file at the start of this epic, before any screen code is written. Communicate the provider contract to the Bufdir Export feature team so both sides align on the same provider reference.
Contingency: If the export feature consumes the period through a different mechanism (e.g., navigation arguments), add a thin adapter that writes the confirmed period to both the Riverpod provider and the navigation argument, ensuring backward compatibility.
Flutter's live region support for screen readers is inconsistent across platforms (iOS VoiceOver vs Android TalkBack), and the record count banner must be announced on every change — a pattern that has historically required platform-channel workarounds.
Mitigation & Contingency
Mitigation: Test the live region announcement on both iOS (VoiceOver) and Android (TalkBack) early in implementation using the accessibility_test harness. Reference the project's existing live-region-announcer component (664-accessibility-live-region-announcer) for a proven implementation pattern.
Contingency: If native live region support is insufficient, wrap the record count text in a Semantics widget with a programmatically updated label string, and trigger a SemanticsService.announce call on each count update as a platform-agnostic fallback.