critical priority high complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

Screen uses SingleActionScreenLayout as the outermost layout widget with no custom page scaffold
ExpenseTypeSelectorWidget and ReceiptCaptureWidget are mounted as distinct steps controlled by the step progress indicator
The step progress indicator visually reflects the current step number and total steps; it is labelled semantically (e.g. 'Step 2 of 4')
The form body is wrapped in a SingleChildScrollView so it remains fully usable at font scale 2.0x and on small screens (320 dp width)
The primary submit/next button is pinned in the bottom safe area and never overlaps scrollable content
The submit button is disabled and shows a loading indicator when ExpenseFormBloc is in loading/submitting state
BlocBuilder scopes rebuild to only the widgets that depend on changed state fields (use buildWhen)
Back navigation from the screen discards draft state only after a confirmation dialog if the user has entered any data
Screen title and step label update as the user advances through steps
All layout parameters (padding, spacing, font sizes) come from design tokens — no hardcoded values

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
Dart
apis
ExpenseFormBloc (event/state stream)
SingleActionScreenLayout API
Design token system (AppSpacing, AppTextStyle, AppColors)
data models
ExpenseFormState
ExpenseFormStep (enum or int index)
DraftExpenseClaim
performance requirements
Screen must render first frame within 100 ms on mid-range device
Step transitions must be animated at 60 fps with no jank; use AnimatedSwitcher or PageView
Keyboard appearance must not cause layout shift that pushes the submit button off screen — use resizeToAvoidBottomInset correctly
security requirements
Draft state must not be persisted to local storage without encryption if it contains personal data (submitter identity, amounts)
Receipt images captured must not be written to the device gallery without explicit user consent
ui components
SingleActionScreenLayout
StepProgressIndicator
ExpenseTypeSelectorWidget
ReceiptCaptureWidget
PersistentBottomButton
LoadingButtonState

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Model the multi-step flow as a PageController or an IndexedStack so each step widget remains alive (preserving scroll position and field values). SingleActionScreenLayout should receive the step body as a scrollable child slot and the action button as a bottom slot — do not nest another Scaffold. The step progress indicator should be a simple LinearProgressIndicator or dot indicator above the form body, not a bottom navigation bar. Integrate ExpenseFormBloc with BlocProvider above the route (in the router) rather than inside the screen widget so the BLoC survives step navigation.

Use addPostFrameCallback to request focus on the first field of each step after the step transition animation completes — this aids keyboard and screen-reader users. Follow the cognitive accessibility guideline: one primary action per screen, no floating action buttons, no side-swipe navigation between steps.

Testing Requirements

Widget tests: screen renders with correct initial step; advancing to next step updates progress indicator label; back navigation on first step triggers route pop; back navigation on later steps returns to previous step without route pop. BLoC integration tests: loading state disables submit button and shows spinner; error state does not advance step. Golden tests for each step at 1.0x and 2.0x font scale and 320 dp screen width. Accessibility: Semantics tree contains step label 'Step N of M'; submit button Semantics includes disabled state when loading.

Test that keyboard appearance does not clip the submit button.

Component
Expense Registration Screen
ui high
Epic Risks (3)
medium impact medium prob dependency

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.

high impact medium prob technical

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.

high impact medium prob technical

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.