import 'dart:convert'; import 'dart:async'; // Necessario per il Timeout import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; import 'package:http/http.dart' as http; import 'package:url_launcher/url_launcher.dart'; class CarroAttrezziScreen extends StatefulWidget { const CarroAttrezziScreen({super.key}); @override State createState() => _CarroAttrezziScreenState(); } class _CarroAttrezziScreenState extends State { List _officine = []; bool _isLoading = true; String _statusMessage = "Attivazione GPS..."; @override void initState() { super.initState(); _avviaRicercaSicura(); } Future _avviaRicercaSicura() async { // Piccolo ritardo iniziale per dare tempo alla UI di disegnarsi await Future.delayed(const Duration(milliseconds: 500)); if (!mounted) return; _cercaSoccorsiVicini(); } Future _cercaSoccorsiVicini() async { try { // 1. GESTIONE PERMESSI ROBUSTA bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { if (!mounted) return; setState(() { _statusMessage = "Attiva il GPS per trovare i soccorsi."; _isLoading = false; }); return; } LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { if (!mounted) return; setState(() { _statusMessage = "Permesso GPS negato."; _isLoading = false; }); return; } } if (permission == LocationPermission.deniedForever) { if (!mounted) return; setState(() { _statusMessage = "Permessi GPS bloccati permanentemente."; _isLoading = false; }); return; } // 2. CICLO WHILE PER OTTENERE LA POSIZIONE (WAIT FOR GPS) if (mounted) setState(() => _statusMessage = "Ricerca posizione in corso..."); Position? position; int tentativi = 0; const int maxTentativi = 3; // Prova 3 volte prima di arrendersi // FINCHÉ non ho la posizione E non ho superato i tentativi... while (position == null && tentativi < maxTentativi) { try { if (tentativi > 0) { if (mounted) setState(() => _statusMessage = "Aggancio satelliti (Tentativo ${tentativi + 1}/$maxTentativi)..."); } // Prova a prendere la posizione con un timeout di 6 secondi per tentativo position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high, timeLimit: const Duration(seconds: 6), ); } catch (e) { // Se fallisce, aspetta 1 secondo e riprova tentativi++; await Future.delayed(const Duration(seconds: 1)); } } // Se dopo il ciclo while è ancora null, proviamo l'ultima posizione nota if (position == null) { if (mounted) setState(() => _statusMessage = "Segnale debole, uso ultima posizione..."); position = await Geolocator.getLastKnownPosition(); } // Se è ancora null, alziamo bandiera bianca if (position == null) { throw "Impossibile ottenere la posizione GPS dopo vari tentativi."; } // 3. RICERCA SU OVERPASS API (OSM) if (mounted) setState(() => _statusMessage = "Ricerca soccorsi nei paraggi..."); String query = """ [out:json][timeout:25]; ( node["craft"="car_repair"](around:15000,${position.latitude},${position.longitude}); node["shop"="car_repair"](around:15000,${position.latitude},${position.longitude}); node["service"="vehicle_recovery"](around:15000,${position.latitude},${position.longitude}); ); out body; """; final url = Uri.parse('https://overpass-api.de/api/interpreter?data=${Uri.encodeComponent(query)}'); final response = await http.get( url, headers: {'User-Agent': 'CidApp_Flutter/1.0'}, ).timeout(const Duration(seconds: 20)); if (response.statusCode == 200) { final data = json.decode(response.body); List elements = data['elements']; List> listaElaborata = []; for (var element in elements) { if (element['tags'] != null && element['tags']['name'] != null) { double distanzaMetri = Geolocator.distanceBetween( position!.latitude, position.longitude, element['lat'], element['lon'] ); listaElaborata.add({ 'name': element['tags']['name'], 'phone': element['tags']['phone'] ?? element['tags']['contact:phone'] ?? element['tags']['mobile'], 'street': element['tags']['addr:street'] ?? "", 'city': element['tags']['addr:city'] ?? "", 'distance': distanzaMetri, 'lat': element['lat'], 'lon': element['lon'], }); } } listaElaborata.sort((a, b) => (a['distance'] as double).compareTo(b['distance'] as double)); if (mounted) { setState(() { _officine = listaElaborata; _isLoading = false; }); } } else { throw "Errore server OSM: ${response.statusCode}"; } } catch (e) { if (mounted) { setState(() { _statusMessage = "Nessun soccorso trovato o errore GPS.\nRiprova tra poco."; _isLoading = false; }); } } } Future _chiamaNumero(String? numero) async { if (numero == null) return; // Pulisce il numero da spazi o caratteri strani final pulito = numero.replaceAll(RegExp(r'[^0-9+]'), ''); final Uri url = Uri.parse("tel:$pulito"); if (await canLaunchUrl(url)) await launchUrl(url); } Future _apriMappa(double lat, double lon) async { // Link universale per aprire la navigazione final Uri url = Uri.parse("https://www.google.com/maps/search/?api=1&query=$lat,$lon"); try { if (await canLaunchUrl(url)) { await launchUrl(url, mode: LaunchMode.externalApplication); } else { throw 'Impossibile aprire la mappa'; } } catch (e) { debugPrint("Errore apertura mappa: $e"); } } Future _cercaSuGoogle(String nome) async { final Uri url = Uri.parse("https://www.google.com/search?q=soccorso stradale $nome telefono"); if (await canLaunchUrl(url)) await launchUrl(url, mode: LaunchMode.externalApplication); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Soccorso Stradale"), backgroundColor: Colors.red.shade800, foregroundColor: Colors.white, ), body: _isLoading ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const CircularProgressIndicator(color: Colors.red), const SizedBox(height: 20), Text(_statusMessage, style: const TextStyle(color: Colors.grey)), ], ), ) : _officine.isEmpty ? Center( child: Padding( padding: const EdgeInsets.all(20.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.car_crash, size: 60, color: Colors.grey), const SizedBox(height: 10), const Text("Nessun soccorso trovato nei paraggi (15km).", textAlign: TextAlign.center), const SizedBox(height: 20), ElevatedButton( onPressed: () => _cercaSuGoogle("vicino a me"), child: const Text("Cerca su Google"), ) ], ), ), ) : ListView.builder( padding: const EdgeInsets.all(10), itemCount: _officine.length, itemBuilder: (context, index) { final officina = _officine[index]; double km = (officina['distance'] as double) / 1000; String indirizzo = officina['street']; if (officina['city'] != "") indirizzo += (indirizzo.isNotEmpty ? ", " : "") + officina['city']; if (indirizzo.isEmpty) indirizzo = "Posizione GPS"; return Card( elevation: 3, margin: const EdgeInsets.only(bottom: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: ListTile( // KM a sinistra leading: Container( width: 55, height: 55, decoration: BoxDecoration( color: Colors.red.shade50, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.red.shade100) ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.location_on, color: Colors.red.shade700, size: 24), Text("${km.toStringAsFixed(1)} km", style: const TextStyle(fontSize: 10, fontWeight: FontWeight.bold), maxLines: 1), ], ), ), title: Text(officina['name'], style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), subtitle: Text(indirizzo, style: const TextStyle(fontSize: 13), maxLines: 2, overflow: TextOverflow.ellipsis), // DUE TASTI A DESTRA: MAPPA e CHIAMA trailing: Row( mainAxisSize: MainAxisSize.min, children: [ // Tasto MAPPA IconButton( icon: const Icon(Icons.directions, color: Colors.blue, size: 32), onPressed: () => _apriMappa(officina['lat'], officina['lon']), tooltip: "Naviga", ), // Tasto CHIAMA officina['phone'] != null ? IconButton( icon: const Icon(Icons.phone_in_talk, color: Colors.green, size: 32), onPressed: () => _chiamaNumero(officina['phone']), ) : IconButton( icon: const Icon(Icons.search, color: Colors.orange, size: 32), onPressed: () => _cercaSuGoogle(officina['name']), tooltip: "Cerca Web", ), ], ), ), ), ); }, ), ); } }