import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; // Fondamentale per leggere il JSON import 'package:geolocator/geolocator.dart'; import 'package:url_launcher/url_launcher.dart'; class ProntoSoccorsoScreen extends StatefulWidget { const ProntoSoccorsoScreen({super.key}); @override State createState() => _ProntoSoccorsoScreenState(); } class _ProntoSoccorsoScreenState extends State { List _ospedali = []; bool _isLoading = true; String _statusMessage = "Caricamento archivio ospedali..."; Position? _posizioneUtente; @override void initState() { super.initState(); _inizializzaDati(); } Future _inizializzaDati() async { try { // 1. CARICA IL FILE JSON LOCALE // Assicurati che il nome del file in assets sia esattamente questo final String jsonString = await rootBundle.loadString('assets/ospedali_completo.json'); List datiGrezzi = json.decode(jsonString); setState(() => _statusMessage = "Ricerca posizione GPS..."); // 2. CHIEDI PERMESSI E TROVA POSIZIONE Position? posizione = await _determinaPosizione(); if (posizione != null) { _posizioneUtente = posizione; // 3. CALCOLA DISTANZA PER OGNI OSPEDALE for (var ospedale in datiGrezzi) { // Gestione sicura dei numeri (a volte arrivano come stringhe o int) double lat = (ospedale['lat'] is String) ? double.parse(ospedale['lat']) : ospedale['lat'].toDouble(); double lng = (ospedale['lng'] is String) ? double.parse(ospedale['lng']) : ospedale['lng'].toDouble(); double distanzaInMetri = Geolocator.distanceBetween( posizione.latitude, posizione.longitude, lat, lng, ); ospedale['distanza'] = distanzaInMetri; ospedale['lat_num'] = lat; // Salviamo il double pulito per dopo ospedale['lng_num'] = lng; } // 4. ORDINA DAL PIÙ VICINO datiGrezzi.sort((a, b) => (a['distanza'] as double).compareTo(b['distanza'] as double)); // (Opzionale) Prendi solo i primi 50 per non appesantire la lista, o tienili tutti // datiGrezzi = datiGrezzi.take(50).toList(); } if (mounted) { setState(() { _ospedali = datiGrezzi; _isLoading = false; }); } } catch (e) { debugPrint("Errore: $e"); if (mounted) { setState(() { _statusMessage = "Errore: Impossibile caricare gli ospedali.\n$e"; _isLoading = false; }); } } } Future _determinaPosizione() async { bool serviceEnabled; LocationPermission permission; serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) return null; permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) return null; } if (permission == LocationPermission.deniedForever) return null; return await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high); } Future _apriMappa(double lat, double lng) async { // URL universale che funziona su Android e iOS aprendo l'app di mappe predefinita final Uri url = Uri.parse("http://maps.google.com/maps?q=$lat,$lng"); if (!await launchUrl(url, mode: LaunchMode.externalApplication)) { throw Exception('Could not launch $url'); } } Future _chiama112() async { final Uri url = Uri.parse("tel:112"); if (await canLaunchUrl(url)) await launchUrl(url); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Pronto Soccorso Vicini"), backgroundColor: Colors.green.shade800, foregroundColor: Colors.white, ), body: Column( children: [ // HEADER EMERGENZA Container( padding: const EdgeInsets.all(16), color: Colors.red.shade50, child: Row( children: [ Icon(Icons.warning_amber_rounded, color: Colors.red.shade800, size: 30), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("EMERGENZA GRAVE?", style: TextStyle(color: Colors.red.shade900, fontWeight: FontWeight.bold)), const Text("Non andare da solo, chiama il 112."), ], ), ), ElevatedButton( onPressed: _chiama112, style: ElevatedButton.styleFrom(backgroundColor: Colors.red, foregroundColor: Colors.white), child: const Text("CHIAMA 112"), ) ], ), ), Expanded( child: _isLoading ? Center(child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(color: Colors.green.shade800), const SizedBox(height: 20), Text(_statusMessage, textAlign: TextAlign.center), ], )) : _ospedali.isEmpty ? const Center(child: Text("Nessun ospedale trovato nel database.")) : ListView.builder( padding: const EdgeInsets.all(10), itemCount: _ospedali.length, itemBuilder: (context, index) { final ospedale = _ospedali[index]; final double km = (ospedale['distanza'] ?? 0) / 1000; final String livello = ospedale['livello'] ?? ""; // Colore badge livello Color badgeColor = Colors.grey; if (livello.contains("DEA2")) badgeColor = Colors.red.shade700; else if (livello.contains("DEA1")) badgeColor = Colors.orange.shade700; else if (livello.contains("PS")) badgeColor = Colors.green.shade700; return Card( elevation: 3, margin: const EdgeInsets.only(bottom: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), leading: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.green.shade50, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.green.shade100) ), child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.place, color: Colors.green, size: 20), Text("${km.toStringAsFixed(1)} km", style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 11)), ], ), ), title: Text( ospedale['nome'], style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15), maxLines: 2, overflow: TextOverflow.ellipsis, ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), Text("${ospedale['indirizzo']}, ${ospedale['citta']}", style: TextStyle(color: Colors.grey.shade700, fontSize: 13)), const SizedBox(height: 6), if (livello != "nan" && livello.isNotEmpty) Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: badgeColor.withOpacity(0.1), borderRadius: BorderRadius.circular(4), border: Border.all(color: badgeColor.withOpacity(0.3)) ), child: Text( "Livello: $livello", style: TextStyle(color: badgeColor, fontSize: 11, fontWeight: FontWeight.bold), ), ) ], ), trailing: const Icon(Icons.arrow_forward_ios, size: 16, color: Colors.grey), onTap: () { if (ospedale['lat_num'] != null && ospedale['lng_num'] != null) { _apriMappa(ospedale['lat_num'], ospedale['lng_num']); } }, ), ); }, ), ), ], ), ); } }