high priority medium complexity integration pending integration specialist Tier 3

Acceptance Criteria

NSPhotoLibraryAddUsageDescription key is present in ios/Runner/Info.plist with a plain Norwegian and English description explaining why gallery access is needed
Permission is requested at the moment of save action — not at app launch
PermissionStatus.granted: image is saved to the photo library via image_gallery_saver and a success SnackBar is shown
PermissionStatus.limited (iOS 14+): image is saved to the selected limited library and a non-blocking informational snackbar informs the user that access is limited
PermissionStatus.denied: a modal dialog with plain-language explanation and 'Open Settings' button appears; tapping it opens iOS Settings via openAppSettings(); dismissing the dialog does not crash
PermissionStatus.restricted: a dialog informs the user that their device policy prevents photo access, with no settings redirect (since it would be ineffective)
If image_gallery_saver.saveImage() returns a result with isSuccess=false, a descriptive error SnackBar is shown and the error is logged
Permission request and save flow are implemented behind a SaveToGalleryUseCase abstraction that is injectable for testing
No permission prompt is shown if the user taps cancel before the capture completes

Technical Requirements

frameworks
Flutter
apis
image_gallery_saver (pub.dev)
permission_handler (pub.dev)
openAppSettings (permission_handler)
performance requirements
Gallery save must complete within 2 seconds for images up to 5MB
security requirements
Image containing personal aggregate statistics stored in user's private photo library — inform user in permission rationale text
No image file is written to external/shared storage on iOS
Exif metadata not applicable for synthetically rendered PNG — no stripping needed
ui components
AlertDialog (settings-redirect)
SnackBar (success/error feedback)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Add permission_handler and image_gallery_saver to pubspec.yaml. In Info.plist add NSPhotoLibraryAddUsageDescription (NOT NSPhotoLibraryUsageDescription — add-only permission is sufficient and less invasive). Use Permission.photos.request() for iOS < 14 and Permission.photosAddOnly for iOS 14+ (check Platform.version or use the permission_handler's built-in limited status). Wrap the entire flow in a SaveToGalleryUseCase class that accepts a PermissionHandler interface and a GallerySaver interface — this allows full mock-based unit testing without a real device.

For the settings dialog, use flutter_platform_aware dialogs only if the project already uses cupertino_alert_dialog; otherwise use Material AlertDialog for consistency. Ensure the 'Open Settings' button calls openAppSettings() from permission_handler, not a hardcoded URL scheme.

Testing Requirements

Widget tests using flutter_test with permission_handler mocked via a FakePermissionHandler: (1) mock granted → assert saveImage called and success snackbar shown; (2) mock denied → assert dialog shown with 'Open Settings' button; (3) mock restricted → assert informational dialog shown without settings button; (4) mock limited → assert save proceeds and limited-access snackbar shown; (5) simulate saveImage failure → assert error snackbar shown. Manual QA on physical iPhone with iOS 14+ for limited permission flow; verify Info.plist string appears in system permission alert.

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.