critical priority medium complexity frontend pending fullstack developer Tier 2

Acceptance Criteria

ContactListScreen renders a scrollable list of ContactCardWidget items using mock fixture data
ContactSearchBar is rendered at the top of the screen and its debounced value filters the displayed contact list (case-insensitive name match)
ContactViewSwitcher is rendered below the search bar when the active org's feature flags include peerMentorView: true; it is hidden otherwise
When ContactViewSwitcher is set to 'Contacts', only ContactCardWidget items are displayed; when set to 'Peer Mentors', only PeerMentorCardWidget items are displayed
Role-based filtering is applied before rendering: coordinator role sees all contacts, peer mentor role sees only contacts where assignedMentorId matches the current user's ID, org admin role sees the full roster
Filtering by search query and role is composed — both filters are applied simultaneously
An empty state message is displayed when the filtered list produces zero results (e.g., 'No contacts found')
Tapping a ContactCardWidget or PeerMentorCardWidget navigates to the appropriate detail screen via the router
The screen uses mock data from task-001 fixtures — no live Supabase calls are made at this stage
Screen renders without errors for all three role types (coordinator, peer mentor, org admin) when the mock data and role are swapped
ContactListScreen is integrated into the app's bottom navigation Contacts tab route

Technical Requirements

frameworks
Flutter
Riverpod
BLoC
flutter_test
data models
Contact
PeerMentor
UserRole
OrgFeatureFlags
OrgLabels
ContactSearchQuery
performance requirements
ListView must use ListView.builder (not ListView with children: [...]) to lazily render only visible items
Search filtering must be performed on a pre-built list in memory — no async operations triggered per keystroke
Widget rebuilds triggered by search query changes must only rebuild the list section, not the entire screen
security requirements
Role-based filtering is enforced in the UI layer and must also be enforced at the data layer (Supabase RLS) when live providers are connected — document this assumption clearly in code comments
Mock data must not contain real PII — use synthetic fixture names and IDs only
ui components
ListView.builder for contact list
ContactCardWidget
PeerMentorCardWidget
ContactSearchBar
ContactViewSwitcher
Empty state widget (text + icon)
Page header / AppBar with screen title

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Structure ContactListScreen as a ConsumerWidget (Riverpod) that reads three providers: currentUserRoleProvider, orgFeatureFlagsProvider, and orgLabelsProvider — all of which return mock data at this stage. Keep the role-filtering and search-filtering logic in a separate pure function (List filterContacts(List all, UserRole role, String currentUserId, String query)) that is easy to unit test in isolation without widgets. Use a local StateProvider for the search query driven by ContactSearchBar's debounced onChanged callback. Use a local StateProvider for the view switcher selection.

For the ListView, pass the filtered list to ListView.builder's itemCount and itemBuilder — never use a where(...).toList() inside itemBuilder (compute the filtered list once outside the builder). When connecting to real Supabase providers in a later task, the filtering logic should migrate to the repository layer with Supabase RLS as the authoritative enforcement — add a TODO comment marking this handoff point.

Testing Requirements

Write flutter_test widget tests: (1) render with coordinator role mock data and assert all contacts are visible; (2) render with peer mentor role and assert only assigned contacts are visible; (3) enter a search query and assert the list filters to matching contacts only; (4) enter a query with no matches and assert the empty state widget is shown; (5) render with an org that has peerMentorView: false and assert ContactViewSwitcher is absent; (6) render with an org that has peerMentorView: true and assert ContactViewSwitcher is present; (7) switch ContactViewSwitcher to 'Peer Mentors' and assert PeerMentorCardWidget items are rendered instead of ContactCardWidget items; (8) tap a ContactCardWidget and assert the correct navigation event is fired (mock the router or use a navigation observer). Use pumpWidget with a ProviderScope wrapping mock providers for Riverpod-based state.

Component
Contact List Screen
ui medium
Dependencies (4)
Build the ContactViewSwitcher as a segmented control widget (SegmentedButton or equivalent) with two or more toggle options. Default label values are 'Contacts' and 'Peer Mentors'. Accept an optional label-override map from the org-labels provider so Barnekreftforeningen and other organizations can substitute terminology (e.g., 'Members' / 'Mentors'). Expose a selectedView ValueNotifier and an onChanged callback. The widget visibility is controlled by a parent-provided boolean flag. epic-contact-list-management-ui-components-task-007 Build the ContactCardWidget Flutter widget displaying contact name as the primary label, a color-coded role badge, an organization label derived from the org-labels provider, and a truncated notes preview capped at two lines with ellipsis overflow. Wire the onTap callback to pass the contact ID to the parent navigator. Use design token values for spacing, typography, and border radius. epic-contact-list-management-ui-components-task-002 Build PeerMentorCardWidget as a visually distinct Flutter widget from ContactCardWidget. Display mentor name, availability status (active/paused/unavailable) as a colored chip, certification status (current/expiring/expired) as a badge, and assigned member count as a numeric indicator. Use a distinct card background color and layout from ContactCardWidget to allow users to instantly differentiate the two types in a mixed list. epic-contact-list-management-ui-components-task-004 Build the ContactSearchBar widget as a styled Flutter TextField with a 300ms debounce on value changes to prevent excessive provider calls. Include a clear (X) icon button that resets the search query and returns focus to the input. Provide an accessible label and hint text via semanticsLabel and decoration hintText. Expose an onChanged stream/callback and a controller for programmatic control by the parent ContactListScreen. epic-contact-list-management-ui-components-task-006
Epic Risks (2)
medium impact medium prob technical

Design token color values used in role badges, certification status indicators, and availability chips may not meet the WCAG 2.2 AA contrast ratio of 4.5:1 when rendered against card backgrounds, requiring rework after accessibility review and potentially blocking acceptance sign-off.

Mitigation & Contingency

Mitigation: Run the contrast-ratio-validator on every new token combination during widget development. Enforce the CI accessibility lint runner on all PRs touching visualization components, and validate against the contrast-safe-color-palette before finalizing card designs.

Contingency: If contrast failures are found late, adjust token values in the design token theme centrally — since all widgets consume design tokens rather than hardcoded colors, all affected widgets will be corrected by a single token update without per-widget changes.

low impact medium prob dependency

The ContactViewSwitcher is required for Barnekreftforeningen but must not appear for other organizations. If the organization labels provider does not yet expose a reliable feature flag for this widget, it may render universally or be conditionally hidden in an inconsistent way, breaking the role-specific layout contract.

Mitigation & Contingency

Mitigation: Implement view switcher visibility as a constructor parameter on ContactListScreen injected from a provider, defaulting to hidden. Document the integration point for the org labels provider so the flag can be wired without changing the widget's API.

Contingency: If org labels integration is delayed beyond this epic, use a feature flag constant keyed to the Barnekreftforeningen organization ID as a temporary gate, with a tracked issue to replace it with the runtime labels provider before general release.