import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:http/http.dart' as http; const _mapsApiKey = String.fromEnvironment('MAPS_API_KEY', defaultValue: ''); class LocationResult { final LatLng position; final String address; final String city; const LocationResult({required this.position, required this.address, required this.city}); } class LocationPickerDialog extends StatefulWidget { final LatLng? initialPosition; const LocationPickerDialog({super.key, this.initialPosition}); static Future show(BuildContext context, {LatLng? initialPosition}) { return showDialog( context: context, barrierDismissible: false, builder: (_) => LocationPickerDialog(initialPosition: initialPosition), ); } @override State createState() => _LocationPickerDialogState(); } class _LocationPickerDialogState extends State { GoogleMapController? _mapController; LatLng _position = const LatLng(4.6097, -74.0817); // Bogotá por defecto String _address = ''; String _city = ''; bool _loading = true; bool _geocoding = false; @override void initState() { super.initState(); if (widget.initialPosition != null) { _position = widget.initialPosition!; _loading = false; _reverseGeocode(_position); } else { _detectLocation(); } } Future _detectLocation() async { setState(() => _loading = true); try { bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { _useFallback(); return; } LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { _useFallback(); return; } } if (permission == LocationPermission.deniedForever) { _useFallback(); return; } final pos = await Geolocator.getCurrentPosition( locationSettings: const LocationSettings(accuracy: LocationAccuracy.medium, timeLimit: Duration(seconds: 8)), ); _position = LatLng(pos.latitude, pos.longitude); await _reverseGeocode(_position); } catch (_) { _useFallback(); } finally { if (mounted) setState(() => _loading = false); } } void _useFallback() { // Colombia por defecto _position = const LatLng(4.6097, -74.0817); _address = ''; _city = ''; if (mounted) setState(() => _loading = false); } Future _reverseGeocode(LatLng pos) async { if (_mapsApiKey.isEmpty) return; setState(() => _geocoding = true); try { final url = Uri.parse( 'https://maps.googleapis.com/maps/api/geocode/json' '?latlng=${pos.latitude},${pos.longitude}' '&key=$_mapsApiKey' '&language=es', ); final res = await http.get(url); if (res.statusCode == 200) { final data = jsonDecode(res.body); if (data['results'] != null && data['results'].isNotEmpty) { final result = data['results'][0]; _address = result['formatted_address'] ?? ''; _city = _extractCity(result['address_components'] ?? []); } } } catch (_) {} if (mounted) setState(() => _geocoding = false); } String _extractCity(List components) { for (final c in components) { final types = List.from(c['types'] ?? []); if (types.contains('locality') || types.contains('administrative_area_level_2')) { return c['long_name'] ?? ''; } } return ''; } void _onMapTap(LatLng pos) { setState(() => _position = pos); _reverseGeocode(pos); _mapController?.animateCamera(CameraUpdate.newLatLng(pos)); } @override Widget build(BuildContext context) { return Dialog( insetPadding: const EdgeInsets.all(16), child: SizedBox( width: 600, height: 520, child: Column( children: [ // Header Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: const BoxDecoration( gradient: LinearGradient(colors: [Color(0xFF42A4EF), Color(0xFF1565C0)]), borderRadius: BorderRadius.vertical(top: Radius.circular(12)), ), child: Row( children: [ const Icon(Icons.location_on, color: Colors.white, size: 20), const SizedBox(width: 8), const Expanded( child: Text('Selecciona tu ubicación', style: TextStyle(color: Colors.white, fontWeight: FontWeight.w600, fontSize: 15)), ), IconButton( icon: const Icon(Icons.close, color: Colors.white, size: 20), onPressed: () => Navigator.pop(context), padding: EdgeInsets.zero, constraints: const BoxConstraints(), ), ], ), ), // Map Expanded( child: _loading ? const Center(child: CircularProgressIndicator()) : Stack( children: [ GoogleMap( initialCameraPosition: CameraPosition(target: _position, zoom: 15), onMapCreated: (c) => _mapController = c, onTap: _onMapTap, markers: { Marker( markerId: const MarkerId('selected'), position: _position, draggable: true, onDragEnd: (pos) { setState(() => _position = pos); _reverseGeocode(pos); }, ), }, myLocationEnabled: true, myLocationButtonEnabled: true, zoomControlsEnabled: true, ), if (_geocoding) const Positioned( top: 8, left: 0, right: 0, child: Center( child: Card( child: Padding( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), child: Row( mainAxisSize: MainAxisSize.min, children: [ SizedBox(width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2)), SizedBox(width: 8), Text('Obteniendo dirección...', style: TextStyle(fontSize: 12)), ], ), ), ), ), ), ], ), ), // Address preview + confirm Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey[50], border: const Border(top: BorderSide(color: Color(0xFFE0E0E0))), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ if (_address.isNotEmpty) ...[ Text(_address, style: const TextStyle(fontSize: 12, color: Colors.black87)), const SizedBox(height: 8), ], if (_address.isEmpty && !_loading) const Text('Toca el mapa para seleccionar una dirección', style: TextStyle(fontSize: 12, color: Colors.grey)), const SizedBox(height: 4), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: (_loading || _geocoding) ? null : () => Navigator.pop( context, LocationResult(position: _position, address: _address, city: _city), ), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF42A4EF), foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), child: const Text('Confirmar ubicación'), ), ), ], ), ), ], ), ), ); } @override void dispose() { _mapController?.dispose(); super.dispose(); } }