medium priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

Share/download icon button is rendered in the AttachmentPreviewModal app bar or bottom action bar with a minimum 44x44 pt touch target
Tapping the button fetches the signed URL from Supabase storage and downloads the file bytes to a temporary device path
Share.shareXFiles() is invoked with the downloaded file and the original filename as the subject
A loading indicator is shown on the button while the download is in progress; the button is disabled during download to prevent duplicate taps
On download failure (network error, expired URL), a plain-language SnackBar error is shown and the button returns to its active state
Semantics widget wraps the button with a label such as 'Share attachment: filename.pdf'
The feature works on both iOS (native share sheet) and Android (intent chooser)
Unit test confirms that Share.shareXFiles is called with the correct XFile path and subject

Technical Requirements

frameworks
Flutter
BLoC
apis
Supabase Storage signed URL API
share_plus ShareXFiles API
data models
Attachment (id, file_name, mime_type, signed_url, file_size)
performance requirements
Download must not block the UI thread — use an isolate or async Future with proper state management
Signed URL must be fetched fresh at share time to avoid expired-URL errors (URLs typically expire in 60 s)
security requirements
Never cache signed URLs in persistent storage — fetch on demand only
Temporary downloaded file must be placed in the app's temp directory (getTemporaryDirectory) and deleted after Share.shareXFiles resolves
Do not log file contents or signed URLs
ui components
IconButton with Semantics wrapper
CircularProgressIndicator overlay on button
SnackBar for error feedback

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use the share_plus package (already common in Flutter projects) — do not use url_launcher for downloads. Fetch a fresh Supabase signed URL inside the BLoC event handler rather than reusing one stored in state. Use path_provider's getTemporaryDirectory() to write the file, then pass the path to XFile. Wrap the entire download-and-share flow in a try/catch and emit distinct BLoC states (ShareInProgress, ShareSuccess, ShareError) so the UI reacts correctly.

The button should use a SizedBox(width: 44, height: 44) constraint to meet the touch-target requirement even if the icon is smaller.

Testing Requirements

Write widget tests using flutter_test: (1) mock the Supabase storage client to return a known byte array, (2) verify Share.shareXFiles is called with an XFile whose path ends with the expected filename, (3) verify the button is disabled while the BLoC state is ShareInProgress, (4) verify SnackBar appears when the BLoC emits ShareError. Integration test on a real device via TestFlight should confirm the native share sheet opens and the file is readable.

Component
Attachment Preview Modal
ui low
Epic Risks (4)
medium impact medium prob dependency

Flutter does not include a first-party PDF renderer. Third-party packages (e.g., flutter_pdfview, syncfusion_flutter_pdf) have inconsistent accessibility support, may not honour dynamic type scaling, and can introduce large binary size increases. Choosing the wrong package late in development risks rework or an inaccessible PDF experience for Blindeforbundet users.

Mitigation & Contingency

Mitigation: Evaluate and spike two PDF rendering packages (flutter_pdfview and pdfx) in the first task of this epic before committing to implementation. Criteria: VoiceOver compatibility, dynamic type support, APK/IPA size delta, and licence. Document the decision in an ADR.

Contingency: If no package meets accessibility requirements, fall back to opening PDFs in the system browser via url_launcher, which inherits the OS's accessible PDF viewer. Display a clear 'Opening in external viewer' message to set user expectation.

medium impact medium prob technical

Flutter's Semantics API for live region announcements (analogous to aria-live) has known gaps on Android for dynamic content updates. Upload progress announcements required by the accessibility user story may not fire reliably on Android devices used by HLF and NHF members.

Mitigation & Contingency

Mitigation: Use SemanticsService.announce() for imperative announcements at each upload state transition rather than relying on declarative Semantics widget tree updates. Test on a physical Android device with TalkBack enabled during development, not only on iOS with VoiceOver.

Contingency: If SemanticsService.announce() proves unreliable, implement a persistent accessible status banner at the top of the screen that reflects the current upload state as plain text, satisfying the WCAG success criterion through a visual + programmatic mechanism.

low impact high prob technical

iOS and Android file picker behaviour diverges significantly: MIME type filtering works reliably on Android but is advisory on iOS (users can still navigate to and select non-compliant files via the Files app). This could allow unsupported file types to reach the upload service, causing validation failures with a confusing user experience.

Mitigation & Contingency

Mitigation: Rely on the AttachmentUploadService (Epic 2) as the authoritative MIME validation gate, regardless of platform. In the UI, re-validate the picked file's extension/MIME after selection and show an inline plain-language error ('Only PDF, JPEG, and PNG files are supported') before even calling the service.

Contingency: If users consistently hit the error due to iOS file picker limitations, add a user-facing help tooltip on the picker sheet explaining the supported file types, reducing support volume while the underlying OS limitation persists.

high impact medium prob scope

WCAG 2.2 AA compliance for three new interactive components (picker, grid, modal) with file system integration, role-gated actions, and live regions is a significant accessibility surface area. An incomplete audit before release could result in the feature being unusable for Blindeforbundet screen reader users at launch.

Mitigation & Contingency

Mitigation: Allocate a dedicated accessibility audit task at the end of the epic using the accessibility-test-harness (619) and wcag-compliance-checker (620) components already in the project. Include at least one manual test session on a physical device with VoiceOver enabled. Fail the epic's definition of done if any WCAG 2.2 AA violation remains open.

Contingency: If critical accessibility issues are found late, gate the feature behind the org-level attachments_enabled feature flag and only enable it for NHF (the requesting org) after a targeted fix cycle, rather than delaying the release for all organisations.