Build NotificationSettingsScreen with Category Toggles
epic-push-notification-delivery-ui-task-010 — Implement the NotificationSettingsScreen displaying per-category toggle switches (activity reminders, certification expiry, pause status, scenario prompts) with labels and descriptions. Show current OS permission status banner with a direct link to system settings if permission is denied. Persist toggle state changes immediately to Supabase via NotificationPreferencesRepository. Apply design token system and WCAG AA contrast.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Use a BLoC/Cubit that holds a Map
Use AppOpenSettings or url_launcher with app-settings: URI for the 'Open Settings' button. All colors, text styles, and spacing must reference design tokens — avoid hardcoded values. Keep the repository interface thin: one method to fetch all preferences, one method to update a single category.
Testing Requirements
Unit tests: test NotificationSettingsCubit/Bloc state transitions for toggle-on, toggle-off, load-success, load-failure, and save-failure states using flutter_test and bloc_test. Widget tests: render NotificationSettingsScreen with mocked repository returning granted/denied permission and verify banner visibility, toggle initial values, and Semantics labels. Integration test: tap a toggle, verify repository.updateCategoryPreference() is called with correct arguments, simulate a Supabase failure and verify toggle revert + SnackBar. Accessibility test: run flutter_test semantics inspector to confirm all interactive elements have meaningful labels.
Target 90% line coverage on cubit/bloc and widget.
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.