Build NotificationBadgeWidget with Realtime Count
epic-push-notification-delivery-ui-task-011 — Implement the NotificationBadgeWidget as a small overlay badge on the bottom navigation Notifications tab that subscribes to the Supabase Realtime unread count stream from NotificationRepository. Animate badge appearance and disappearance, cap displayed count at 99+, and update immediately on mark-all-read. Ensure VoiceOver announces unread count as semantic label on the tab item.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Implement as a stateful widget (or ConsumerWidget/BlocBuilder) that wraps a StreamBuilder listening to NotificationRepository.watchUnreadCount(). Use an AnimatedSwitcher with a ScaleTransition child keyed on count > 0 to handle animate-in/out. Format count with a helper: int → String where value > 99 returns '99+'. Wrap the entire bottom nav tab in a Stack, placing the NotificationBadgeWidget at Alignment.topRight with a small positive offset.
For Semantics, override the tab item's semanticsLabel at the NavigationDestination level rather than inside the badge itself to avoid duplicate announcements. Ensure the Supabase Realtime channel is opened once at the repository layer and shared — do not open a new channel per widget instance.
Testing Requirements
Unit tests: test badge label formatting — 0 returns empty/hidden, 1 returns '1', 99 returns '99', 100 returns '99+', 999 returns '99+'. Widget tests: render NotificationBadgeWidget with a StreamController
Integration test: simulate a Supabase Realtime event incrementing the unread count and verify badge updates. Memory leak test: mount and unmount widget, assert StreamSubscription is cancelled. Target 85% line coverage.
The notification badge widget depends on a persistent Supabase Realtime websocket subscription for live unread count updates. On mobile, network transitions (WiFi to cellular, background app state) can silently drop the websocket, resulting in a stale badge count that does not update until the next app foreground — reducing trust in the notification system.
Mitigation & Contingency
Mitigation: Implement connection lifecycle management in the badge widget's BLoC that re-subscribes on app foreground and on network reconnection events. Add a fallback polling query (every 60 seconds when app is foregrounded) to reconcile the badge count if the Realtime subscription is interrupted.
Contingency: If Realtime reliability proves insufficient in production, replace the live subscription with a polling approach using a configurable interval, accepting slightly delayed badge updates in exchange for reliability.
The notification list item widget requires merged semantics combining title, body, timestamp, read state, and role-context icon into a single VoiceOver/TalkBack announcement. Getting the merged semantics structure right for both iOS (VoiceOver) and Android (TalkBack) simultaneously is non-trivial and common to break silently when widgets are refactored.
Mitigation & Contingency
Mitigation: Use the project's existing semantics-wrapper-widget pattern with explicit Semantics widgets and excludeSemantics on decorative children. Write accessibility widget tests using Flutter's SemanticsController to assert the exact announcement string. Test on physical devices with VoiceOver and TalkBack enabled before release.
Contingency: If merged semantics cannot be achieved cleanly on both platforms, implement platform-specific semantic trees using defaultTargetPlatform branching, ensuring each platform receives an optimal announcement even if the implementation differs.