Expense Type Selector — mutual exclusion logic
epic-travel-expense-registration-ui-task-001 — Implement the expense type selector widget with a predefined catalogue of fixed expense types. Enforce mutual exclusion at the UI layer so that incompatible combinations (e.g. mileage + public transit) are disabled when a conflicting type is selected. Render accessible conflict warnings using Semantics labels and live region announcements compliant with WCAG 2.2 AA.
Acceptance Criteria
Technical Requirements
Implementation Notes
Model conflict rules as a Map
Validate against design tokens for color; do not use hardcoded hex values. Keep conflict rules and type catalogue as const data at the top of the config file to make future additions trivial.
Testing Requirements
Unit tests (flutter_test): test conflict resolution logic for all documented incompatible pairs; test that enabling a type disables exactly the correct peers; test re-enabling on deselection. Widget tests: pump the widget in isolation, simulate taps, assert chip states and warning visibility. Accessibility tests: use flutter_test's SemanticsController to verify Semantics labels and liveRegion flags. Integration test: full tap-through on both iOS Simulator (VoiceOver) and Android Emulator (TalkBack).
Target ≥ 90% line coverage on conflict-rule logic.
The image_picker Flutter plugin requires platform-specific permissions (NSPhotoLibraryUsageDescription, camera permission) and behaves differently across iOS and Android versions. Permission denial or plugin misconfiguration can silently prevent receipt attachment.
Mitigation & Contingency
Mitigation: Configure all required permission strings in Info.plist and AndroidManifest.xml during initial plugin setup. Use the permission_handler package to check and request permissions before launching the picker, with clear user-facing explanations. Test on both platforms across at least two OS versions.
Contingency: If image_picker proves unreliable on a specific platform version, fall back to file_picker as an alternative that uses the OS document picker interface, which requires fewer permissions on some Android versions.
The expense form BLoC manages interconnected state across expense type selection, field visibility, receipt requirement, threshold evaluation, and submission flow. Incorrect state transitions can cause UI inconsistencies such as required receipt indicator not updating after amount change, or form appearing valid when mutual exclusion is violated.
Mitigation & Contingency
Mitigation: Model BLoC states as sealed classes with exhaustive pattern matching. Write state transition unit tests covering every combination of: type selection change, amount field change above/below threshold, receipt attachment/removal, and offline mode toggle. Use bloc_test for comprehensive state sequence assertions.
Contingency: If BLoC complexity becomes unmanageable, split into two BLoCs — one for type selection/exclusion state and one for field values/submission — coordinating via a parent provider, accepting the small overhead of inter-BLoC communication.
The expense type selector must enforce mutual exclusion visually by disabling options and showing conflict tooltips, while remaining fully accessible to screen reader users who cannot perceive visual disable states. Incorrect semantics labelling will fail WCAG 2.2 AA requirements critical for Blindeforbundet and HLF users.
Mitigation & Contingency
Mitigation: Use Flutter Semantics widgets to explicitly set disabled state and provide conflict explanations as semanticLabel strings on disabled options. Run accessibility audits with TalkBack and VoiceOver during widget development, not post-completion. Reference the project's accessibility test harness for required test coverage.
Contingency: If custom widget accessibility is difficult to certify, implement the selector as a standard Flutter Radio/Checkbox group with built-in accessibility semantics and an explanatory Text widget below each conflicting option, sacrificing visual elegance for guaranteed WCAG compliance.