high priority medium complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

AttachmentPickerUI renders as a modal bottom sheet with two clearly labeled source options: 'Camera Roll' (image_picker) and 'Files' (file_picker)
image_picker is configured to return only image types; file_picker is configured with allowedExtensions: ['jpg', 'jpeg', 'png', 'pdf'] and type: FileType.custom
Files with MIME types other than image/jpeg, image/png, or application/pdf are silently excluded and a plain-language toast is shown ('Only images and PDFs are supported')
After selection, each chosen file is shown as a preview row containing: thumbnail (image preview or PDF icon), file name, and human-readable file size (e.g., '2.3 MB')
A 'Confirm' primary button and 'Cancel' secondary button are visible below the preview list
Tapping 'Confirm' emits the selected files list to the parent widget/BLoC via a callback or event
Tapping 'Cancel' or dismissing the sheet returns without emitting any files
The sheet respects safe-area insets on all device sizes
Widget test confirms that selecting a PDF file renders a preview row with the correct filename and size

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
apis
image_picker ImagePicker.pickMultiImage
file_picker FilePicker.platform.pickFiles
data models
PendingAttachment (localPath, fileName, mimeType, fileSizeBytes)
performance requirements
Thumbnail generation for images must use Image.file with cacheWidth set to 80 px to avoid loading full-resolution images into memory
File size must be read from XFile.length() asynchronously — never block the build method
security requirements
Validate MIME type by reading the first bytes of the file (magic bytes), not just by file extension, to prevent extension spoofing
Do not persist local file paths beyond the picker session; hand off to the upload service immediately after confirmation
ui components
DraggableScrollableSheet or showModalBottomSheet
ListTile preview row with leading thumbnail
AppButton (primary Confirm, secondary Cancel)
SizedBox 44x44 touch targets for source options

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use showModalBottomSheet with isScrollControlled: true and a DraggableScrollableSheet so the sheet expands when many files are selected. Keep picker logic in a dedicated AttachmentPickerCubit or similar BLoC component to separate picker state (idle, picking, previewing) from upload state. For MIME validation, use the mime package to derive MIME from extension as a first pass, then optionally validate magic bytes for PDFs (first 4 bytes: %PDF). Store selected files as a list of PendingAttachment value objects, not raw XFile objects, to make the model testable without file system access.

Ensure AndroidManifest.xml has READ_EXTERNAL_STORAGE permission for file_picker on older Android versions.

Testing Requirements

Widget tests using flutter_test with mocked image_picker and file_picker: (1) verify both source option buttons are rendered, (2) mock a PDF file selection and assert a preview row with the correct name and size appears, (3) mock an unsupported file type and assert it does not appear in the preview list and a rejection message is shown, (4) verify 'Confirm' emits the correct PendingAttachment list and 'Cancel' emits nothing. Manual test on TestFlight device for camera roll permissions prompt (iOS requires NSPhotoLibraryUsageDescription in Info.plist).

Component
Attachment Picker UI
ui medium
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.