critical priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

NotificationCentreScreen is a StatefulWidget registered as a branch route under StatefulShellRoute in the app router
Screen renders an AppBar with the title 'Notifications' (using design token typography) and a mark-all-read action icon button
Mark-all-read button is visible only when BLoC state contains at least one unread notification
Mark-all-read button dispatches MarkAllRead event to NotificationBLoC when tapped
NotificationFilterBar widget is rendered directly below the AppBar (persistent, not scrollable)
Body area below the filter bar is scrollable and reserved for the notification list (or loading/empty states in task-008)
All colors, spacing, border radius, and typography values are sourced from design tokens — zero hardcoded style values
Screen preserves scroll position and filter state when the user navigates to another tab and returns (StatefulShellRoute behavior)
AppBar title has correct contrast ratio (WCAG 2.2 AA minimum 4.5:1) against the AppBar background color
Mark-all-read button has a Semantics label 'Mark all notifications as read' for screen reader accessibility
Screen renders correctly on screen sizes from 320px width (small phone) to 428px width (large phone)
Widget test confirms mark-all-read button is absent when BLoC state has 0 unread, present when unread > 0

Technical Requirements

frameworks
Flutter
BLoC
data models
NotificationBLoC state
NotificationState (unread count, notification list)
performance requirements
Screen must reach first meaningful paint within 300ms of tab switch on a mid-range device
AppBar and FilterBar must not cause jank — avoid expensive computations in build()
security requirements
Screen must only be reachable by authenticated users — route guard must be applied at the router level
ui components
Scaffold (Flutter built-in)
AppBar (Flutter built-in)
IconButton with Semantics for mark-all-read action
Column layout: AppBar → NotificationFilterBar → Expanded scrollable body
BlocBuilder<NotificationBLoC, NotificationState> for conditional mark-all-read visibility

Execution Context

Execution Tier
Tier 2

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` to select only the unread count and avoid full rebuilds on every state change. Use `context.read().add(MarkAllRead())` inside the button's onPressed. Apply `AutomaticKeepAliveClientMixin` on the State class if needed for StatefulShellRoute tab state preservation — verify this is required based on the router implementation.

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.

Component
Notification Centre Screen
ui medium
Epic Risks (3)
medium impact medium prob integration

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.

medium impact low prob technical

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.

high impact medium prob scope

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.