Implement receipt storage adapter with compression
epic-travel-expense-registration-foundation-task-006 — Build the ReceiptStorageAdapter Dart class that handles image selection via image_picker, compresses images to ≤500 KB using flutter_image_compress before upload, uploads to the Supabase Storage bucket, and returns short-lived signed URLs for display. Implement download caching for previously fetched receipt thumbnails to avoid redundant network calls.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Use flutter_image_compress's compressWithList method on the raw bytes from image_picker to avoid writing temp files. The compression loop should be: compress at quality Q → check output size → if still >500 KB and Q > 40, reduce Q by 10 and retry. Cap at 3 iterations to avoid UI stalls. For the signed URL cache, use a simple Map
Invalidate cache entries where expiry < DateTime.now().add(Duration(minutes: 10)) to avoid serving near-expiry URLs. For upload progress, use Supabase Storage's onUploadProgress callback (available in supabase_flutter). Be aware that on Android, image_picker may return a Uri that requires FileProvider context — handle this with the `path_provider` package if needed for temp file resolution. Define storage path as: `$orgId/$claimId/${DateTime.now().millisecondsSinceEpoch}_receipt.jpg` to ensure uniqueness within a claim.
Testing Requirements
Unit tests (flutter_test + mocktail): mock Supabase storage client and image_picker, test compression logic with oversized mock image bytes (verify output ≤500 KB), test cache hit/miss behaviour for signed URLs, test cancellation (null picker result), test each ReceiptUploadFailureReason mapping. Widget tests: ReceiptThumbnailWidget renders loading spinner while future is pending, renders image on success, renders error icon on failure. Integration test: upload a real JPEG to local Supabase instance, verify it is retrievable via signed URL, verify the file size in storage is ≤500 KB. Test on both iOS simulator and Android emulator for permission handling differences.
Row-level security policies for expense claims must correctly scope data to organisation, role (peer mentor sees own claims only, coordinator sees org-wide queue), and claim status. Incorrect RLS can expose claims cross-organisation or prevent coordinators from accessing the attestation queue.
Mitigation & Contingency
Mitigation: Define RLS policies in code-reviewed migration files. Write integration tests that attempt cross-org reads with different JWT roles and assert access denial. Review with a second engineer before merging migrations.
Contingency: If RLS is misconfigured post-deployment, disable the affected policy temporarily and apply a hotfix migration within the same release window. No claim data is exposed publicly due to Supabase project-level auth requirement.
The auto-approval Edge Function is triggered server-side on expense insert. Cold-start latency or Edge Function failures can block the submission response and degrade UX, especially on mobile networks.
Mitigation & Contingency
Mitigation: Implement the auto-approval Edge Function client with a timeout and graceful fallback: if no result is received within 5 seconds, treat the claim as 'pending' and poll for the status update via Supabase Realtime. Keep the Edge Function warm with a periodic ping.
Contingency: If Edge Function reliability is unacceptable, move auto-approval evaluation to a database trigger or Postgres function as an interim measure, accepting that threshold configuration changes require a migration rather than a settings update.
The expense type catalogue and threshold configuration are cached locally for offline use. If an organisation updates their catalogue exclusion rules or thresholds while a peer mentor is offline, the local cache may allow submissions that violate the new policy.
Mitigation & Contingency
Mitigation: Cache entries include a TTL (24 hours). On connectivity restore, refresh cache before allowing new submissions. Server-side validation in the Edge Function and save functions provides a second enforcement layer.
Contingency: If a stale-cache submission passes client validation but fails server validation, surface a clear error message explaining that the expense type rules have been updated and prompt the user to review their selection with the refreshed catalogue.