Build Receipt Thumbnail Preview Widget base layout
epic-receipt-capture-and-attachment-ui-accessibility-task-001 — Implement the core Receipt Thumbnail Preview Widget with compressed image thumbnail display, file metadata row (filename, size, format), and placeholder state. Wire to a Receipt model for image bytes and metadata. No actions yet — purely the visual card shell.
Acceptance Criteria
Technical Requirements
Implementation Notes
Create the widget in lib/features/receipts/widgets/receipt_thumbnail_preview.dart. Define a Receipt model (or reuse the existing one from the foundation epic) with fields: Uint8List? imageBytes, String? filename, int?
fileSizeBytes, String? format. For size formatting, use a helper that converts bytes to KB/MB with one decimal place. Use ClipRRect with the design token border radius for the image container.
The placeholder icon should use Icons.receipt_long or a custom SVG from the asset bundle if one exists. Keep the widget fully dumb — no callbacks, no state — so it composes cleanly when actions are added in task-002 and task-003.
Testing Requirements
Write flutter_test widget tests covering: (1) widget renders thumbnail when Receipt has valid imageBytes, (2) widget renders placeholder when Receipt is null or imageBytes is null, (3) filename truncation at 30 characters shows ellipsis, (4) metadata row shows correctly formatted size string. Use pumpWidget with a MaterialApp wrapper. No golden tests required unless the team has an established golden test pipeline.
Flutter's accessibility live region support (SemanticsProperties.liveRegion) has known inconsistencies between iOS VoiceOver and Android TalkBack, and between Flutter versions. Threshold-crossing announcements may fail to fire or double-fire, breaking the accessibility contract for Blindeforbundet users.
Mitigation & Contingency
Mitigation: Test live region announcements on physical devices with VoiceOver and TalkBack enabled from the first iteration. Use the AccessibilityLiveRegionAnnouncer component pattern already established in the project. Verify announcement timing relative to Bloc state emissions to avoid double-fires.
Contingency: If Flutter live regions prove unreliable, implement a platform-channel fallback that calls UIAccessibility.post(notification:) on iOS and AccessibilityManager.sendAccessibilityEvent() on Android directly, bypassing Flutter's abstraction.
The org-configurable threshold must be available at form-render time. If the threshold configuration is fetched asynchronously and not cached, the indicator may briefly show the wrong state (e.g., 'optional' before the threshold loads), confusing users and potentially allowing invalid submissions.
Mitigation & Contingency
Mitigation: Ensure the receipt threshold validator loads and caches the org configuration at app startup or organization selection time, not lazily on form open. Use a loading state in the indicator widget rather than defaulting to 'optional' while configuration is pending.
Contingency: If startup caching is not feasible, treat an unknown threshold as 'receipt required' (fail safe) and surface a clear loading indicator until the configuration resolves, preventing invalid submissions while the config loads.