critical priority low complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

The widget renders as a modal dialog that cannot be dismissed by tapping outside or pressing the back/escape key — only via Opt In or Opt Out buttons
The dialog displays a plain-language title (e.g., 'Location sharing for mentor matching'), a body paragraph explaining approximate location collection and its purpose, and a tappable link to the org-specific privacy policy URL from location-privacy-config
The privacy policy URL is fetched from location-privacy-config at widget construction time; if unavailable, the link is hidden and a fallback message is shown
An 'Opt In' primary action button and an 'Opt Out' secondary action button are both visible without scrolling on all supported screen sizes (min 320px width)
Both buttons meet WCAG 2.2 AA minimum touch target size of 44×44 logical pixels
Text contrast ratio meets WCAG 2.2 AA minimum 4.5:1 for body text and 3:1 for large text against the dialog background — verified with a contrast checker
The dialog is fully navigable by screen reader (VoiceOver on iOS, TalkBack on Android): all interactive elements have semantic labels, reading order is logical top-to-bottom, and button roles are announced correctly
Font size respects system font scale (Flutter's MediaQuery.textScaleFactor) and layout reflows correctly at 200% scale without overflow
The widget exposes onOptIn and onOptOut callbacks so the parent widget controls navigation after the choice
Widget is a stateless or minimal-state widget — it does not call the consent service directly; consent logic is handled by the parent via the callbacks
Widget renders correctly in both light and dark mode using design token colors from the project's token system

Technical Requirements

frameworks
Flutter
Riverpod (for reading location-privacy-config)
apis
location-privacy-config provider (privacy policy URL)
data models
accessibility_preferences (for font scale and contrast mode)
performance requirements
Widget builds in under 16ms (one frame) — no async operations inside build()
Privacy policy URL loaded once at widget initialization, not on every rebuild
security requirements
Privacy policy URL must be validated as HTTPS before rendering as a tappable link — reject http:// URLs
Dialog text must not include any PII or identifiers
WillPopScope (or PopScope in Flutter 3.16+) used to intercept back navigation and prevent accidental dismissal
ui components
LocationConsentDialog (stateless modal widget)
AppButton (primary and secondary variants from shared widget library)
PrivacyPolicyLink (tappable URL widget with HTTPS validation)
WillPopScope / PopScope wrapper to block back dismissal

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use Flutter's showDialog with barrierDismissible: false and wrap the dialog content in a PopScope(canPop: false) to prevent all dismissal paths. Do not use AlertDialog directly — build a custom Dialog widget to have full control over padding, button placement, and accessibility semantics. Place the Opt Out button as a TextButton (secondary) and Opt In as an ElevatedButton (primary) using the project's AppButton widget. Fetch the privacy policy URL via a Riverpod provider that reads from location-privacy-config; use .when() to show the link only when the URL is available.

Add Semantics wrappers with explicit labels on the privacy policy link and both buttons. Test on a 320px-wide device profile in flutter_test to ensure no overflow. Keep the widget stateless — lift all state to the parent using the callback pattern to make it easily testable.

Testing Requirements

Widget tests using flutter_test: render the dialog and assert both buttons are present, assert WillPopScope blocks back navigation (simulate pop and verify false returned), assert onOptIn callback fires when Opt In is tapped, assert onOptOut callback fires when Opt Out is tapped, assert privacy policy link is hidden when URL is null. Accessibility tests: use flutter_test's SemanticsHandle to verify all interactive elements have semanticsLabel set. Golden tests for light and dark mode at 1x and 2x text scale. Manual test on a physical device with VoiceOver (iOS) enabled to confirm reading order and button announcements.

Contrast ratio documented in widget test file using computed values.

Component
Location Consent Dialog
ui low
Epic Risks (2)
medium impact medium prob scope

If the privacy policy text or consent terms change after mentors have already opted in, existing consent records may become legally insufficient, requiring re-consent from all opted-in mentors which could temporarily reduce map coverage.

Mitigation & Contingency

Mitigation: Store a consent_version field on every consent record. Implement a consent version check in location-consent-service that compares the stored version against the current policy version from location-privacy-config and flags stale consents for re-consent prompting.

Contingency: If a policy update invalidates existing consents, suppress affected mentors from the map, queue them for re-consent notification via the existing in-app notification system, and restore map visibility only after new consent is recorded.

medium impact medium prob scope

A poorly designed consent dialog may lead to low opt-in rates, reducing map utility for coordinators to the point where the feature delivers insufficient value to justify maintenance cost.

Mitigation & Contingency

Mitigation: Follow plain-language writing guidelines from the cognitive accessibility feature. User-test the dialog with 2-3 peer mentors from Blindeforbundet before implementation is finalised. Ensure the dialog explains the benefit to the mentor, not just the data collection facts.

Contingency: If opt-in rate after launch is below 40%, conduct a targeted usability study and iterate on dialog copy and layout. The coordinator can also send a bulk opt-in invitation notification (per the user story) to non-consenting mentors.