import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import 'package:geolocator/geolocator.dart'; import 'package:geocoding/geocoding.dart'; import 'comp_6-7.dart'; import 'global_data.dart'; // Formattatore per inserire automaticamente gli slash nella data class DateInputFormatter extends TextInputFormatter { @override TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) { var text = newValue.text.replaceAll('/', ''); if (text.length > 8) text = text.substring(0, 8); var result = ""; for (int i = 0; i < text.length; i++) { if (i == 2 || i == 4) result += "/"; result += text[i]; } return newValue.copyWith(text: result, selection: TextSelection.collapsed(offset: result.length)); } } class Comp1_5Screen extends StatefulWidget { const Comp1_5Screen({super.key}); @override _Comp1_5ScreenState createState() => _Comp1_5ScreenState(); } class _Comp1_5ScreenState extends State { late TextEditingController _data, _ora, _luogo, _testimoni; late bool _feriti, _danniVeicoli, _danniCose; final bool isB = GlobalData.latoCorrente == 'B'; bool _isReady = false; // Per l'orientamento bool _gpsLoading = false; // Per mostrare lo spinner @override void initState() { super.initState(); _forzaVerticaleEInizializza(); } Future _forzaVerticaleEInizializza() async { await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); await Future.delayed(const Duration(milliseconds: 150)); _initControllers(); if (mounted) { setState(() => _isReady = true); // MOSTRA IL POPUP INFORMATIVO APPENA LA SCHERMATA È CARICATA WidgetsBinding.instance.addPostFrameCallback((_) { _mostraInfoPopup(context); }); // Se il luogo è vuoto, provo il GPS automatico in background if (_luogo.text.isEmpty) { _trovaPosizione(silenzioso: true); } } } void _initControllers() { _data = TextEditingController(text: GlobalData.data_incidente); if (_data.text.isEmpty) { _data.text = DateFormat('dd/MM/yyyy').format(DateTime.now()); } _ora = TextEditingController(text: GlobalData.ora); if (_ora.text.isEmpty) { _ora.text = DateFormat('HH:mm').format(DateTime.now()); } _luogo = TextEditingController(text: GlobalData.luogo); _testimoni = TextEditingController(text: GlobalData.testimoni); _feriti = GlobalData.feriti; _danniVeicoli = GlobalData.Veicoli_danni_materiali_oltre; _danniCose = GlobalData.Oggetti_diversi_danni_materiali; } // --- POPUP INFORMATIVO --- void _mostraInfoPopup(BuildContext context) { Color activeColor = isB ? Colors.amber.shade700 : Colors.blue.shade900; showGeneralDialog( context: context, barrierDismissible: false, // Obbliga l'utente a premere "Ho capito" barrierLabel: "Popup", barrierColor: Colors.black.withOpacity(0.5), // Sfondo scuro transitionDuration: const Duration(milliseconds: 400), // Durata della dissolvenza (300 millisecondi) pageBuilder: (context, animation, secondaryAnimation) { return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), title: Row( children: [ Icon(Icons.info_outline, color: activeColor, size: 28), const SizedBox(width: 10), const Expanded(child: Text("Dati Generali", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20))), ], ), content: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ const Text("In questa prima pagina dovrai inserire i dati condivisi dell'incidente (Sezioni 1-5 del modulo).", style: TextStyle(fontSize: 15)), const SizedBox(height: 16), _buildPopupRow(Icons.place, "Luogo", "Usa il mirino GPS per trovare automaticamente l'indirizzo esatto."), const SizedBox(height: 12), _buildPopupRow(Icons.local_hospital, "Feriti", "Indica se ci sono persone che hanno subito lesioni."), const SizedBox(height: 12), _buildPopupRow(Icons.people, "Testimoni", "Se qualcuno ha visto l'incidente, segna i suoi contatti (nome, cognome, telefono)."), ], ), ), actions: [ SizedBox( width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: activeColor, foregroundColor: isB ? Colors.black87 : Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), padding: const EdgeInsets.symmetric(vertical: 14), ), onPressed: () => Navigator.pop(context), child: const Text("HO CAPITO", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), ), ), ], ); }, // QUI GESTIAMO L'ANIMAZIONE DI DISSOLVENZA // NUOVA ANIMAZIONE COMBINATA (FADE + ZOOM) transitionBuilder: (context, animation, secondaryAnimation, child) { var curvePosizione = CurvedAnimation( parent: animation, curve: Curves.easeOutBack, // Quando entra, fa un piccolo "rimbalzo" frenato reverseCurve: Curves.easeInBack, // Quando esce, "prende la rincorsa" e cade giù ); var curveOpacita = CurvedAnimation( parent: animation, curve: Curves.easeOut, reverseCurve: Curves.easeIn, ); return SlideTransition( // Muove il popup sull'asse Y (verticale) position: Tween( begin: const Offset(0.0, 0.4), // Parte dal basso (o cade verso il basso) end: Offset.zero, // Centro esatto ).animate(curvePosizione), child: FadeTransition( opacity: curveOpacita, child: child, ), ); }, ); } Widget _buildPopupRow(IconData icon, String title, String desc) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, size: 24, color: Colors.blueGrey), const SizedBox(width: 12), Expanded( child: RichText( text: TextSpan( style: const TextStyle(fontSize: 14, color: Colors.black87, height: 1.4), children: [ TextSpan(text: "$title: ", style: const TextStyle(fontWeight: FontWeight.bold)), TextSpan(text: desc), ], ), ), ), ], ); } // --- GEOLOCALIZZAZIONE --- Future _trovaPosizione({bool silenzioso = false}) async { if (!silenzioso) setState(() => _gpsLoading = true); try { LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { if (!silenzioso) throw Exception("Permesso negato"); return; } } if (permission == LocationPermission.deniedForever) { if (!silenzioso) throw Exception("Permessi GPS bloccati."); return; } Position position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high ).timeout(const Duration(seconds: 5)); List placemarks = await placemarkFromCoordinates(position.latitude, position.longitude); if (placemarks.isNotEmpty) { Placemark place = placemarks[0]; String via = place.thoroughfare ?? place.street ?? ""; String numero = place.subThoroughfare ?? ""; String citta = place.locality ?? place.subLocality ?? ""; String prov = place.administrativeArea ?? ""; String indirizzoCompleto = "$via $numero, $citta ($prov)".trim(); if (indirizzoCompleto.startsWith(",")) indirizzoCompleto = indirizzoCompleto.substring(1).trim(); if (indirizzoCompleto.isEmpty) { indirizzoCompleto = "${position.latitude}, ${position.longitude}"; } if (mounted) { setState(() { _luogo.text = indirizzoCompleto.toUpperCase(); }); } } } catch (e) { if (!silenzioso && mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text("Errore GPS: ${e.toString().replaceAll('Exception:', '')}"), backgroundColor: Colors.orange, )); } } finally { if (mounted) setState(() => _gpsLoading = false); } } Future _selezionaData(BuildContext context) async { DateTime initialDate = DateTime.now(); if (_data.text.length == 10) { try { List parti = _data.text.split('/'); initialDate = DateTime(int.parse(parti[2]), int.parse(parti[1]), int.parse(parti[0])); } catch (_) {} } final DateTime? picked = await showDatePicker( context: context, initialDate: initialDate, firstDate: DateTime(1990), lastDate: DateTime.now(), // Non si può fare un incidente nel futuro! locale: const Locale('it', 'IT'), ); if (picked != null) { String dataFormattata = "${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}"; setState(() { _data.text = dataFormattata; }); } } void _salvaEProsegui() { if (_data.text.isEmpty || _ora.text.isEmpty || _luogo.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Compila data, ora e luogo!"), backgroundColor: Colors.red)); return; } GlobalData.data_incidente = _data.text; GlobalData.ora = _ora.text; GlobalData.luogo = _luogo.text.toUpperCase(); GlobalData.feriti = _feriti; GlobalData.Veicoli_danni_materiali_oltre = _danniVeicoli; GlobalData.Oggetti_diversi_danni_materiali = _danniCose; GlobalData.testimoni = _testimoni.text.toUpperCase(); Navigator.push(context, MaterialPageRoute(builder: (context) => const Comp6_7Screen())); } @override Widget build(BuildContext context) { Color activeColor = isB ? Colors.amber.shade700 : Colors.blue.shade900; Color bgColor = isB ? const Color(0xFFFFF9C4) : const Color(0xFFE3F2FD); if (!_isReady) { return Scaffold(backgroundColor: bgColor, body: Container()); } return Scaffold( backgroundColor: bgColor, appBar: AppBar( title: const Text("Sez. 1-5: Dati Generali"), backgroundColor: activeColor, foregroundColor: Colors.white, ), body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // CARD 1: DATA E ORA _buildCard( titolo: "1. DATA E ORA", accentColor: activeColor, child: Row(children: [ Expanded( child: _buildField(_data, "Data (GG/MM/AAAA)", Icons.calendar_today, activeColor, isDate: true, customPrefix: InkWell( onTap: () => _selezionaData(context), borderRadius: BorderRadius.circular(20), child: Container( width: 38, alignment: Alignment.center, child: Icon(Icons.calendar_today, size: 20, color: activeColor), ), ), ), ), const SizedBox(width: 15), Expanded(child: _buildField(_ora, "Ora (HH:MM)", Icons.access_time, activeColor)), ]), ), // CARD 2: LUOGO _buildCard( titolo: "2. LUOGO", accentColor: activeColor, child: Row(children: [ Expanded(child: _buildField(_luogo, "Indirizzo / Luogo", Icons.map, activeColor, maxLines: 2)), _gpsLoading ? const Padding(padding: EdgeInsets.all(12), child: SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2))) : IconButton( icon: const Icon(Icons.my_location, color: Colors.red), onPressed: () => _trovaPosizione(silenzioso: false), tooltip: "Usa GPS", ) ]), ), // CARD 3: FERITI _buildCard( titolo: "3. FERITI", accentColor: activeColor, child: _buildSwitch("Ci sono feriti?", _feriti, (v) => setState(() => _feriti = v), activeColor), ), // CARD 4: DANNI MATERIALI _buildCard( titolo: "4. ALTRI DANNI", accentColor: activeColor, child: Column(children: [ _buildSwitch("A veicoli oltre A e B?", _danniVeicoli, (v) => setState(() => _danniVeicoli = v), activeColor), const Divider(), _buildSwitch("A oggetti diversi dai veicoli?", _danniCose, (v) => setState(() => _danniCose = v), activeColor), ]), ), // CARD 5: TESTIMONI _buildCard( titolo: "5. TESTIMONI", accentColor: activeColor, child: _buildField(_testimoni, "Nomi, Indirizzi, Telefoni...", Icons.people, activeColor, maxLines: 3), ), const SizedBox(height: 20), ElevatedButton( onPressed: _salvaEProsegui, style: ElevatedButton.styleFrom( backgroundColor: activeColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 18), textStyle: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: const Text("SALVA E PROSEGUI"), ), const SizedBox(height: 30), ], ), ), ); } // --- WIDGET PERSONALIZZATO NO / SÌ --- Widget _buildSwitch(String label, bool value, Function(bool) onChanged, Color activeColor) { return InkWell( onTap: () => onChanged(!value), // Permette di cliccare su tutta la riga child: Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( children: [ // Etichetta Domanda Expanded( child: Text( label, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), ), ), const SizedBox(width: 8), // Testo NO Text( "NO", style: TextStyle( fontWeight: !value ? FontWeight.bold : FontWeight.normal, color: !value ? Colors.red.shade700 : Colors.grey.shade400, fontSize: 14, ), ), // Switch Fisico Switch( value: value, onChanged: onChanged, activeColor: activeColor, activeTrackColor: activeColor.withOpacity(0.4), inactiveThumbColor: Colors.grey, inactiveTrackColor: Colors.grey.shade300, ), // Testo SÌ Text( "SÌ", style: TextStyle( fontWeight: value ? FontWeight.bold : FontWeight.normal, color: value ? activeColor : Colors.grey.shade400, fontSize: 14, ), ), ], ), ), ); } Widget _buildField(TextEditingController c, String label, IconData i, Color iconColor, {String? hint, int maxLines = 1, bool isDate = false, Widget? customPrefix}) { return TextField( controller: c, maxLines: maxLines, textCapitalization: TextCapitalization.sentences, keyboardType: isDate ? TextInputType.number : TextInputType.text, inputFormatters: isDate ? [DateInputFormatter(), LengthLimitingTextInputFormatter(10)] : [], style: TextStyle(fontSize: isDate ? 14 : 16), // Rimpicciolisce un po' il font se è una data decoration: InputDecoration( labelText: label, hintText: hint, prefixIcon: customPrefix ?? Icon(i, size: 20, color: iconColor), prefixIconConstraints: isDate ? const BoxConstraints(minWidth: 38, minHeight: 38) : null, // Stringe l'icona border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 15), ), ); } Widget _buildCard({required String titolo, required Widget child, required Color accentColor}) { return Container( width: double.infinity, margin: const EdgeInsets.only(bottom: 20), padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(15), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.08), blurRadius: 10, offset: const Offset(0, 4))]), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(titolo, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: accentColor)), const Divider(height: 25, thickness: 1.2), child ]), ); } @override void dispose() { _data.dispose(); _ora.dispose(); _luogo.dispose(); _testimoni.dispose(); super.dispose(); } }