Scaffold Mileage Entry Form top-level screen
epic-mileage-reimbursement-entry-user-interface-task-009 — Create the MileageEntryForm screen widget as a StatefulWidget with a Form key, a ScrollView wrapper, a pre-filled read-only date field showing today's date (formatted per locale), and placeholder slots for the four sub-widgets (DistanceInputField, RouteInputFields, RealtimeReimbursementDisplay, and a confirm button). Apply design token theme, single-action screen layout pattern, and 48dp minimum touch targets throughout.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 3 - 413 tasks
Can start after Tier 2 completes
Implementation Notes
Follow the app's single-action screen layout pattern — confirm the pattern name/class used in existing screens (e.g., activity wizard screens) and replicate the same structure exactly. Use `final _formKey = GlobalKey
Store `_today = DateTime.now()` in initState. Use intl's DateFormat('dd.MM.yyyy').format(_today) for formatting. Placeholder slots should be clearly commented: `// TODO(task-010): embed DistanceInputFieldWidget here`. Keep this scaffold free of business logic — it is a structural shell to be composed in task-010.
Testing Requirements
Write flutter_test widget tests: (1) smoke test that MileageEntryForm pumps without exceptions. (2) Verify date field displays today's date in 'dd.MM.yyyy' format. (3) Verify confirm button is present and has a minimum height of 48dp using tester.getRect(). (4) Verify Form widget is present in the tree using find.byType(Form).
(5) Verify SingleChildScrollView is present. Use MockGoRouter or NavigatorObserver if route registration is tested. No integration tests required at scaffold stage.
Triggering MileageCalculationService on every keystroke in the distance field could cause frame drops on lower-end Android devices if the widget rebuild chain is too broad. Jank during real-time calculation would degrade the peer-mentor experience, particularly for users with motor impairments who may type slowly and need immediate feedback.
Mitigation & Contingency
Mitigation: Keep the reimbursement calculation inside the RealtimeReimbursementDisplay widget subtree only, using a StatefulWidget with a local state variable rather than lifting state to the BLoC. This limits rebuilds to a single widget. Profile on a low-end device (Qualcomm Snapdragon 450 class) before code review.
Contingency: If profiling shows frame drops, debounce the onChanged callback by 50ms using a Timer before updating the local state, which remains imperceptible to the user while eliminating excessive rebuilds.
The optional destination field requires balancing two competing requirements: WCAG 2.2 AA demands a clear label association for screen readers, while the privacy requirement means the label must communicate optionality and privacy sensitivity without exposing the user to undue pressure to fill it in. A poorly worded label could cause screen-reader users to misunderstand the field's optional nature or feel compelled to enter private location data.
Mitigation & Contingency
Mitigation: Draft label text and hint text with input from at least one screen-reader user during the accessibility review. Use Flutter's Semantics widget to provide a separate semantic label that is more descriptive than the visual placeholder. Test with TalkBack and VoiceOver before sign-off.
Contingency: If user testing reveals confusion, replace the inline hint with a tappable information icon that opens a brief explanation of why the field is optional and how the data is used, reducing cognitive load on the field label itself.
If OrgRateConfigRepository has not yet resolved the org rate when the form loads (e.g. slow network, first launch with empty cache), the reimbursement display cannot show a meaningful value. Showing '0' or an error state could confuse mentors and undermine trust in the feature.
Mitigation & Contingency
Mitigation: Display a loading skeleton in the reimbursement display widget until the rate is available. Once loaded, animate the value in. Show a subtle 'rate unavailable' message if the fetch times out, but allow form completion and submission (rate will be fetched server-side at submission time).
Contingency: If rate unavailability is frequent due to connectivity issues, store the last successfully fetched rate in SharedPreferences as an additional fallback so the widget can render a stale-but-indicative value with a caveat label.