Implement FileDownloadHandler for native device save/share
epic-bufdir-report-export-foundation-task-008 — Build the file download handler that retrieves a signed URL from export storage and triggers the native device save/share sheet using the share_plus or open_file Flutter package. Implement downloadAndShare(signedUrl, fileName, mimeType) with progress callbacks and error states. Handle iOS share sheet and Android save-to-downloads separately. This is the final delivery mechanism that coordinators interact with after an export completes.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Use `http.Client.send(Request)` with a `StreamedResponse` to download bytes in chunks and update the DownloadProgress stream incrementally — do not use `http.get()` which buffers the entire response. Write chunks to a File sink in the Flutter temp directory using `IOSink`. After download completes, call `Share.shareXFiles([XFile(tempPath, mimeType: mimeType)], subject: fileName)` from share_plus for the cross-platform share sheet. On Android, share_plus delegates to the system intent which will include the Downloads option.
For cleanup, register a callback on the Share result Future — if the user dismisses without saving, delete the temp file immediately. Add a fallback: if share_plus throws PlatformException (e.g., no apps installed), open the file directly with `OpenFile.open(tempPath)` from the open_file package. Keep the handler stateless — all state (progress, error) lives in the Riverpod DownloadNotifier that calls this service.
Testing Requirements
Unit tests (flutter_test) for FileDownloadHandler: (1) successful download emits progress from 0.0 to 1.0 then complete; (2) HTTP 403 on signed URL emits DownloadError.urlExpired; (3) network timeout emits DownloadError.network; (4) fileName sanitisation replaces illegal characters; (5) temp file is deleted after handler completes. Widget tests for the calling screen verifying that Riverpod/BLoC state updates trigger progress indicator rebuild. Manual device testing on iOS simulator (share sheet appearance) and Android emulator API 31+ (Downloads save + notification). No automated e2e test for the native share sheet itself — document as a manual test case.
NHF's three-level hierarchy (national / region / chapter) with 1,400 chapters may have edge cases such as chapters belonging to multiple regions, orphaned nodes, or missing parent links in the database. Incorrect scope expansion would silently under- or over-report activities, which could invalidate a Bufdir submission.
Mitigation & Contingency
Mitigation: Obtain a full hierarchy fixture export from NHF before implementation begins. Write exhaustive unit tests covering boundary cases: single chapter, full national roll-up, chapters with no activities, and chapters assigned to multiple regions. Validate resolver output against a known-good manual count.
Contingency: If hierarchy data quality is too poor for automated resolution at launch, implement a manual scope override in the coordinator UI that allows the coordinator to explicitly select org units from a tree picker, bypassing the resolver.
The activity_type_configuration table may not cover all activity types currently in use, leaving a subset unmapped at launch. Bufdir submissions with unmapped categories will be incomplete and may be rejected by Bufdir.
Mitigation & Contingency
Mitigation: Run a query against production activity data before implementation to enumerate all distinct activity type IDs. Cross-reference with Bufdir's published category schema (request from Norse Digital Products). Flag every gap as a known issue and build the warning surface into the preview panel.
Contingency: Implement a fallback 'Other' category bucket for unmapped types and surface a prominent warning in the export preview requiring coordinator acknowledgement before proceeding. Log unmapped types for post-launch cleanup.
Supabase RLS policies on generated_reports and the storage bucket must enforce strict org isolation. A misconfigured policy could allow a coordinator from one organisation to read another organisation's export files, creating a serious data breach with GDPR implications.
Mitigation & Contingency
Mitigation: Write RLS integration tests that attempt cross-org reads with explicitly different JWT tokens and assert that all attempts return empty sets or 403 errors. Include RLS policy review in the pull request checklist. Use Supabase's built-in policy tester during development.
Contingency: If a policy gap is discovered post-deployment, immediately revoke all signed URLs for affected exports, audit the access log for unauthorised reads, and issue a coordinated disclosure to affected organisations per GDPR breach notification requirements.