import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'comp_9.dart'; import 'global_data.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 Comp8Screen extends StatefulWidget { const Comp8Screen({super.key}); @override _Comp8ScreenState createState() => _Comp8ScreenState(); } class _Comp8ScreenState extends State { late TextEditingController _polizza, _cartaVerde, _validoDal, _validoAl; late TextEditingController _agenzia, _denomAgenzia, _indirizzoAgenzia, _statoAgenzia, _telAgenzia; late TextEditingController _compagniaManuale; String? _selectedAssicurazione; bool _isManuale = false; late bool _danniMaterialiAssicurati; bool get isB => GlobalData.latoCorrente == 'B'; String? _erroreValidoDal; String? _erroreValidoAl; @override void initState() { super.initState(); _initControllers(); WidgetsBinding.instance.addPostFrameCallback((_) { if (_validoDal.text.isNotEmpty) _validaDataDal(_validoDal.text); if (_validoAl.text.isNotEmpty) _validaDataAl(_validoAl.text); // MOSTRA IL POPUP ANIMATO ALL'AVVIO _mostraInfoPopup(context); }); } void _initControllers() { String denominazioneSalvata = isB ? GlobalData.Denominazione_B : GlobalData.Denominazione_A; _polizza = TextEditingController(text: isB ? GlobalData.Numero_Polizza_B : GlobalData.Numero_Polizza_A); _cartaVerde = TextEditingController(text: isB ? GlobalData.N_carta_verde_B : GlobalData.N_carta_verde_A); _validoDal = TextEditingController(text: isB ? GlobalData.Data_Inizio_Dal_B : GlobalData.Data_Inizio_Dal_A); _validoAl = TextEditingController(text: isB ? GlobalData.Data_Scadenza_Al_B : GlobalData.Data_Scadenza_Al_A); _agenzia = TextEditingController(text: isB ? GlobalData.Agenzia_B : GlobalData.Agenzia_A); _denomAgenzia = TextEditingController(text: isB ? GlobalData.Denominazione_agenzia_B : GlobalData.Denominazione_agenzia_A); _indirizzoAgenzia = TextEditingController(text: isB ? GlobalData.Indirizzo_agenzia_B : GlobalData.Indirizzo_agenzia_A); _statoAgenzia = TextEditingController(text: (isB ? GlobalData.Stato_agenzia_B : GlobalData.Stato_agenzia_A).isEmpty ? "ITALIA" : (isB ? GlobalData.Stato_agenzia_B : GlobalData.Stato_agenzia_A)); _telAgenzia = TextEditingController(text: isB ? GlobalData.N_tel_mail_agenzia_B : GlobalData.N_tel_mail_agenzia_A); _danniMaterialiAssicurati = isB ? GlobalData.FLAG_danni_mat_assicurati_B : GlobalData.FLAG_danni_mat_assicurati_A; _compagniaManuale = TextEditingController(); if (denominazioneSalvata.isNotEmpty) { if (GlobalData.assicurazioni.containsKey(denominazioneSalvata)) { _selectedAssicurazione = denominazioneSalvata; _isManuale = (denominazioneSalvata == "ALTRO (Inserimento manuale)"); } else { _selectedAssicurazione = "ALTRO (Inserimento manuale)"; _compagniaManuale.text = denominazioneSalvata; _isManuale = true; } } else { _selectedAssicurazione = null; _isManuale = false; } } // --- 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.business, color: activeColor, size: 28), const SizedBox(width: 10), const Expanded(child: Text("Compagnia di Assicurazione", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18))), ], ), content: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text("Recupera i dati assicurativi per il Veicolo ${GlobalData.latoCorrente}.", style: const TextStyle(fontSize: 15)), const SizedBox(height: 16), _buildPopupRow(Icons.description, "Polizza", "Il numero di polizza e le date di validità sono obbligatorie."), const SizedBox(height: 12), _buildPopupRow(Icons.calendar_today, "Date", "Usa l'icona del calendario per inserire rapidamente la data o scrivila manualmente nel formato GG/MM/AAAA."), const SizedBox(height: 12), _buildPopupRow(Icons.store, "Agenzia", "I dati dell'Agenzia sono facoltativi ma aiutano a velocizzare la pratica."), ], ), ), 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)), ), ), ], ); }, 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), ], ), ), ), ], ); } String? _calcolaErroreData(String data) { if (data.isEmpty) return null; // Se vuoto non diamo errore immediato (ci pensa il tasto Salva) if (data.length < 10) return "Formato: GG/MM/AAAA"; // Avvisa subito se mancano cifre 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 (mese < 1 || mese > 12) return "Mese errato"; int giorniMax = _giorniInMese(mese, anno); if (giorno < 1 || giorno > giorniMax) return "Giorno errato per questo mese"; if (anno < 1900 || anno > 2100) return "Anno non valido"; return null; // La data è perfetta } 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(1990), 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); }); } } void _validaDataDal(String val) => setState(() => _erroreValidoDal = _calcolaErroreData(val)); void _validaDataAl(String val) => setState(() => _erroreValidoAl = _calcolaErroreData(val)); 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; } // --------------------------------- void _salvaTutto() { bool nomeCompagniaMancante = (_selectedAssicurazione == null || (_selectedAssicurazione == "ALTRO (Inserimento manuale)" && _compagniaManuale.text.trim().isEmpty)); // MODIFICA: Rimossi i controlli sui campi Agenzia, Denominazione, Indirizzo, Stato e Telefono if (nomeCompagniaMancante || _polizza.text.trim().isEmpty || _validoDal.text.trim().isEmpty || _validoAl.text.trim().isEmpty) { // MODIFICA: Testo aggiornato ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Compagnia, Polizza e Date sono obbligatorie"), backgroundColor: Colors.red)); return; } if (_erroreValidoDal != null || _erroreValidoAl != null) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Correggi le date prima di proseguire!"), backgroundColor: Colors.red)); return; } if ((_validoDal.text.length < 10) || (_validoAl.text.length < 10)) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Le date devono essere complete (GG/MM/AAAA)"), backgroundColor: Colors.orange)); return; } String denomFinale = (_selectedAssicurazione == "ALTRO (Inserimento manuale)") ? _compagniaManuale.text.toUpperCase() : (_selectedAssicurazione ?? ""); if (isB) { GlobalData.Denominazione_B = denomFinale; GlobalData.Numero_Polizza_B = _polizza.text.toUpperCase(); GlobalData.N_carta_verde_B = _cartaVerde.text.toUpperCase(); GlobalData.Data_Inizio_Dal_B = _validoDal.text; GlobalData.Data_Scadenza_Al_B = _validoAl.text; GlobalData.Agenzia_B = _agenzia.text.toUpperCase(); GlobalData.Denominazione_agenzia_B = _denomAgenzia.text.toUpperCase(); GlobalData.Indirizzo_agenzia_B = _indirizzoAgenzia.text.toUpperCase(); GlobalData.Stato_agenzia_B = _statoAgenzia.text.toUpperCase(); GlobalData.N_tel_mail_agenzia_B = _telAgenzia.text.toUpperCase(); GlobalData.FLAG_danni_mat_assicurati_B = _danniMaterialiAssicurati; } else { GlobalData.Denominazione_A = denomFinale; GlobalData.Numero_Polizza_A = _polizza.text.toUpperCase(); GlobalData.N_carta_verde_A = _cartaVerde.text.toUpperCase(); GlobalData.Data_Inizio_Dal_A = _validoDal.text; GlobalData.Data_Scadenza_Al_A = _validoAl.text; GlobalData.Agenzia_A = _agenzia.text.toUpperCase(); GlobalData.Denominazione_agenzia_A = _denomAgenzia.text.toUpperCase(); GlobalData.Indirizzo_agenzia_A = _indirizzoAgenzia.text.toUpperCase(); GlobalData.Stato_agenzia_A = _statoAgenzia.text.toUpperCase(); GlobalData.N_tel_mail_agenzia_A = _telAgenzia.text.toUpperCase(); GlobalData.FLAG_danni_mat_assicurati_A = _danniMaterialiAssicurati; } Navigator.push(context, MaterialPageRoute(builder: (c) => const Comp9Screen())); } @override Widget build(BuildContext context) { Color mainCol = isB ? Colors.amber.shade700 : Colors.blue.shade900; Color bgCol = isB ? const Color(0xFFFFF9C4) : const Color(0xFFE3F2FD); return GestureDetector( onTap: () => FocusScope.of(context).unfocus(), child: Scaffold( backgroundColor: bgCol, resizeToAvoidBottomInset: true, appBar: AppBar( title: Text('8. Assicurazione (${GlobalData.latoCorrente})'), backgroundColor: mainCol, foregroundColor: isB ? Colors.black : Colors.white, ), // --- LAYOUT SLIVER --- body: SafeArea( child: CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ // 1. Contenuto Scrollabile SliverPadding( padding: const EdgeInsets.all(16), sliver: SliverList( delegate: SliverChildListDelegate([ _buildCard( titolo: "8. IMPRESA DI ASSICURAZIONE", accentColor: mainCol, child: Column( children: [ Padding( padding: const EdgeInsets.only(bottom: 10), child: DropdownButtonFormField( value: _selectedAssicurazione, isExpanded: true, decoration: InputDecoration( labelText: "Seleziona Compagnia *", prefixIcon: Icon(Icons.business, color: isB ? Colors.orange.shade800 : Colors.blue.shade700), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)), filled: true, fillColor: Colors.white, ), items: GlobalData.assicurazioni.keys.map((String key) { return DropdownMenuItem(value: key, child: Text(key, style: const TextStyle(fontSize: 13))); }).toList(), onChanged: (val) { setState(() { _selectedAssicurazione = val; _isManuale = (val == "ALTRO (Inserimento manuale)"); if (!_isManuale) _compagniaManuale.clear(); }); }, ), ), if (_isManuale) _buildField(_compagniaManuale, "Inserisci Nome Compagnia *", Icons.edit_note, mainCol), _buildField(_polizza, "N. di polizza *", Icons.description, mainCol), _buildField(_cartaVerde, "N. di Carta Verde (Opz)", Icons.language, mainCol), Row(children: [ Expanded( child: _buildField(_validoDal, "Valida dal *", Icons.date_range, mainCol, isDate: true, errorText: _erroreValidoDal, onChanged: _validaDataDal, // USIAMO INKWELL PER STRINGERE I MARGINI customPrefix: InkWell( onTap: () => _selezionaData(context, _validoDal, _validaDataDal), borderRadius: BorderRadius.circular(20), child: Container( width: 38, alignment: Alignment.center, child: Icon(Icons.calendar_today, size: 20, color: isB ? Colors.orange.shade800 : Colors.blue.shade700), ), ), ), ), const SizedBox(width: 10), Expanded( child: _buildField(_validoAl, "Fino al *", Icons.event_available, mainCol, isDate: true, errorText: _erroreValidoAl, onChanged: _validaDataAl, // USIAMO INKWELL PER STRINGERE I MARGINI customPrefix: InkWell( onTap: () => _selezionaData(context, _validoAl, _validaDataAl), borderRadius: BorderRadius.circular(20), child: Container( width: 38, alignment: Alignment.center, child: Icon(Icons.calendar_today, size: 20, color: isB ? Colors.orange.shade800 : Colors.blue.shade700), ), ), ), ), ]), ], ), ), _buildCard( titolo: "AGENZIA (OPZIONALE)", // Modificato titolo per chiarezza accentColor: mainCol, child: Column( children: [ // MODIFICA: Rimossi asterischi dalle label _buildField(_agenzia, "Ufficio/Agenzia", Icons.store, mainCol), _buildField(_denomAgenzia, "Denominazione", Icons.info_outline, mainCol), _buildField(_indirizzoAgenzia, "Indirizzo", Icons.place, mainCol), Row(children: [ Expanded(child: _buildField(_statoAgenzia, "Stato", Icons.flag, mainCol)), const SizedBox(width: 10), Expanded(child: _buildField(_telAgenzia, "Tel. / E-mail", Icons.contact_phone, mainCol)), ]), ], ), ), _buildCard( titolo: "DANNI PROPRI", accentColor: mainCol, child: _buildSwitch( "La polizza copre anche i danni materiali al proprio veicolo?", _danniMaterialiAssicurati, (val) => setState(() => _danniMaterialiAssicurati = val), mainCol ), ), ]), ), ), // 2. Piè di pagina elastico SliverFillRemaining( hasScrollBody: false, child: Align( alignment: Alignment.bottomCenter, child: Padding( padding: const EdgeInsets.all(16), child: _navButtons(context, mainCol), ), ), ), ], ), ), ), ); } // --- NUOVO WIDGET SWITCH PERSONALIZZATO (NO / SÌ) --- Widget _buildSwitch(String label, bool value, Function(bool) onChanged, Color activeColor) { return InkWell( onTap: () => onChanged(!value), child: Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( children: [ Expanded( child: Text( label, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), ), ), const SizedBox(width: 8), Text( "NO", style: TextStyle( fontWeight: !value ? FontWeight.bold : FontWeight.normal, color: !value ? Colors.red.shade700 : Colors.grey.shade400, fontSize: 14, ), ), Switch( value: value, onChanged: onChanged, activeColor: activeColor, activeTrackColor: activeColor.withOpacity(0.4), inactiveThumbColor: Colors.grey, inactiveTrackColor: Colors.grey.shade300, ), Text( "SÌ", style: TextStyle( fontWeight: value ? FontWeight.bold : FontWeight.normal, color: value ? activeColor : Colors.grey.shade400, fontSize: 14, ), ), ], ), ), ); } Widget _buildField(TextEditingController controller, String label, IconData icon, Color iconColor, {bool isDate = false, String? errorText, Function(String)? onChanged, Widget? customPrefix}) { return Padding( padding: const EdgeInsets.only(bottom: 10), child: TextField( controller: controller, keyboardType: isDate ? TextInputType.number : TextInputType.text, inputFormatters: isDate ? [FilteringTextInputFormatter.digitsOnly, DateInputFormatter()] : [], textCapitalization: TextCapitalization.characters, onChanged: onChanged, // Rimpiccioliamo leggermente il testo se è una data per farlo stare più comodo style: TextStyle(fontSize: isDate ? 14 : 16), decoration: InputDecoration( labelText: label, hintText: isDate ? "GG/MM/AAAA" : null, errorText: errorText, errorStyle: const TextStyle(fontSize: 10), // Rende il testo di errore più compatto prefixIcon: customPrefix ?? Icon(icon, size: 20, color: isB ? Colors.orange.shade800 : Colors.blue.shade700), // --- IL TRUCCO È QUI: Diciamo all'icona di occupare meno spazio --- prefixIconConstraints: const BoxConstraints(minWidth: 38, minHeight: 38), contentPadding: const EdgeInsets.only(left: 0, right: 8, top: 15, bottom: 15), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)), filled: true, fillColor: Colors.white, ), ), ); } Widget _buildCard({required String titolo, required Widget child, required Color accentColor}) { return Card( elevation: 2, margin: const EdgeInsets.only(bottom: 16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: Padding( padding: const EdgeInsets.all(16), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(titolo, style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold, color: accentColor)), const Divider(height: 20), child, ]), ), ); } Widget _navButtons(BuildContext context, Color color) { return Row( children: [ Expanded(child: OutlinedButton(onPressed: () => Navigator.pop(context), style: OutlinedButton.styleFrom(minimumSize: const Size(0, 55)), child: const Text("INDIETRO"))), const SizedBox(width: 15), Expanded(flex: 2, child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: color, foregroundColor: isB ? Colors.black : Colors.white, minimumSize: const Size(0, 55)), onPressed: _salvaTutto, child: const Text("SALVA E PROSEGUI"), )), ], ); } @override void dispose() { _polizza.dispose(); _cartaVerde.dispose(); _validoDal.dispose(); _validoAl.dispose(); _agenzia.dispose(); _denomAgenzia.dispose(); _indirizzoAgenzia.dispose(); _statoAgenzia.dispose(); _telAgenzia.dispose(); _compagniaManuale.dispose(); super.dispose(); } }