critical priority medium complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

Tapping Opt In calls location-consent-service.grantConsent(mentorId, orgId) and on success updates the ConsentStatusNotifier to ConsentStatus.granted
Tapping Opt Out calls location-consent-service.recordDenial(mentorId, orgId) and on success updates ConsentStatusNotifier to ConsentStatus.denied
After Opt In, the mentor is navigated to the map feature screen (or next onboarding step); after Opt Out, they are navigated to the home screen with location features hidden
While the grantConsent or recordDenial call is in flight, both action buttons are disabled and a loading indicator is shown inside the tapped button to prevent double-submission
If the service call fails (network error, server error), the dialog remains visible, buttons are re-enabled, and a non-dismissable error snackbar is shown with a retry message
The dialog is shown only once per mentor per org session: a Riverpod provider reads ConsentStatus on app start; if status is granted or denied, the dialog is skipped entirely
If consent is later revoked (via settings), the ConsentStatus resets to pending and the dialog is shown again on next map feature access
Downstream widgets (e.g., map feature button) observe ConsentStatusProvider and hide/disable map access when status is denied or pending
All Riverpod state transitions are covered by unit tests using ProviderContainer
The wiring does not contain business logic — it delegates entirely to location-consent-service and location-consent-dialog callbacks

Technical Requirements

frameworks
Flutter
Riverpod
BLoC (if existing state layer uses BLoC for navigation)
apis
location-consent-service (grantConsent, recordDenial)
consent status API endpoint (task-008)
data models
consent_grants
performance requirements
ConsentStatus read on app start must not block the main thread — use AsyncNotifierProvider with proper loading state
Navigation after consent decision must occur within one frame of the service call completing
security requirements
mentorId and orgId passed to service methods must come from the authenticated JWT claims, not from widget parameters
Do not cache ConsentStatus in local storage (SharedPreferences) — always read from Supabase to prevent stale consent state
Re-consent check must be server-authoritative — local Riverpod state is a cache, not the source of truth
ui components
ConsentStatusNotifier (AsyncNotifier<ConsentStatus>)
ConsentStatusProvider (Riverpod provider)
LocationConsentDialog (from task-006)
Loading indicator inside AppButton
Error snackbar

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Create a ConsentStatusNotifier extending AsyncNotifier that fetches initial status from the consent service on build() and exposes grant() and deny() methods. The parent widget (map feature guard) watches this provider and shows the dialog using showDialog only when status is ConsentStatus.pending. Pass onOptIn and onOptOut callbacks from the parent to the dialog widget — in onOptIn, call notifier.grant(), await it, then navigate. Use GoRouter's redirect or a guard widget rather than imperative navigation to ensure map routes are unreachable when status is not granted.

Avoid storing consent status in flutter_secure_storage — the Supabase database is the authoritative store; the Riverpod provider is just a runtime cache. For the loading state inside buttons, use a local ValueNotifier isLoading rather than lifting loading state to the Riverpod provider to keep the provider's state semantically clean.

Testing Requirements

Unit tests using ProviderContainer: mock location-consent-service, simulate Opt In tap → assert grantConsent called with correct args, assert provider transitions to granted. Simulate Opt Out → assert recordDenial called, provider transitions to denied. Simulate service failure → assert provider stays in previous state, error is surfaced. Widget integration test: render the parent screen with a mock provider, assert dialog is shown when status is pending, assert dialog is NOT shown when status is granted.

Test navigation: use a NavigatorObserver mock to assert correct route pushed after each decision. Test double-tap prevention: simulate rapid double-tap on Opt In and assert grantConsent called exactly once.

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.