critical priority medium complexity infrastructure pending frontend specialist Tier 1

Acceptance Criteria

MediaQuery.disableAnimations is read inside SummaryAccessibilityProvider on every widget rebuild; a change in system setting propagates within one frame
AccessibilityFeatures.reduceMotion is also checked as a secondary source; if either signal is true the provider exposes isReducedMotion = true
WrappedAnimationController receives the isReducedMotion flag and skips animation playback (duration = Duration.zero or immediate value jump) when the flag is true
Toggling system reduced-motion while the summary screen is open applies the change without requiring a hot restart or screen reload
SummaryAccessibilityProvider exposes a single bool getter isReducedMotion with no additional public surface that could leak internal state
Unit tests confirm the provider returns true when disableAnimations is true, when reduceMotion is true, and when both are true
Unit tests confirm the provider returns false when both signals are false
No animation frame is rendered after the controller receives isReducedMotion = true; value jumps directly to final state

Technical Requirements

frameworks
Flutter
flutter_test
apis
MediaQuery.disableAnimations
MediaQuery.accessibilityFeatures
AccessibilityFeatures.reduceMotion
performance requirements
Provider rebuild must complete within a single frame (< 16 ms at 60 fps)
No expensive computation inside build; boolean evaluation only
security requirements
No user preference data persisted to Supabase; reduced-motion is a purely local, session-scoped signal

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Read both MediaQuery.disableAnimations (the user-facing 'Reduce Motion' toggle) and MediaQuery.accessibilityFeatures.reduceMotion (platform-level signal available on iOS 13+ and Android 9+). Combine with a logical OR so either signal activates reduced-motion mode. Expose this as a single isReducedMotion bool from the provider rather than exposing both raw signals — this keeps consuming widgets decoupled from the detection mechanism. In Riverpod, implement as an InheritedWidget-backed provider that calls context.dependOnInheritedWidgetOfExactType so it rebuilds automatically when the system setting changes.

If using BLoC, dispatch a ReducedMotionChanged event from a top-level WidgetsBindingObserver and update state accordingly. Avoid storing this preference in Supabase or SharedPreferences; it must always reflect the live system state.

Testing Requirements

Write unit tests using flutter_test and ProviderContainer (Riverpod) or BlocTest (BLoC) to verify all four combinations of the two boolean inputs. Write widget tests that wrap a mock WrappedAnimationController inside a MediaQuery with disableAnimations: true and assert that the controller's duration is Duration.zero. Write a golden/integration smoke test toggling the accessibility setting during summary screen display and asserting no animation frames are emitted.

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.