cid_app/lib/ps.dart

238 lines
No EOL
9.2 KiB
Dart

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<ProntoSoccorsoScreen> createState() => _ProntoSoccorsoScreenState();
}
class _ProntoSoccorsoScreenState extends State<ProntoSoccorsoScreen> {
List<dynamic> _ospedali = [];
bool _isLoading = true;
String _statusMessage = "Caricamento archivio ospedali...";
Position? _posizioneUtente;
@override
void initState() {
super.initState();
_inizializzaDati();
}
Future<void> _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<dynamic> 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<Position?> _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<void> _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<void> _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']);
}
},
),
);
},
),
),
],
),
);
}
}