critical priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

ReceiptCameraSheet is implemented as a DraggableScrollableSheet and presented via showModalBottomSheet with correct initialChildSize, minChildSize, and maxChildSize values
Two action buttons are rendered: 'Take photo' (launches camera via ReceiptImagePickerIntegration) and 'Choose from library' (launches gallery via ReceiptImagePickerIntegration)
All touch targets are at minimum 44x44 logical pixels in compliance with WCAG 2.2 AA touch target size requirements
Sheet uses design token values for background color (dark surface), border radius (top corners only, token-defined radius), and button styling — no hardcoded color or size literals
Both camera and gallery actions call ReceiptImagePickerIntegration and return the selected image file to the caller
Sheet is dismissible via swipe-down gesture and backdrop tap, returning null when dismissed without selection
Sheet renders correctly on both iOS and Android at all standard screen sizes (SE to 14 Pro Max, small Android to large tablet)
Camera permission prompt is triggered by ReceiptImagePickerIntegration before launching camera; sheet remains open if permission is denied and shows a brief inline error state
Gallery permission prompt is triggered before opening gallery on Android; on iOS photo library limited access is handled gracefully
Widget tree uses const constructors wherever possible for performance

Technical Requirements

frameworks
Flutter
image_picker
apis
ReceiptImagePickerIntegration (internal)
Flutter DraggableScrollableSheet API
showModalBottomSheet
performance requirements
Sheet open animation completes within 300ms at 60fps
No jank during drag interaction — sheet must respond at native frame rate
Widget must not trigger unnecessary rebuilds outside its own subtree
security requirements
Camera and gallery permissions requested only at the moment of user interaction, not at app launch
No image data stored or transmitted within this widget — delegation to ReceiptImagePickerIntegration and ReceiptAttachmentService only
No file paths or image bytes logged to console or analytics
ui components
DraggableScrollableSheet
AppButton (primary and secondary variants from design system)
Design token surface color container
Icon widgets using design token icon size

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use showModalBottomSheet with isScrollControlled: true to allow full-screen drag. Wrap content in SafeArea with bottom: true to handle home indicator on iOS. DraggableScrollableController is not needed unless snap behaviour is required — keep it simple. Do not use Navigator.push for this sheet; it must overlay the wizard form without replacing it.

Pass ReceiptImagePickerIntegration as a constructor dependency (not created inside the widget) to enable testability. Design token values must be read from the Theme extension — do not use Theme.of(context).colorScheme directly unless your token system wraps it. Handle the case where image_picker returns null (user cancelled OS picker) by propagating null back up cleanly. Avoid setState inside an async gap — use a local BLoC or Cubit if upload status tracking is needed here (though that belongs to task-012).

Testing Requirements

Widget tests using flutter_test: verify sheet renders with correct button count and labels; verify tapping 'Take photo' invokes ReceiptImagePickerIntegration.pickFromCamera(); verify tapping 'Choose from library' invokes ReceiptImagePickerIntegration.pickFromGallery(); verify sheet can be dismissed without selection; verify touch targets meet 44pt minimum via widget bounds assertions. Use mocktail to mock ReceiptImagePickerIntegration. Golden tests for visual regression of sheet layout on at least two screen sizes. No integration tests required for this task.

Component
Receipt Camera Sheet
ui medium
Epic Risks (3)
high impact medium prob technical

Non-blocking upload creates a race condition: if the claim record is submitted and saved before the upload completes, the storage path may never be written to the claim_receipts table, leaving the claim with a missing receipt that was nonetheless required.

Mitigation & Contingency

Mitigation: Design the attachment service to queue a completion callback that writes the storage path to the claim record upon upload completion, even after the claim form has submitted. Use a local task queue with persistence to survive app backgrounding. Test the race condition explicitly with simulated slow uploads.

Contingency: If the async path association proves unreliable, fall back to blocking upload before claim submission with a clear progress indicator, accepting the UX trade-off in exchange for data integrity.

high impact medium prob scope

The offline capture requirement (cache locally, sync when connected) significantly increases state management complexity. If the offline queue is not durable, receipts captured without connectivity may be lost when the app is killed, causing claim submission failures users are not aware of.

Mitigation & Contingency

Mitigation: Persist the offline upload queue to local storage (e.g., Hive or SQLite) on every state transition. Implement background sync using WorkManager (Android) and BGTaskScheduler (iOS). Scope the initial delivery to online-only flow if offline sync cannot be adequately tested before release.

Contingency: Ship without offline support in the first release, displaying a clear 'Upload requires connection' message. Add offline sync as a follow-on task once the core online flow is validated in production.

medium impact low prob integration

The inline bottom sheet presentation within a multi-step wizard can conflict with existing modal navigation and back-button handling, particularly if the expense wizard itself uses nested navigation or custom route management.

Mitigation & Contingency

Mitigation: Review the expense wizard navigation architecture before implementation. Use showModalBottomSheet with barrier dismissal disabled to prevent accidental dismissal. Coordinate with the expense wizard team on modal stacking behavior and ensure the camera sheet does not interfere with wizard step transitions.

Contingency: If modal stacking causes navigation issues, present the camera sheet as a full-screen dialog using PageRouteBuilder with a transparent barrier, preserving wizard state via the existing Bloc while still appearing inline.