critical priority medium complexity infrastructure pending frontend specialist Tier 0

Acceptance Criteria

A SummaryAccessibilityProvider abstract class or interface is defined in a dedicated file (e.g. lib/features/wrapped/accessibility/summary_accessibility_provider.dart) with all public methods and properties documented via dartdoc comments
The interface exposes a bool reduceMotion property that returns true when the system's disableAnimations MediaQuery flag is true OR when the device's AnimationMode accessibility setting requests reduced motion
The interface exposes a bool isScreenReaderActive property that returns true when MediaQuery.accessibleNavigation is true (covers both VoiceOver and TalkBack)
The interface exposes a String announceSlide(SlideType type, SlideSummaryData data) method that returns a fully formed, screen-reader-ready announcement string for each slide type — covering all slide variants defined in the epic
The interface exposes a String announceProgress(int current, int total) method returning a localised string such as 'Slide 3 of 7'
The interface exposes a String announceShareButton() and String announceCloseButton() methods returning localised accessible labels
A concrete SummaryAccessibilityProviderImpl implementation class is provided that reads from MediaQuery and optionally from the accessibility_preferences data model for user-overridden contrast and font scale settings
A Riverpod provider (summaryAccessibilityProvider) is defined that exposes SummaryAccessibilityProvider and can be overridden in tests via ProviderScope
The provider is auto-disposed and does not hold stale MediaQuery values — it reads fresh values on each build via a ref.watch(mediaQueryProvider) or equivalent reactive pattern
All announcement strings are in English by default and are structured to be internationalisation-ready (i.e. generated via a method that accepts locale-specific formatting, even if only English is implemented initially)

Technical Requirements

frameworks
Riverpod (Provider or StateNotifierProvider for summaryAccessibilityProvider with ProviderScope override support)
Flutter Semantics API (MediaQuery.accessibleNavigation, MediaQuery.disableAnimations for platform signal reading)
Dart abstract class / interface pattern (no third-party DI framework — use Riverpod consistently)
apis
MediaQuery Flutter API (accessibleNavigation, disableAnimations, textScaleFactor — platform accessibility signals)
No external APIs required for this infrastructure definition task
data models
accessibility_preferences (user_id, font_scale_factor, contrast_mode, haptic_feedback_enabled — read by SummaryAccessibilityProviderImpl to overlay user preferences on top of system defaults)
annual_summary (SlideSummaryData type derived from annual_summary fields: activity_count, total_hours, year, period_type — used as input to announceSlide())
performance requirements
Provider reads are O(1) — no computation or database access on every read; computed values cached and invalidated only when MediaQuery or accessibility_preferences change
announceSlide() string generation completes in under 1 ms — pure string interpolation, no async operations
security requirements
No PII is included in accessibility announcement strings — announcements use only aggregated statistics (counts, hours, percentages) never names or contact data
accessibility_preferences are fetched using the authenticated user's scoped Supabase query (RLS enforced) — the provider must not access another user's preferences
ui components
No UI components rendered by this provider — it is a pure infrastructure/service layer
All Wrapped UI components (slide widgets, progress indicator, share button, close button) will consume this provider via ref.watch(summaryAccessibilityProvider)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

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 that constructs SummaryAccessibilityProviderImpl and passes it the BuildContext-derived MediaQueryData via a mediaQueryProvider ref.watch — this ensures the provider rebuilds when the user changes their device accessibility settings at runtime. For the SlideType enum, coordinate with the task that defines slide types (likely in the UI components epic) — use a placeholder enum in this task if slide types are not yet defined, and update when they are.

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.

Component
Summary Accessibility Provider
infrastructure medium
Epic Risks (2)
medium impact medium prob technical

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.

medium impact low prob integration

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.