high priority medium complexity integration pending integration specialist Tier 1

Acceptance Criteria

shareWidget(globalKey, shareText) successfully invokes the native share sheet on both iOS and Android without throwing an unhandled exception
The PNG file passed to share_plus is written to a platform-appropriate temp directory (NSTemporaryDirectory on iOS, getCacheDir on Android) and not to external storage
If captureWidget returns an empty Uint8List (0 bytes), shareWidget throws a ScreenshotCaptureException with message 'Captured image is empty' before invoking the share sheet
captureAsBase64(globalKey) returns a non-empty base64-encoded String representing the PNG; the decoded bytes are identical to those returned by captureWidget for the same widget
When the user dismisses the share sheet without sharing, shareWidget completes normally without throwing; the temporary file is deleted afterward
If share_plus throws a PlatformException (e.g., permission denied on an Android profile that blocks sharing), shareWidget catches it and rethrows a ShareException with the platform error code preserved
The share sheet is invoked with a subject field populated from shareText and the PNG XFile attachment correctly named 'wrapped_summary.png'
Temporary files created during shareWidget are deleted in a finally block regardless of share outcome or exception
The method works correctly when called from a non-root route (modal, bottom sheet) — share sheet anchors to the correct view on iPad
captureAsBase64 does not write any file to disk and relies solely on in-memory byte conversion

Technical Requirements

frameworks
Flutter
share_plus (^10.x)
dart:convert (base64Encode)
dart:io (File, XFile)
path_provider (getTemporaryDirectory)
apis
Share.shareXFiles() from share_plus
XFile constructor with mimeType 'image/png'
ScreenshotCaptureUtility.captureToFile (from task-009)
ScreenshotCaptureUtility.captureWidget (from task-009)
data models
annual_summary
performance requirements
Total time from shareWidget call to share sheet appearing must be under 2 seconds for a standard Wrapped summary card on a mid-range device
Base64 encoding in captureAsBase64 must not block the UI thread — perform encoding in a compute() isolate for images larger than 500 KB
security requirements
Shared content must contain only aggregated statistics and achievement badges — no contact names, assignment details, or health-related PII from the contact or activity data models
Temporary PNG files must be created with a UUID filename (not a predictable path) to prevent path traversal by other apps on Android
On Android, file must be shared via a FileProvider URI (share_plus handles this internally — verify the plugin version supports it)
shareText must be sanitised to strip any markdown or HTML that could be misinterpreted by the receiving app

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use share_plus's `Share.shareXFiles([XFile(filePath, mimeType: 'image/png')], subject: shareText)` API. Always wrap the share invocation in a try/catch for PlatformException. Build the temp file path as `'${tempDir.path}/${const Uuid().v4()}.png'` using the uuid package. Delete the file in a finally block using `File(filePath).deleteSync(ignoreIfAbsent: true)`.

For captureAsBase64, call captureWidget, then `base64Encode(bytes)` inside a `compute()` call: `await compute(_encodeBase64, bytes)` where `_encodeBase64` is a top-level function. On iPad, pass the `sharePositionOrigin` parameter to Share.shareXFiles using the GlobalKey's renderBox rect to anchor the popover correctly. Do not use deprecated `Share.share()` with file paths — always use shareXFiles with XFile.

Testing Requirements

Unit tests (flutter_test) with a mocked Share.shareXFiles() to verify: (1) correct XFile mime type and filename, (2) empty-byte guard throws ScreenshotCaptureException, (3) PlatformException is wrapped in ShareException, (4) temp file deletion in finally block. Widget test confirming captureAsBase64 output decodes to identical bytes as captureWidget. Integration test on device (see task-014) to verify share sheet appears and dismissal completes without error. Minimum 85% branch coverage on shareWidget and captureAsBase64.

Component
Screenshot Capture Utility
infrastructure medium
Epic Risks (3)
medium impact medium prob dependency

Rive animation files may not be available at implementation time, blocking the wrapped-animation-controller from being fully tested. If asset delivery is delayed, the controller cannot be validated for memory-leak-free disposal.

Mitigation & Contingency

Mitigation: Implement the animation controller with stub/placeholder AnimationController instances first so the lifecycle and disposal logic can be unit-tested independently of Rive assets. Define a named animation registry interface early so UI components can reference animations by name without coupling to specific Rive files.

Contingency: If Rive assets are not delivered before Epic 3 begins, replace Rive animations with Flutter implicit animations (AnimatedOpacity, ScaleTransition) as a drop-in and schedule Rive integration as a follow-on task once assets arrive.

high impact medium prob technical

The annual_summaries Supabase RPC aggregating 12 months of activity records per mentor may exceed acceptable query latency (>2s) for mentors with high activity volumes such as the HLF mentor with 380 registrations cited in workshop notes.

Mitigation & Contingency

Mitigation: Design the RPC to materialise summary results into the annual_summaries table via a scheduled edge function rather than computing on demand. The repository reads pre-computed rows, keeping query latency constant regardless of activity volume.

Contingency: If on-demand queries are required for real-time period switching, add a PostgreSQL partial index on (mentor_id, activity_date) and implement a client-side loading skeleton so slow queries degrade gracefully rather than blocking the UI.

medium impact low prob technical

iOS requires photo library permission before saving a screenshot to the gallery. If the permission prompt is triggered at an unexpected point in the share flow, the UX breaks and users may deny permission permanently, making gallery save unavailable.

Mitigation & Contingency

Mitigation: Trigger the permission request only when the user explicitly chooses 'Save to gallery' in the share overlay, not on screen load. Implement a pre-prompt explanation screen following Apple HIG so users understand why the permission is needed before the system dialog appears.

Contingency: If permission is denied, gracefully fall back to clipboard copy and system share sheet options which do not require photo library access, and surface a non-blocking snackbar explaining the limitation.