high priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

An abstract DonutChartAdapter interface is defined with methods: buildPieSections(List<ActivityTypeCount>), buildCenterSpaceRadius(double availableWidth), buildCenterLabel(int total), and onSectionTouched(PieTouchResponse) — no fl_chart types in method signatures
A concrete FlChartDonutChartAdapter implements DonutChartAdapter; it is the sole file importing fl_chart
Each PieChartSectionData has radius set to `availableWidth * 0.35` and a center space of `availableWidth * 0.25` producing a donut hole
The center of the donut renders the total count as a Text widget via badgeWidget or centerSpaceColor overlay (not as showTitle inside a section)
A legend is built as a separate widget (DonutLegendRow) rendered below the chart — one row per activity type with a color swatch and label; the legend is not embedded inside fl_chart
Touch callback invokes onSectionTouched(activityTypeId, count, percentage) — no fl_chart types leaked to callers
Touched segments scale up by 8 dp radius and display a tooltip with count and percentage using fl_chart's PieTouchData; untouched segments return to normal radius
Segment entry animation uses 500 ms duration with easeInOut curve
All segment colors are sourced from the design token palette; each color achieves a WCAG 2.2 AA contrast ratio of at least 3:1 against the chart background (verified via contrast check utility in shared utils)
Adapter is injectable via Riverpod/BLoC DI for testability
Unit tests verify: correct number of sections for input, correct percentage calculation per section (sum = 100 ± 0.1%), correct color assignment from tokens

Technical Requirements

frameworks
Flutter
fl_chart
Riverpod or BLoC
data models
ActivityTypeCount (activityTypeId: String, label: String, count: int)
ActivityTypeSummary
performance requirements
Section construction for up to 10 activity types completes in under 1 ms
Legend row list renders without overflow at screen widths down to 320 dp
security requirements
No PII in segment labels or tooltips
Colors validated against WCAG contrast at adapter construction time — throw AssertionError in debug mode if a token fails contrast check
ui components
DonutLegendRow (color swatch + label + percentage)
CenterTotalLabel (Text overlay in donut hole)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Define DonutChartAdapter in `lib/features/statistics/adapters/donut_chart_adapter.dart`. The center label (total count) is best implemented as a Stack — place a IgnorePointer Text widget centered over the PieChart widget rather than using fl_chart's badge system, which has alignment quirks. For the legend, build a Wrap or Column of DonutLegendRow widgets outside the fl_chart PieChart widget; this keeps the legend accessible (Semantics-friendly) and independently scrollable on small screens. Use fl_chart's `pieTouchData: PieTouchData(touchCallback: ...)` for interaction; keep a `_touchedIndex` in state and rebuild sections with a larger radius for the touched index.

For WCAG contrast validation, implement a `WcagContrastChecker.meetsAA(foreground, background)` utility in `lib/core/utils/wcag_contrast_checker.dart` using the WCAG relative luminance formula — run it in assert blocks during development. Avoid using `showTitle: true` inside PieChartSectionData for percentage labels; render them in the legend instead to keep the chart clean and accessible.

Testing Requirements

Unit tests (flutter_test): (1) Adapter with 5 activity types returns exactly 5 PieChartSectionData objects. (2) Sum of section values equals sum of input counts. (3) Each section color is a valid token color (mock token provider). (4) Touch response with sectionIndex=2 calls onSectionTouched with correct activityTypeId, count, and percentage.

(5) Adapter with empty input returns empty list without throwing. Widget test: adapter wired to test PieChart renders on 300×300 canvas without overflow — golden test. Contrast check utility test: assert that all design token activity-type colors pass 3:1 contrast against the standard chart background token.

Component
Activity Type Donut Chart
ui medium
Epic Risks (4)
medium impact medium prob technical

PeerMentorStatsList must handle rosters of 40+ mentors efficiently. A naive ListView implementation that re-renders all rows on filter change may cause frame drops on mid-range devices, degrading the experience for coordinators managing large chapters.

Mitigation & Contingency

Mitigation: Use ListView.builder with const constructors for row widgets. Profile the list with Flutter DevTools on a release build against a 60-mentor dataset before submitting for review. Implement sort in the BLoC layer, not in the widget.

Contingency: If frame drops persist, introduce pagination (load 20 mentors, scroll to load more) and add a search filter to reduce visible row count in practice.

medium impact low prob integration

The ActivityTypeDonutChart must render org-configured activity type labels from the org-labels system. If the terminology provider is not available or returns stale labels, chart segments will display raw key strings instead of human-readable organisation-specific names, confusing coordinators.

Mitigation & Contingency

Mitigation: Always resolve labels through the OrganizationLabelsProvider before passing data to the donut chart widget. Implement a fallback that formats the raw key as a readable string (e.g., 'peer_support' → 'Peer Support') if the provider returns null.

Contingency: If the org-labels system is unavailable, display the formatted fallback label and log a warning. Do not block chart rendering on label resolution — render with fallbacks immediately.

high impact high prob technical

fl_chart widgets do not natively expose semantic labels for individual bars and donut segments. Without explicit Semantics wrappers, VoiceOver and TalkBack users will receive no meaningful chart information, failing accessibility requirements critical for Blindeforbundet and NHF deployments.

Mitigation & Contingency

Mitigation: Wrap each fl_chart widget in a Semantics widget with a descriptive label summarising the chart data. Implement the data table fallback toggle from the start, not as an afterthought. Validate with VoiceOver on iOS and TalkBack on Android during development.

Contingency: If fl_chart's rendering pipeline prevents semantic overlay from working reliably, replace chart widgets with a custom Canvas-based implementation that has full semantic control, or use a different charting library with better accessibility support.

medium impact medium prob technical

The period filter state must be preserved when navigating away from the dashboard (e.g., drilling into a mentor's detail screen and pressing back). If the BLoC is re-created on navigation, the filter resets to default, forcing coordinators to re-select their period every time they drill down, degrading operational workflow efficiency.

Mitigation & Contingency

Mitigation: Provide the StatsBloc at a route level above the dashboard screen using a BlocProvider scoped to the statistics navigation shell, not inside the screen widget itself. Verify persistence with a widget integration test that navigates away and back.

Contingency: If route-level scoping is not achievable within the current navigation architecture, persist the last-used filter to a local session store and restore it on screen re-entry.