Connect Filter Bar to NotificationBLoC state
epic-in-app-notification-centre-ui-task-002 — Wire the NotificationFilterBar to the NotificationBLoC using BlocBuilder so the active filter chips reflect the current FilterState from the BLoC. Dispatching a chip tap should fire the appropriate FilterTypeChanged or FilterReadStateChanged event. Ensure the widget rebuilds only on filter state changes (not on list-loading state changes) to avoid unnecessary redraws. Add unit tests covering event dispatch and chip active-state rendering.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
The buildWhen implementation should compare previous.activeFilter == current.activeFilter and return the negation. Avoid comparing entire state objects unless NotificationState implements Equatable with only activeFilter in props (it should have all fields). Place BlocBuilder as close to NotificationFilterBar in the tree as possible — not at the screen root. For event dispatch, use context.read
This keeps NotificationFilterBar itself BLoC-agnostic (pure widget), which improves testability. Verify the wiring works end-to-end by running the full notification screen manually or via a golden test if the team uses golden_toolkit. Document the buildWhen logic with a brief inline comment explaining why it guards against non-filter state changes.
Testing Requirements
Widget tests using flutter_test and bloc_test/mocktail. Cover: (1) BlocBuilder only rebuilds the chip row when activeFilter changes — simulate a list-only state change and assert build count is unchanged, (2) correct chip is marked active for each of the 7 filter values, (3) tapping a type chip dispatches FilterTypeChanged with the correct filter value to a mock bloc, (4) tapping a read-state chip dispatches FilterReadStateChanged with the correct value, (5) no chip is double-highlighted when filter is 'all' (default state). Use a MockNotificationBloc with mocktail and provide it via BlocProvider in pump(). Use a build call counter (via a flag in a test-only wrapper) to verify buildWhen prevents extra builds.
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.