Implement Badge Earned Celebration Overlay
epic-achievement-badges-ui-task-014 — Build the badge-earned-celebration-overlay as a transient full-screen Flutter animation overlay triggered by badge-bloc badge-earned state. Auto-dismiss after animation completes. Implement Flutter Semantics announcement for screen readers (VoiceOver/TalkBack) announcing badge name as a live region. Ensure overlay is fully operable via assistive technology and meets WCAG 2.2 AA contrast for all text elements.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 3 - 413 tasks
Can start after Tier 2 completes
Implementation Notes
Implement overlay display using Navigator.of(context).overlay?.insert(OverlayEntry(...)) rather than pushing a new route — this preserves the navigation stack. Manage a queue (List
Call SemanticsService.announce() in initState or the BLoC listener callback, not inside build(). Wrap badge name text in a Semantics widget with liveRegion: true for persistent announcement. All animation durations should be defined as constants in a shared animations.dart file for easy tuning. Dispose AnimationController in dispose() using a try/finally guard.
Testing Requirements
Write widget tests (flutter_test): (1) overlay renders with correct badge name when bloc emits badge-earned, (2) overlay auto-dismisses after animation duration (use fake async / tester.pump(duration)), (3) overlay can be manually dismissed via close gesture, (4) Reduce Motion suppresses particle animation and uses fade instead, (5) queuing logic shows overlays sequentially for two rapid badge-earned events. Assert SemanticsService.announce() is called with the correct badge name using a test observer. Manual test on iOS with VoiceOver to confirm live region announcement fires without focus navigation. Integration test: confirm underlying route remains navigable after overlay dismiss.
Minimum 75% coverage for overlay widget.
The badge-earned-celebration overlay must appear within 2 seconds of the triggering activity being saved, but badge evaluation runs server-side in an edge function triggered by a database webhook. Network latency, edge function cold start, and Supabase Realtime delivery delays could cause the overlay to appear late or not at all, breaking the motivational loop.
Mitigation & Contingency
Mitigation: Implement an optimistic UI path: after activity save, badge-bloc immediately checks whether any badge thresholds are crossed client-side using cached stats and badge definitions, showing the overlay speculatively before server confirmation. The server result then reconciles. Subscribe to Supabase Realtime on the earned_badges table for authoritative confirmation.
Contingency: If Realtime delivery is unreliable in production, add a polling fallback: badge-bloc polls for new earned badges 3 seconds after an activity save and shows the overlay if a new record is detected, accepting up to 5-second latency as a fallback SLA.
The celebration overlay uses animation for positive reinforcement, but motion sensitivity (prefers-reduced-motion) and screen reader users require a non-animated or text-only alternative. Failing to handle this risks excluding Blindeforbundet users or triggering vestibular discomfort for motion-sensitive volunteers.
Mitigation & Contingency
Mitigation: Check MediaQuery.disableAnimations in badge-earned-celebration-overlay and skip animation entirely when true, showing a static card instead. Add an ExcludeSemantics wrapper around the decorative animation widget and a separate Semantics node with a live region announcement of the badge name and congratulatory message.
Contingency: If accessibility issues are identified in TestFlight testing with Blindeforbundet's test group, fast-track a patch that defaults to the static card path and gates the animation behind a user preference setting in notification preferences.