high priority medium complexity integration pending integration specialist Tier 3

Acceptance Criteria

ShareDestination enum exposes three values: systemSheet, clipboard, gallery
SummaryShareService.share(ShareDestination destination, Uint8List imageBytes) is the single public entry point; it dispatches to the correct sub-flow based on destination
systemSheet: image bytes are written to a cache-directory temp file, then Share.shareXFiles([XFile(path)]) is called; temp file is deleted after the share sheet is dismissed
clipboard: image bytes are base64-encoded and written to the system clipboard via Clipboard.setData; a SnackBar informs the user 'Image copied to clipboard'
gallery (Android < 10, API < 29): WRITE_EXTERNAL_STORAGE permission is requested via permission_handler before saving; denied state shows a settings-redirect dialog matching the iOS design pattern
gallery (Android 10+, API >= 29): no runtime permission required; image is saved directly via MediaStore ContentValues insert with MIME type image/png and RELATIVE_PATH = 'Pictures/Eircodex'
gallery save success shows a SnackBar with 'Image saved to gallery'
gallery save failure (e.g., MediaStore insert returns null URI) shows an error SnackBar and logs the failure
All three destinations work correctly on both Android 9 and Android 13 (verified in emulator or device)

Technical Requirements

frameworks
Flutter
BLoC
apis
share_plus (pub.dev)
permission_handler (pub.dev)
path_provider (pub.dev)
MediaStore (Android SDK via platform channel or image_gallery_saver)
performance requirements
Temp file write for system share must complete in under 300ms for a 5MB PNG
MediaStore insert must complete in under 1 second
security requirements
Temp file for share sheet written to getTemporaryDirectory() (app-private cache), not external storage
Temp file deleted after share sheet closes to avoid lingering sensitive image data on device
Clipboard content is aggregate stats only — no contact PII; document this in code comments
WRITE_EXTERNAL_STORAGE only requested on API < 29; never request on API >= 29 per Android documentation
ui components
SnackBar (success/error feedback)
AlertDialog (permission denied, Android < 10 only)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use share_plus XFile approach for system sheet — write bytes to path.join((await getTemporaryDirectory()).path, 'wrapped_summary.png') using File.writeAsBytes, then wrap in XFile. Attach a share result listener (ShareResult) from share_plus v7+ to detect when the sheet is dismissed and delete the temp file in the callback. For API version detection at runtime, use DeviceInfoPlugin from device_info_plus to get androidInfo.version.sdkInt rather than compile-time constants. For MediaStore on API >= 29, use the method channel approach through image_gallery_saver which already handles the ContentValues insert internally — avoid writing a custom platform channel for this.

Expose ShareDestination as a sealed class alternative if Dart 3 sealed classes are already used in the project; otherwise an enum is fine. The BLoC should dispatch a SaveToGalleryEvent / ShareViaSheetEvent and the handler calls the single share(destination, bytes) method — no platform-specific logic in the BLoC layer.

Testing Requirements

Widget tests using flutter_test with share_plus and permission_handler mocked: (1) destination=systemSheet → assert Share.shareXFiles called with a valid file path and temp file is cleaned up; (2) destination=clipboard → assert Clipboard.setData called and success snackbar shown; (3) destination=gallery, API < 29, permission granted → assert MediaStore/gallery save called; (4) destination=gallery, API < 29, permission denied → assert settings dialog shown; (5) destination=gallery, API >= 29 → assert no permission request and save proceeds directly; (6) simulate MediaStore null URI return → assert error snackbar shown. Manual QA on Android 9 emulator (API 28) and Android 13 device.

Component
Summary Share Service
service medium
Epic Risks (3)
high impact medium prob integration

Activity records may contain duplicate entries (as evidenced by the duplicate-detection feature dependency) or proxy-registered activities that should be attributed differently. Including duplicates or mis-attributed records would produce inflated stats, undermining trust in the summary.

Mitigation & Contingency

Mitigation: Implement the aggregation query to join against the deduplication-reviewed-flag on activity records and filter out unresolved duplicates. Coordinate with the duplicate-detection feature team to confirm the authoritative flag field before implementing the RPC. Include a data-quality warning in the summary when unresolved duplicates are detected.

Contingency: If deduplication state is unreliable at release time, add a prominent disclaimer in the summary UI noting that figures reflect all registered activities and may include duplicates pending review. Track a follow-up task to re-aggregate after deduplication runs.

medium impact high prob scope

Each organisation wants to define their own milestone thresholds (e.g., NHF's counting model differs from HLF's certification model). Implementing configurable thresholds may expand scope significantly if the configuration UI is expected in this epic.

Mitigation & Contingency

Mitigation: Scope this epic strictly to the evaluation engine and a hardcoded default threshold set. Define the MilestoneDefinition interface with an organisation_id discriminator so per-org configs can be loaded from the database in a later sprint. Build the admin configuration UI as a separate follow-on task outside this epic.

Contingency: If stakeholders require per-org milestone configuration before launch, deliver a JSON-based configuration file per org as an interim solution, loaded from Supabase storage, until a full admin UI is built.

medium impact medium prob technical

Android 13+ restricts access to media collections and requires READ_MEDIA_IMAGES permission for gallery saves, while older Android versions use WRITE_EXTERNAL_STORAGE. Handling both permission models correctly across the device matrix is error-prone.

Mitigation & Contingency

Mitigation: Use the permission_handler Flutter package with version-aware permission requests abstracted behind the summary-share-service interface. Write platform-specific unit tests for both Android API levels in the test harness. Test on a minimum of three Android versions (API 29, 32, 34) in CI.

Contingency: If gallery save is broken on specific Android versions at launch, disable the 'Save to gallery' option on affected API levels and surface only clipboard and system share sheet, which require no media permissions.