Build Notification Centre Screen scaffold and layout
epic-in-app-notification-centre-ui-task-007 — Implement the NotificationCentreScreen as the main Notifications tab screen. Assemble the layout: app bar with title and mark-all-read action button, NotificationFilterBar beneath the app bar, and a scrollable body area for the notification list. Use the design token system for colors, spacing, and typography. Ensure the screen registers with StatefulShellRoute to preserve tab state. The mark-all-read button must dispatch MarkAllRead event to NotificationBLoC and be hidden when all notifications are already read.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Structure the screen as: Scaffold → Column → [AppBar equivalent via SliverAppBar or standard AppBar, NotificationFilterBar, Expanded(child: BlocBuilder(...)]. For the mark-all-read button visibility, use `BlocSelector
All spacing values (e.g., padding between filter bar and list) must use design token spacing constants (e.g., `AppSpacing.md`). The AppBar actions list should conditionally include the mark-all-read button using a ternary in the actions array rather than a conditional render with Visibility widget.
Testing Requirements
Write widget tests using flutter_test. Provide a mocked NotificationBLoC via BlocProvider in the test harness. Test: (1) screen renders AppBar with correct title, (2) mark-all-read button hidden when state.unreadCount == 0, (3) mark-all-read button visible when state.unreadCount > 0, (4) tapping mark-all-read dispatches MarkAllRead event to BLoC. Test responsive layout at 320px and 428px width using WidgetTester constraints.
Verify Semantics tree contains 'Mark all notifications as read' label on the action button. Run accessibility audit using flutter_test's SemanticsController to confirm no missing labels or contrast issues in the test environment.
If a referenced entity (contact, certification, activity) has been deleted or its RLS policy now excludes the current user, the deep link handler may navigate to a screen that renders in an error state or throws an unhandled exception.
Mitigation & Contingency
Mitigation: The deep link handler must perform a lightweight existence check (HEAD request or minimal SELECT) before pushing the route. Define a contract with each destination screen for how to handle a not-found entity ID passed as a route parameter.
Contingency: If the existence check itself fails (network error), navigate to the destination screen anyway and let it handle the error gracefully with its own error state; do not block navigation for network timeouts.
If the tab badge widget triggers a full rebuild of the bottom navigation bar on every unread count change, it will cause visible jank on devices with many active Realtime events (e.g., org admins receiving org-wide alerts).
Mitigation & Contingency
Mitigation: Scope the badge widget to a dedicated BlocSelector that rebuilds only when the unread count value changes, not on any BLoC state emission. Use RepaintBoundary to isolate the badge from the rest of the nav bar.
Contingency: If performance issues persist, debounce badge updates to a maximum of one rebuild per 500ms and display the last known count during the debounce window.
Complex swipe-to-mark-read gestures and dynamic list updates may conflict with VoiceOver/TalkBack navigation patterns, particularly for Blindeforbundet users who rely exclusively on screen readers.
Mitigation & Contingency
Mitigation: Provide a dedicated accessibility action (Semantics.onTap / CustomSemanticsAction) for mark-as-read on each list item so screen reader users do not need the swipe gesture. Test with VoiceOver on iOS and TalkBack on Android before each release.
Contingency: If the swipe gesture proves incompatible with assistive technologies, disable it when a screen reader is detected (via ScreenReaderDetectionService) and rely solely on the tap-to-read and accessible action pathways.