high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

AttachmentPreviewModal is a full-screen modal (covers entire screen, no visible underlying content)
Modal is navigable via go_router or Navigator.of(context).push with a MaterialPageRoute or custom route
A close button is rendered in the top-right corner with a minimum touch area of 44x44 logical pixels
Tapping the close button dismisses the modal and returns to the previous route
Modal accepts signedUrl (String) and mimeType (String) as required constructor/route parameters
A loading state placeholder (centered CircularProgressIndicator or shimmer) is shown before content is rendered
Modal background colour is consistent with the app's dark/light theme via design tokens
Modal title or accessible label identifies the content type ('Image preview' or 'Document preview') for screen readers
Back gesture (Android back button / iOS swipe-from-edge) dismisses the modal correctly
Widget renders without overflow or layout errors at all standard Flutter device sizes (375pt to 428pt width)

Technical Requirements

frameworks
Flutter
go_router (if used project-wide) or Navigator 2.0
data models
AttachmentPreviewArgs (signedUrl, mimeType)
performance requirements
Modal must appear (shell rendered) within one frame of navigation — no async work in constructor
Loading placeholder must be visible immediately before content widget mounts
security requirements
signedUrl must not be logged or written to persistent storage within the modal
mimeType must be validated against allowlist (image/jpeg, image/png, application/pdf) before passing to child renderers
ui components
Scaffold with backgroundColor from design tokens
AppBar or custom header with close IconButton (Icons.close)
SafeArea wrapper for notch/status bar clearance
CircularProgressIndicator for loading placeholder
Semantics widget for accessible modal label

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Implement as a standard StatefulWidget (or ConsumerStatefulWidget if Riverpod is used). Accept signedUrl and mimeType as final fields. Use a WillPopScope (or PopScope in Flutter 3.16+) to handle Android back button. For the close button, use IconButton with constraints: BoxConstraints(minWidth: 44, minHeight: 44).

Apply design token colours to the Scaffold background — avoid hardcoded hex values. Validate mimeType with a simple allowlist check and set an isUnsupportedType bool that will be used in task-004 to render an error state instead of content. Keep this scaffold free of content-rendering logic — content widgets will be composed in task-004. Use go_router's GoRoute with a path like /attachment-preview if the project uses go_router; otherwise use Navigator.push with a custom route transition.

Testing Requirements

Widget tests: verify modal renders at full screen size; verify close button is present and has semantics label 'Close'; verify tapping close button triggers Navigator.pop; verify loading placeholder is shown when content area is empty; verify signedUrl and mimeType parameters are passed through without mutation. Use WidgetTester.pumpWidget with a MaterialApp and Navigator to simulate modal context. Verify that back gesture (WidgetTester equivalent) also dismisses the modal.

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.