import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:geolocator/geolocator.dart'; import '../../core/region/geo.dart'; import '../../l10n/app_localizations.dart'; import 'location_service.dart'; /// Obtains a position, explaining why before asking. /// /// Google Play requires a prominent disclosure **before** the system permission /// dialog: the user has to be told what is collected and why while they can /// still decline without a dialog in their face. That ordering is the whole /// reason this lives in one function instead of being scattered across the /// screens that need a fix. /// /// Returns null whenever no position is available, having already told the user /// why. Callers fall back to the region centre rather than blocking. Future requestPosition(BuildContext context, WidgetRef ref) async { final l10n = AppLocalizations.of(context); final service = ref.read(locationServiceProvider); var availability = await service.availability(); if (!context.mounted) return null; if (availability == LocationAvailability.serviceDisabled) { _explain( context, l10n.locationStatusServiceDisabled, actionLabel: l10n.locationOpenSystemSettings, onAction: Geolocator.openLocationSettings, ); return null; } if (availability == LocationAvailability.deniedForever) { _explain( context, l10n.locationStatusDeniedForever, actionLabel: l10n.locationOpenSystemSettings, onAction: Geolocator.openAppSettings, ); return null; } if (availability == LocationAvailability.denied) { final shown = ref.read(locationDisclosureShownProvider); if (!shown) { final accepted = await showLocationDisclosure(context); if (!context.mounted) return null; if (!accepted) return null; ref.read(locationDisclosureShownProvider.notifier).markShown(); } availability = await service.request(); if (!context.mounted) return null; if (availability != LocationAvailability.granted) { _explain( context, availability == LocationAvailability.deniedForever ? l10n.locationStatusDeniedForever : l10n.locationStatusDenied, ); return null; } } final point = await service.currentPoint(); if (!context.mounted) return null; if (point == null) { _explain(context, l10n.locationUnavailable); return null; } return point; } /// Shows the prominent disclosure. Returns true if the user chose to continue. Future showLocationDisclosure(BuildContext context) async { final l10n = AppLocalizations.of(context); final accepted = await showDialog( context: context, builder: (context) => AlertDialog( icon: const Icon(Icons.my_location), title: Text(l10n.locationDisclosureTitle), content: SingleChildScrollView(child: Text(l10n.locationDisclosureBody)), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: Text(l10n.locationDisclosureDecline), ), FilledButton( onPressed: () => Navigator.of(context).pop(true), child: Text(l10n.locationDisclosureContinue), ), ], ), ); return accepted ?? false; } void _explain( BuildContext context, String message, { String? actionLabel, VoidCallback? onAction, }) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), duration: const Duration(seconds: 6), action: actionLabel == null || onAction == null ? null : SnackBarAction(label: actionLabel, onPressed: onAction), ), ); }