high priority low complexity integration pending integration specialist Tier 1

Acceptance Criteria

ImagePickerReceiptImagePickerImpl (or equivalent) implements ReceiptImagePickerIntegration using the image_picker package
pickFromGallery() calls ImagePicker().pickImage(source: ImageSource.gallery) and returns ReceiptImageResult with raw bytes and detected MIME type
pickFromCamera() calls ImagePicker().pickImage(source: ImageSource.camera) and returns ReceiptImageResult with raw bytes and detected MIME type
User cancellation (XFile is null) returns null without throwing
EXIF orientation is corrected before returning bytes (image rotated to match display orientation)
No compression applied — imageQuality parameter not set or set to 100
EXIF metadata (including GPS) stripped from bytes before return
Platform permission denial (PlatformException with code 'photo_access_denied' or 'camera_access_denied') surfaces as a typed ReceiptPickerPermissionDeniedException rather than an unhandled exception
Riverpod provider overrides in main.dart (or platform bootstrap) register this implementation replacing the UnimplementedError default
Info.plist entries for NSCameraUsageDescription and NSPhotoLibraryUsageDescription are present on iOS

Technical Requirements

frameworks
Flutter
Riverpod
dart
image_picker Flutter Package (iOS UIImagePickerController / Android Camera2)
performance requirements
readAsBytes() must complete within 3 seconds for images up to 12MP
EXIF stripping must not add more than 200ms overhead on mid-range devices
security requirements
EXIF metadata stripped from all returned bytes to prevent GPS location leakage
Receipt images containing PII treated as sensitive — bytes must not be written to device temp storage beyond the XFile lifecycle
No logging of byte arrays or file paths containing PII
Permission denial handled gracefully — no crash, no silent failure

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use image_picker ^1.x. EXIF orientation correction can be done with the flutter_exif_rotation package or by reading the orientation tag and rotating using the image package before stripping. For EXIF stripping, use the flutter_image_compress package's stripExif option or the image package's encodeJpg (which drops EXIF by default). Be careful: readAsBytes() returns the raw file bytes including EXIF, so stripping must happen after readAsBytes().

The implementation class should be a single file shared between iOS and Android since image_picker abstracts the platform differences — only add conditional code if a genuine platform divergence is discovered. Register the implementation in ProviderScope overrides at app startup.

Testing Requirements

Unit tests using flutter_test with mockito to mock ImagePicker. Test scenarios: (1) mock returns valid XFile → assert ReceiptImageResult returned with correct MIME type, (2) mock returns null (cancellation) → assert null returned, (3) mock throws PlatformException with photo_access_denied → assert ReceiptPickerPermissionDeniedException thrown, (4) EXIF strip: provide a JPEG byte array with known EXIF orientation tag and assert it is absent in output. Widget tests not required for this task — UI integration tested in a later task.

Component
Receipt Image Picker Integration
infrastructure low
Epic Risks (3)
high impact medium prob security

Supabase Storage RLS policies using org/user/claim path scoping may not enforce correctly if claim ownership is not present in the JWT or if path segments are constructed differently at upload vs. read time, leading to data leakage or access denial for legitimate users.

Mitigation & Contingency

Mitigation: Define and test RLS policies in isolation before wiring to app code. Write integration tests that assert cross-org and cross-user access is denied. Use service-role key only in edge functions, never in client code.

Contingency: If client-side RLS proves insufficient, route all storage reads through a Supabase Edge Function that validates ownership before generating signed URLs, adding a controlled server-side enforcement layer.

high impact medium prob technical

Aggressive image compression may reduce receipt legibility below the threshold required for financial auditing, causing claim rejections or compliance failures despite technically successful uploads.

Mitigation & Contingency

Mitigation: Define minimum legibility requirements with HLF finance team before implementation. Set compression targets conservatively (e.g., max 1MB, min 80% JPEG quality) and validate with sample receipt images. Provide compression statistics in verbose/debug mode.

Contingency: If post-compression quality is disputed by auditors, increase the quality floor at the cost of larger file sizes, and add a manual override allowing users to skip compression for PDFs and high-quality scans.

medium impact medium prob dependency

The Flutter image_picker package behaves differently on iOS 17+ (PHPicker) vs older Android (Intent-based), particularly for file types, permission flows, and PDF selection, which may cause platform-specific failures not caught in development.

Mitigation & Contingency

Mitigation: Test image picker integration on physical devices for both platforms early in the sprint. Pin the image_picker package version and review changelogs before updates. Write widget tests using mock file results for each platform branch.

Contingency: If PHPicker or Android Intent differences cause blocking issues, implement separate platform-specific picker delegates behind the unified interface, allowing platform-specific fixes without breaking the shared API.