// Versione: 2.3.5 - Popup Automatico + Calendari Cliccabili import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'global_data.dart'; import 'comp_10.dart'; 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 Comp9Screen extends StatefulWidget { const Comp9Screen({super.key}); @override _Comp9ScreenState createState() => _Comp9ScreenState(); } class _Comp9ScreenState extends State { late TextEditingController _cognome, _nome, _cf, _nascita, _indirizzo, _stato, _tel, _patente, _categoriaAltro, _scadenza; String _selectedCat = "B"; String? _erroreNascita; String? _erroreScadenza; bool get isB => GlobalData.latoCorrente == 'B'; @override void initState() { super.initState(); _initControllers(); // Appena la pagina è costruita, lancia il controllo per i popup WidgetsBinding.instance.addPostFrameCallback((_) { // PRIMA mostra le info della pagina... _mostraInfoPopup(context); // ...POI controlla se ci sono date pre-compilate da validare if (_nascita.text.isNotEmpty) _validaNascita(_nascita.text); if (_scadenza.text.isNotEmpty) _validaScadenza(_scadenza.text); }); } void _initControllers() { if (isB) { _cognome = TextEditingController(text: GlobalData.Cognome_cond_B); _nome = TextEditingController(text: GlobalData.Nome_cond_B); _cf = TextEditingController(text: GlobalData.Cod_fiscale_cond_B); _nascita = TextEditingController(text: GlobalData.Data_nascita_cond_B); _indirizzo = TextEditingController(text: GlobalData.Indirizzo_cond_B); _stato = TextEditingController(text: GlobalData.Stato_cond_B.isEmpty ? "ITALIA" : GlobalData.Stato_cond_B); _tel = TextEditingController(text: GlobalData.N_tel_mail_cond_B); _patente = TextEditingController(text: GlobalData.N_Patente_cond_B); _scadenza = TextEditingController(text: GlobalData.Scadenza_cond_B); _setupCategoria(GlobalData.Categoria_cond_B); } else { _cognome = TextEditingController(text: GlobalData.Cognome_cond_A); _nome = TextEditingController(text: GlobalData.Nome_cond_A); _cf = TextEditingController(text: GlobalData.Cod_fiscale_cond_A); _nascita = TextEditingController(text: GlobalData.Data_nascita_cond_A); _indirizzo = TextEditingController(text: GlobalData.Indirizzo_cond_A); _stato = TextEditingController(text: GlobalData.Stato_cond_A.isEmpty ? "ITALIA" : GlobalData.Stato_cond_A); _tel = TextEditingController(text: GlobalData.N_tel_mail_cond_A); _patente = TextEditingController(text: GlobalData.N_Patente_cond_A); _scadenza = TextEditingController(text: GlobalData.Scadenza_cond_A); _setupCategoria(GlobalData.Categoria_cond_A); } } // --- POPUP INFORMATIVO ANIMATO --- void _mostraInfoPopup(BuildContext context) { Color activeColor = isB ? Colors.amber.shade700 : Colors.blue.shade900; showGeneralDialog( context: context, barrierDismissible: false, barrierLabel: "Popup", barrierColor: Colors.black.withOpacity(0.5), transitionDuration: const Duration(milliseconds: 400), pageBuilder: (context, animation, secondaryAnimation) { return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), title: Row( children: [ Icon(Icons.person_pin, color: activeColor, size: 28), const SizedBox(width: 10), const Expanded(child: Text("Dati Conducente", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18))), ], ), content: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text("In questa sezione inseriremo i dati della persona che era alla guida del Veicolo ${GlobalData.latoCorrente}.", style: const TextStyle(fontSize: 15)), const SizedBox(height: 16), _buildPopupRow(Icons.file_download, "Importazione", "Se il conducente è la stessa persona del contraente (l'assicurato), potrai importare i suoi dati in un tocco."), const SizedBox(height: 12), _buildPopupRow(Icons.badge, "Patente", "Assicurati di inserire correttamente il numero, la categoria e la data di scadenza della patente di guida."), ], ), ), 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); // Chiude l'info popup // Dopo aver chiuso questo, controlla se deve mostrare l'altro popup per l'importazione _mostraDialogoImportazione(); }, child: const Text("HO CAPITO", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), ), ), ], ); }, transitionBuilder: (context, animation, secondaryAnimation, child) { var curvePosizione = CurvedAnimation(parent: animation, curve: Curves.easeOutBack, reverseCurve: Curves.easeInBack); var curveOpacita = CurvedAnimation(parent: animation, curve: Curves.easeOut, reverseCurve: Curves.easeIn); return SlideTransition(position: Tween(begin: const Offset(0.0, 0.4), end: Offset.zero).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), ], ), ), ), ], ); } // LOGICA POPUP AUTOMATICO void _mostraDialogoImportazione() { // 1. Se i campi sono già compilati (es. torno indietro per modificare), NON mostrare nulla if (_cognome.text.trim().isNotEmpty && _nome.text.trim().isNotEmpty) return; // 2. Controllo se esistono i dati del contraente da importare String contraenteNome = isB ? GlobalData.Nome_contraente_B : GlobalData.Nome_contraente_A; String contraenteCognome = isB ? GlobalData.Cognome_contraente_B : GlobalData.Cognome_contraente_A; if (contraenteNome.isEmpty && contraenteCognome.isEmpty) return; // 3. Mostra il Popup showDialog( context: context, barrierDismissible: false, // L'utente deve fare una scelta builder: (ctx) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), title: Row( children: [ Icon(Icons.person_add_alt_1, color: isB ? Colors.amber[800] : Colors.blue[800]), const SizedBox(width: 10), const Text("Conducente"), ], ), content: Text( "Il conducente è la stessa persona del contraente ($contraenteNome $contraenteCognome)?\n\nVuoi usare i suoi dati?", style: const TextStyle(fontSize: 16), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: Text("NO, scrivo a mano", style: TextStyle(color: Colors.grey[600])), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: isB ? Colors.amber[700] : Colors.blue.shade900, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), onPressed: () { _importaDati(); Navigator.pop(ctx); }, child: const Text("SÌ, importa"), ), ], ), ); } void _importaDati() { setState(() { if (isB) { _cognome.text = GlobalData.Cognome_contraente_B; _nome.text = GlobalData.Nome_contraente_B; _cf.text = GlobalData.Codice_Fiscale_contraente_B; String addr = GlobalData.Indirizzo_contraente_B; if (GlobalData.CAP_contraente_B.isNotEmpty) addr += " ${GlobalData.CAP_contraente_B}"; _indirizzo.text = addr.trim(); _stato.text = GlobalData.Stato_contraente_B.isNotEmpty ? GlobalData.Stato_contraente_B : "ITALIA"; _tel.text = GlobalData.N_telefono_mail_contraente_B; } else { _cognome.text = GlobalData.Cognome_contraente_A; _nome.text = GlobalData.Nome_contraente_A; _cf.text = GlobalData.Codice_Fiscale_contraente_A; String addr = GlobalData.Indirizzo_contraente_A; if (GlobalData.CAP_contraente_A.isNotEmpty) addr += " ${GlobalData.CAP_contraente_A}"; _indirizzo.text = addr.trim(); _stato.text = GlobalData.Stato_contraente_A.isNotEmpty ? GlobalData.Stato_contraente_A : "ITALIA"; _tel.text = GlobalData.N_telefono_mail_contraente_A; } }); } void _setupCategoria(String catEsistente) { if (["A", "B", "C", "D", "E"].contains(catEsistente)) { _selectedCat = catEsistente; _categoriaAltro = TextEditingController(); } else if (catEsistente.isNotEmpty) { _selectedCat = "Altro"; _categoriaAltro = TextEditingController(text: catEsistente); } else { _selectedCat = "B"; _categoriaAltro = TextEditingController(); } } // --- LOGICA CALENDARIO --- Future _selezionaData(BuildContext context, TextEditingController controller, Function(String) onChanged) async { DateTime initialDate = DateTime.now(); if (controller.text.length == 10) { try { List parti = controller.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(1900), // Anno minimo sensato per la nascita lastDate: DateTime(2100), 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(() { controller.text = dataFormattata; onChanged(dataFormattata); }); } } String? _calcolaErroreData(String data) { if (data.isEmpty) return null; if (data.length < 10) return "Formato: GG/MM/AAAA"; List parti = data.split('/'); if (parti.length != 3) return "Formato errato"; int? giorno = int.tryParse(parti[0]); int? mese = int.tryParse(parti[1]); int? anno = int.tryParse(parti[2]); if (giorno == null || mese == null || anno == null) return "Data non valida"; if (giorno < 1 || giorno > 31) return "Giorno errato"; if (mese < 1 || mese > 12) return "Mese errato"; int giorniMax = _giorniInMese(mese, anno); if (giorno > giorniMax) { String nomeMese = _getNomeMese(mese); return "$nomeMese $anno ha solo $giorniMax gg"; } return null; } int _giorniInMese(int mese, int anno) { if (mese == 2) { bool bisestile = (anno % 4 == 0 && anno % 100 != 0) || (anno % 400 == 0); return bisestile ? 29 : 28; } if ([4, 6, 9, 11].contains(mese)) return 30; return 31; } String _getNomeMese(int mese) { const mesi = ["Gen", "Feb", "Mar", "Apr", "Mag", "Giu", "Lug", "Ago", "Set", "Ott", "Nov", "Dic"]; if (mese >= 1 && mese <= 12) return mesi[mese - 1]; return "Mese"; } void _validaNascita(String val) => setState(() => _erroreNascita = _calcolaErroreData(val)); void _validaScadenza(String val) => setState(() => _erroreScadenza = _calcolaErroreData(val)); void _salvaEProsegui() { String catFinale = (_selectedCat == "Altro") ? _categoriaAltro.text.toUpperCase() : _selectedCat; bool categoriaMancante = (_selectedCat == "Altro" && _categoriaAltro.text.trim().isEmpty); if (_cognome.text.trim().isEmpty || _nome.text.trim().isEmpty || _cf.text.trim().isEmpty || _nascita.text.trim().isEmpty || _indirizzo.text.trim().isEmpty || _stato.text.trim().isEmpty || _patente.text.trim().isEmpty || _scadenza.text.trim().isEmpty || categoriaMancante) { _mostraErrore("Tutti i dati del conducente sono obbligatori!", Colors.red); return; } if (_erroreNascita != null || _erroreScadenza != null) { _mostraErrore("Correggi le date in rosso!", Colors.red); return; } if (_nascita.text.length < 10 || _scadenza.text.length < 10) { _mostraErrore("Formato data incompleto (GG/MM/AAAA)", Colors.orange); return; } if (isB) { GlobalData.Cognome_cond_B = _cognome.text.toUpperCase(); GlobalData.Nome_cond_B = _nome.text.toUpperCase(); GlobalData.Cod_fiscale_cond_B = _cf.text.toUpperCase(); GlobalData.Data_nascita_cond_B = _nascita.text; GlobalData.Indirizzo_cond_B = _indirizzo.text.toUpperCase(); GlobalData.Stato_cond_B = _stato.text.toUpperCase(); GlobalData.N_tel_mail_cond_B = _tel.text.toUpperCase(); GlobalData.N_Patente_cond_B = _patente.text.toUpperCase(); GlobalData.Categoria_cond_B = catFinale; GlobalData.Scadenza_cond_B = _scadenza.text; } else { GlobalData.Cognome_cond_A = _cognome.text.toUpperCase(); GlobalData.Nome_cond_A = _nome.text.toUpperCase(); GlobalData.Cod_fiscale_cond_A = _cf.text.toUpperCase(); GlobalData.Data_nascita_cond_A = _nascita.text; GlobalData.Indirizzo_cond_A = _indirizzo.text.toUpperCase(); GlobalData.Stato_cond_A = _stato.text.toUpperCase(); GlobalData.N_tel_mail_cond_A = _tel.text.toUpperCase(); GlobalData.N_Patente_cond_A = _patente.text.toUpperCase(); GlobalData.Categoria_cond_A = catFinale; GlobalData.Scadenza_cond_A = _scadenza.text; } Navigator.push(context, MaterialPageRoute(builder: (c) => const Comp10Screen())); } void _mostraErrore(String msg, Color colore) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(msg), backgroundColor: colore, duration: const Duration(seconds: 3)), ); } @override Widget build(BuildContext context) { Color accentColor = isB ? Colors.amber.shade700 : Colors.blue.shade900; Color bgColor = isB ? const Color(0xFFFFF9C4) : const Color(0xFFE3F2FD); return GestureDetector( onTap: () => FocusScope.of(context).unfocus(), child: Scaffold( backgroundColor: bgColor, resizeToAvoidBottomInset: true, appBar: AppBar( title: Text("9. Conducente (${GlobalData.latoCorrente})"), backgroundColor: accentColor, foregroundColor: isB ? Colors.black : Colors.white, ), body: SafeArea( child: SingleChildScrollView( physics: const BouncingScrollPhysics(), padding: const EdgeInsets.all(16), child: Column( children: [ _buildSectionCard( titolo: "DATI PERSONALI", accentColor: accentColor, children: [ _buildTextField(_cognome, "Cognome *", Icons.person, accentColor), _buildTextField(_nome, "Nome *", Icons.person_outline, accentColor), // --- MODIFICATO: DATA DI NASCITA --- _buildTextField(_nascita, "Data di Nascita *", Icons.cake, accentColor, isDate: true, hint: "GG/MM/AAAA", errorText: _erroreNascita, onChanged: _validaNascita, customPrefix: InkWell( onTap: () => _selezionaData(context, _nascita, _validaNascita), borderRadius: BorderRadius.circular(20), child: Container( width: 38, alignment: Alignment.center, child: Icon(Icons.cake, size: 20, color: accentColor), ), ), ), _buildTextField(_cf, "Codice Fiscale *", Icons.badge, accentColor, isUpper: true), _buildTextField(_indirizzo, "Indirizzo (Via, Cap, Città) *", Icons.home, accentColor), _buildTextField(_stato, "Stato *", Icons.flag, accentColor), _buildTextField(_tel, "Tel/Email *", Icons.contact_mail, accentColor), ], ), _buildSectionCard( titolo: "PATENTE DI GUIDA", accentColor: accentColor, children: [ _buildTextField(_patente, "N. Patente *", Icons.credit_card, accentColor, isUpper: true), Padding( padding: const EdgeInsets.only(bottom: 12), child: DropdownButtonFormField( value: _selectedCat, decoration: InputDecoration( labelText: "Categoria *", prefixIcon: Icon(Icons.category, color: accentColor), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)), filled: true, fillColor: Colors.white, ), items: ["A", "B", "C", "D", "E", "Altro"].map((c) => DropdownMenuItem(value: c, child: Text(c))).toList(), onChanged: (v) => setState(() => _selectedCat = v!), ), ), if (_selectedCat == "Altro") _buildTextField(_categoriaAltro, "Specifica Categoria *", Icons.edit, accentColor, isUpper: true), // --- MODIFICATO: SCADENZA PATENTE --- _buildTextField(_scadenza, "Valida fino al *", Icons.event_available, accentColor, isDate: true, hint: "GG/MM/AAAA", errorText: _erroreScadenza, onChanged: _validaScadenza, customPrefix: InkWell( onTap: () => _selezionaData(context, _scadenza, _validaScadenza), borderRadius: BorderRadius.circular(20), child: Container( width: 38, alignment: Alignment.center, child: Icon(Icons.event_available, size: 20, color: accentColor), ), ), ), ], ), _navButtons(accentColor), const SizedBox(height: 10), ], ), ), ), ), ); } // --- MODIFICATO IL METODO PER ACCETTARE customPrefix E RESTRINGERE LA UI --- Widget _buildTextField(TextEditingController c, String label, IconData icon, Color color, {bool isDate = false, bool isUpper = false, bool isNumeric = false, String? hint, String? errorText, Function(String)? onChanged, Widget? customPrefix}) { return Padding( padding: const EdgeInsets.only(bottom: 12), child: TextField( controller: c, keyboardType: isDate || isNumeric ? TextInputType.number : TextInputType.text, textCapitalization: isUpper ? TextCapitalization.characters : TextCapitalization.sentences, onChanged: onChanged, style: TextStyle(fontSize: isDate ? 14 : 16), // Rimpicciolisce un po' le date inputFormatters: [ if (isDate) DateInputFormatter(), if (isDate) LengthLimitingTextInputFormatter(10), if (isNumeric) FilteringTextInputFormatter.digitsOnly, if (isNumeric) LengthLimitingTextInputFormatter(5), ], decoration: InputDecoration( labelText: label, hintText: hint, errorText: errorText, errorStyle: const TextStyle(fontSize: 10), prefixIcon: customPrefix ?? Icon(icon, color: color), prefixIconConstraints: isDate ? const BoxConstraints(minWidth: 38, minHeight: 38) : null, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 15), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)), filled: true, fillColor: Colors.white, ), ), ); } Widget _buildSectionCard({required String titolo, required List children, required Color accentColor}) => 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.05), blurRadius: 10)]), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(titolo, style: TextStyle(fontWeight: FontWeight.bold, color: accentColor, fontSize: 17)), const Divider(height: 25), ...children, ]), ); Widget _navButtons(Color btnColor) => Row(children: [ Expanded(flex: 4, child: OutlinedButton(onPressed: () => Navigator.pop(context), style: OutlinedButton.styleFrom(minimumSize: const Size(0, 55), side: BorderSide(color: btnColor, width: 1.5), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))), child: const FittedBox(child: Text("INDIETRO", style: TextStyle(fontWeight: FontWeight.bold))))), const SizedBox(width: 15), Expanded(flex: 7, child: ElevatedButton(style: ElevatedButton.styleFrom(backgroundColor: btnColor, foregroundColor: isB ? Colors.black : Colors.white, minimumSize: const Size(0, 55), elevation: 5, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))), onPressed: _salvaEProsegui, child: const FittedBox(child: Text("SALVA E PROSEGUI", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16))))), ]); @override void dispose() { _cognome.dispose(); _nome.dispose(); _cf.dispose(); _nascita.dispose(); _indirizzo.dispose(); _stato.dispose(); _tel.dispose(); _patente.dispose(); _categoriaAltro.dispose(); _scadenza.dispose(); super.dispose(); } }