Implement Badge Detail Modal
epic-achievement-badges-ui-task-011 — Build the badge-detail-modal Flutter bottom sheet UI component that displays full badge criteria text, earned timestamp for earned badges, and progress toward criteria for locked badges. Reuse badge-card-widget for the header. Ensure accessible focus management on open/close, WCAG 2.2 AA contrast, and full VoiceOver/TalkBack operability.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 3 - 413 tasks
Can start after Tier 2 completes
Implementation Notes
Use showModalBottomSheet with isScrollControlled: true and a DraggableScrollableSheet for content that may overflow. Embed the existing badge-card-widget at the top of the sheet by passing the BadgeState directly — do not recreate the card's internal structure. Use BlocBuilder
Announce modal open via SemanticsService.announce() with AssertiveQueue to trigger immediate screen reader announcement. Apply design tokens exclusively for colors, spacing, and typography — no hardcoded values. Avoid using Navigator.pop inside the modal for the drag-to-dismiss path; rely on Flutter's built-in DraggableScrollableSheet dismiss behaviour.
Testing Requirements
Write widget tests (flutter_test) verifying: (1) modal renders with badge-card-widget header for both earned and locked badge states, (2) earned timestamp is displayed when badge state is earned, (3) progress text is displayed when badge state is locked, (4) modal closes on back-button press and returns focus. Add golden tests for earned and locked states. Perform manual accessibility audit with VoiceOver (iOS) and TalkBack (Android) to confirm focus order and announcements. Use flutter_test Semantics matchers to assert label content.
Minimum 80% widget test coverage for badge-detail-modal.
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.