Define accessibility provider interface and reduced-motion API
epic-annual-impact-summary-ui-components-task-001 — Define the SummaryAccessibilityProvider interface including the contract for generating semantically complete announcement strings for each widget type and the API for detecting and exposing the system reduced-motion preference. Establish the Riverpod provider structure so all widgets can consume it consistently.
Acceptance Criteria
Technical Requirements
Implementation Notes
Define SummaryAccessibilityProvider as a Dart abstract class (not just an interface) so it can be implemented or extended by both the real implementation and test mocks. Place the file in a feature-scoped directory (lib/features/wrapped/accessibility/) to avoid coupling with global accessibility infrastructure. For the Riverpod provider, use a Provider
The announceSlide() method should follow the pattern: for a statistics slide, produce 'You completed {count} activities totalling {hours} hours in {year}' — write these strings as constants or a switch expression for easy future localisation via Flutter's l10n/arb system. Document the provider's Riverpod family variant (if per-locale or per-user overrides are needed) in a code comment, even if not implemented yet, to guide future developers.
Testing Requirements
Write unit tests for SummaryAccessibilityProviderImpl covering: reduceMotion returns true when MediaQuery.disableAnimations is true; isScreenReaderActive returns true when MediaQuery.accessibleNavigation is true; announceSlide() returns correctly formatted strings for each supported SlideType with sample SlideSummaryData; announceProgress() returns 'Slide 1 of 5', 'Slide 5 of 5' for boundary inputs; announceShareButton() and announceCloseButton() return non-empty strings. Write Riverpod container tests asserting that the summaryAccessibilityProvider can be overridden via ProviderScope and that the override value is returned to consumers. Achieve 100% branch coverage on the concrete implementation class. No integration or widget tests required for this task — the interface and provider are tested in isolation.
Simultaneous count-up animations across multiple stat cards and chart draw-in animations on lower-end Android devices may cause frame drops below 60fps, degrading the premium Wrapped experience and making the feature feel unpolished.
Mitigation & Contingency
Mitigation: Stagger animation starts using AnimationController with staggered intervals rather than starting all animations simultaneously. Use RepaintBoundary around each animated widget to isolate rasterisation. Profile on a mid-range Android device (e.g., equivalent to Pixel 4a) during development, not just at QA.
Contingency: If frame rate targets cannot be met on low-end devices, implement a device-capability check at startup and substitute simpler fade-in animations for the count-up and chart draw-in on devices below a CPU performance threshold.
The activity-type-breakdown-widget must render organisation-specific activity type labels sourced from the terminology system. If the terminology provider is not yet integrated at the time this widget is built, the widget will display hardcoded system labels, which is a regression risk for multi-org support.
Mitigation & Contingency
Mitigation: Accept activity type labels as a typed parameter in the widget constructor rather than reading from the terminology provider directly inside the widget. The BLoC or repository layer resolves labels before passing them to the widget, maintaining clean separation and testability.
Contingency: If terminology resolution is unavailable at widget integration time, display internal activity type keys as a temporary fallback with a localised suffix '(label pending)' visible only in non-production builds so QA can identify unresolved labels.