import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'comp_8.dart'; import 'global_data.dart'; // Formattatore per forzare il maiuscolo mentre si scrive (Versione sicura per iOS/Android) class UpperCaseTextFormatter extends TextInputFormatter { @override TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) { return TextEditingValue( text: newValue.text.toUpperCase(), selection: newValue.selection, composing: newValue.composing, // Mantiene intatta la memoria della tastiera! ); } } class Comp6_7Screen extends StatefulWidget { const Comp6_7Screen({super.key}); @override _Comp6_7ScreenState createState() => _Comp6_7ScreenState(); } class _Comp6_7ScreenState extends State { late TextEditingController _cognome, _nome, _cf, _indirizzo, _cap, _stato, _tel; late TextEditingController _marca, _rimorchio, _targa, _statoImm, _statoImm2; // Il tuo FocusNode originale late FocusNode _cfFocusNode; final bool isB = GlobalData.latoCorrente == 'B'; bool _isReady = false; @override void initState() { super.initState(); // Ripristino del tuo listener originale sull'uscita dal campo _cfFocusNode = FocusNode(); _cfFocusNode.addListener(_onCfFocusChange); _forzaVerticaleEInizializza(); } // La tua funzione originale per l'uscita dal campo (Aggiornata per P.IVA) void _onCfFocusChange() { if (!_cfFocusNode.hasFocus) { String cfInserito = _cf.text.trim().toUpperCase(); if (cfInserito.isNotEmpty) { bool isPIVA = RegExp(r"^[0-9]{11}$").hasMatch(cfInserito); if (!isPIVA && !_isCodiceFiscaleValido(cfInserito)) { ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Cod. Fiscale / P. IVA NON VALIDO (controlla i caratteri inseriti!)."), backgroundColor: Colors.orange, duration: Duration(seconds: 3), ) ); } } } } // La tua funzione originale per il controllo in tempo reale void _controllaCfInTempoReale(String value) { String cfScritto = value.trim().toUpperCase(); if (cfScritto.length == 16) { if (!_isCodiceFiscaleValido(cfScritto)) { // NON TOGLIAMO IL FOCUS QUI! Così l'incolla e l'autofill non si rompono. ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("⚠️ Codice Fiscale NON VALIDO (controlla i caratteri inseriti!)."), backgroundColor: Colors.orange, duration: Duration(seconds: 3), ) ); } } } Future _forzaVerticaleEInizializza() async { await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); await Future.delayed(const Duration(milliseconds: 200)); _initControllers(); if (!mounted) return; setState(() => _isReady = true); // Aspettiamo che il frame (la UI visiva) sia completamente disegnato per gestire i popup WidgetsBinding.instance.addPostFrameCallback((_) { _gestisciCatenaPopup(); }); } // --- SINCRONIZZAZIONE DEI POPUP --- Future _gestisciCatenaPopup() async { final prefs = await SharedPreferences.getInstance(); String? sCognome = prefs.getString('user_cognome'); debugPrint("🤖 [AUTOFILL] Dati trovati in memoria: Cognome = $sCognome"); debugPrint("🤖 [AUTOFILL] Testo attuale nel controller: '${_cognome.text}'"); // 1. POPUP AUTOCOMPLETAMENTO LOCALE (se ci sono dati e i campi sono vuoti) if (sCognome != null && sCognome.trim().isNotEmpty && _cognome.text.trim().isEmpty) { debugPrint("🤖 [AUTOFILL] Mostro il popup dei dati salvati!"); await _mostraDialogoAutocompletamento(prefs); } else { debugPrint("🤖 [AUTOFILL] Condizioni non soddisfatte, salto il popup dati."); } // 2. POPUP INFORMATIVO STANDARD (Appare sempre DOPO) if (mounted) { debugPrint("🤖 [AUTOFILL] Mostro il popup informativo standard."); _mostraInfoPopup(context); } } void _initControllers() { String pulisciBarre(String valore, String patternBarre) { if (valore.contains("/")) return ""; return valore; } if (isB) { _cognome = TextEditingController(text: GlobalData.Cognome_contraente_B); _nome = TextEditingController(text: GlobalData.Nome_contraente_B); _cf = TextEditingController(text: GlobalData.Codice_Fiscale_contraente_B); _indirizzo = TextEditingController(text: GlobalData.Indirizzo_contraente_B); _cap = TextEditingController(text: GlobalData.CAP_contraente_B); _stato = TextEditingController(text: GlobalData.Stato_contraente_B.isEmpty ? "ITALIA" : GlobalData.Stato_contraente_B); _tel = TextEditingController(text: GlobalData.N_telefono_mail_contraente_B); _marca = TextEditingController(text: GlobalData.Marca_e_Tipo_B); _targa = TextEditingController(text: GlobalData.Targa_B); _statoImm = TextEditingController(text: GlobalData.Stato_immatricolazione_B.isEmpty ? "ITALIA" : GlobalData.Stato_immatricolazione_B); _rimorchio = TextEditingController(text: pulisciBarre(GlobalData.Rimorchio_B, "/")); _statoImm2 = TextEditingController(text: pulisciBarre(GlobalData.Stato_immatricolazione2_B, "/")); } else { _cognome = TextEditingController(text: GlobalData.Cognome_contraente_A); _nome = TextEditingController(text: GlobalData.Nome_contraente_A); _cf = TextEditingController(text: GlobalData.Codice_Fiscale_contraente_A); _indirizzo = TextEditingController(text: GlobalData.Indirizzo_contraente_A); _cap = TextEditingController(text: GlobalData.CAP_contraente_A); _stato = TextEditingController(text: GlobalData.Stato_contraente_A.isEmpty ? "ITALIA" : GlobalData.Stato_contraente_A); _tel = TextEditingController(text: GlobalData.N_telefono_mail_contraente_A); _marca = TextEditingController(text: GlobalData.Marca_e_Tipo_A); _targa = TextEditingController(text: GlobalData.Targa_A); _statoImm = TextEditingController(text: GlobalData.Stato_immatricolazione_A.isEmpty ? "ITALIA" : GlobalData.Stato_immatricolazione_A); _rimorchio = TextEditingController(text: pulisciBarre(GlobalData.Rimorchio_A, "/")); _statoImm2 = TextEditingController(text: pulisciBarre(GlobalData.Stato_immatricolazione2_A, "/")); } } // --- POPUP: CHIEDE SE USARE I DATI SALVATI IN PRECEDENZA --- Future _mostraDialogoAutocompletamento(SharedPreferences prefs) async { String sCognome = prefs.getString('user_cognome') ?? ''; String sNome = prefs.getString('user_nome') ?? ''; String sCF = prefs.getString('user_cf') ?? ''; String sIndirizzo = prefs.getString('user_indirizzo') ?? ''; String sCap = prefs.getString('user_cap') ?? ''; String sTel = prefs.getString('user_tel') ?? ''; Color activeColor = isB ? Colors.amber.shade700 : Colors.blue.shade900; bool? usaDati = await showDialog( context: context, barrierDismissible: false, builder: (ctx) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), title: Row( children: [ Icon(Icons.contact_mail, color: activeColor, size: 28), const SizedBox(width: 10), const Expanded(child: Text("Dati Trovati", style: TextStyle(fontWeight: FontWeight.bold))), ], ), content: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ const Text("Abbiamo trovato i tuoi dati salvati in una compilazione precedente:", style: TextStyle(fontSize: 15)), const SizedBox(height: 12), Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration(color: Colors.grey.shade100, borderRadius: BorderRadius.circular(10), border: Border.all(color: Colors.grey.shade300)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("• $sNome $sCognome", style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15)), if (sCF.isNotEmpty) Padding(padding: const EdgeInsets.only(top: 4), child: Text("• CF/PIVA: $sCF")), if (sIndirizzo.isNotEmpty) Padding(padding: const EdgeInsets.only(top: 4), child: Text("• $sIndirizzo, $sCap")), if (sTel.isNotEmpty) Padding(padding: const EdgeInsets.only(top: 4), child: Text("• Tel: $sTel")), ], ), ), const SizedBox(height: 16), const Text("Vuoi usarli per compilare automaticamente questa sezione?", style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: Text("NO, SCRIVO A MANO", style: TextStyle(color: Colors.grey[600], fontWeight: FontWeight.bold)), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: activeColor, foregroundColor: isB ? Colors.black87 : Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), onPressed: () => Navigator.pop(ctx, true), child: const Text("SÌ, USA QUESTI", style: TextStyle(fontWeight: FontWeight.bold)), ), ], ), ); if (usaDati == true && mounted) { setState(() { _cognome.text = sCognome; _nome.text = sNome; _cf.text = sCF; _indirizzo.text = sIndirizzo; _cap.text = sCap; _tel.text = sTel; if (_cf.text.length == 16) { _controllaCfInTempoReale(_cf.text); } }); } } 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.directions_car, color: activeColor, size: 28), const SizedBox(width: 10), const Expanded(child: Text("Assicurato e Veicolo", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18))), ], ), content: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text("In questa sezione compilerai i dati relativi al Veicolo ${GlobalData.latoCorrente}.", style: const TextStyle(fontSize: 15)), const SizedBox(height: 16), _buildPopupRow(Icons.person, "Dati Anagrafici", "Il Codice Fiscale o P. IVA non è obbligatorio, ma se inserito verrà verificato in automatico."), const SizedBox(height: 12), _buildPopupRow(Icons.numbers, "Targa e Mezzo", "Inserisci la targa esatta (senza spazi) e la marca del veicolo."), const SizedBox(height: 12), _buildPopupRow(Icons.rv_hookup, "Rimorchio", "Compila questa parte SOLO se il veicolo trainava un rimorchio al momento dell'incidente."), ], ), ), 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 _calcolaCodiceCognome(String cognome) { cognome = cognome.toUpperCase().replaceAll(RegExp(r'[^A-Z]'), ''); if (cognome.isEmpty) return "XXX"; String consonanti = cognome.replaceAll(RegExp(r'[AEIOU]'), ''); String vocali = cognome.replaceAll(RegExp(r'[^AEIOU]'), ''); return (consonanti + vocali + "XXX").substring(0, 3); } String _calcolaCodiceNome(String nome) { nome = nome.toUpperCase().replaceAll(RegExp(r'[^A-Z]'), ''); if (nome.isEmpty) return "XXX"; String consonanti = nome.replaceAll(RegExp(r'[AEIOU]'), ''); String vocali = nome.replaceAll(RegExp(r'[^AEIOU]'), ''); if (consonanti.length >= 4) { return consonanti[0] + consonanti[2] + consonanti[3]; } else { return (consonanti + vocali + "XXX").substring(0, 3); } } bool _isCodiceFiscaleValido(String cf) { cf = cf.toUpperCase().trim(); if (cf.isEmpty) return false; if (!RegExp(r"^[A-Z0-9]{16}$").hasMatch(cf)) return false; final String setDispari = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; final String setPari = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; final List valoriDispari = [1, 0, 5, 7, 9, 13, 15, 17, 19, 21, 1, 0, 5, 7, 9, 13, 15, 17, 19, 21, 2, 4, 18, 20, 11, 3, 6, 8, 12, 14, 16, 10, 22, 25, 24, 23]; final List valoriPari = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]; int somma = 0; for (int i = 0; i < 15; i++) { String char = cf[i]; int val; if ((i + 1) % 2 != 0) { int index = setDispari.indexOf(char); if (index == -1) return false; val = valoriDispari[index]; } else { int index = setPari.indexOf(char); if (index == -1) return false; val = valoriPari[index]; } somma += val; } int resto = somma % 26; String carattereControlloCalcolato = String.fromCharCode(65 + resto); return carattereControlloCalcolato == cf[15]; } Future _salvaDatiSulDispositivo() async { final prefs = await SharedPreferences.getInstance(); debugPrint("🤖 [AUTOFILL] Salvataggio dati sul dispositivo..."); if (_cognome.text.isNotEmpty) await prefs.setString('user_cognome', _cognome.text.trim()); if (_nome.text.isNotEmpty) await prefs.setString('user_nome', _nome.text.trim()); if (_cf.text.isNotEmpty) await prefs.setString('user_cf', _cf.text.trim()); if (_indirizzo.text.isNotEmpty) await prefs.setString('user_indirizzo', _indirizzo.text.trim()); if (_cap.text.isNotEmpty) await prefs.setString('user_cap', _cap.text.trim()); if (_tel.text.isNotEmpty) await prefs.setString('user_tel', _tel.text.trim()); } void _salvaTutto() async { FocusScope.of(context).unfocus(); // Rimosso _cf dalla lista dei campi obbligatori if (_cognome.text.trim().isEmpty || _nome.text.trim().isEmpty || _indirizzo.text.trim().isEmpty || _cap.text.trim().isEmpty || _stato.text.trim().isEmpty || _tel.text.trim().isEmpty || _marca.text.trim().isEmpty || _targa.text.trim().isEmpty || _statoImm.text.trim().isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Compila tutti i campi obbligatori!"), backgroundColor: Colors.red)); return; } String cfInserito = _cf.text.trim().toUpperCase(); // Controlliamo la validità SOLO se il campo non è vuoto if (cfInserito.isNotEmpty) { bool isPIVA = RegExp(r"^[0-9]{11}$").hasMatch(cfInserito); bool isCF = _isCodiceFiscaleValido(cfInserito); if (!isPIVA && !isCF) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Cod. Fiscale / P. IVA NON VALIDO (Errato calcolo finale o formato)."), backgroundColor: Colors.orange, duration: Duration(seconds: 3), ) ); return; } // Applichiamo il controllo incrociato con nome/cognome SOLO se è un Codice Fiscale if (isCF) { String cfCognome = cfInserito.substring(0, 3); String calcolatoCognome = _calcolaCodiceCognome(_cognome.text); if (cfCognome != calcolatoCognome) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text("Il CF non corrisponde al COGNOME inserito.\nAtteso: $calcolatoCognome, Trovato: $cfCognome"), backgroundColor: Colors.red, duration: const Duration(seconds: 4), )); return; } String cfNome = cfInserito.substring(3, 6); String calcolatoNome = _calcolaCodiceNome(_nome.text); if (cfNome != calcolatoNome) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text("Il CF non corrisponde al NOME inserito.\nAtteso: $calcolatoNome, Trovato: $cfNome"), backgroundColor: Colors.red, duration: const Duration(seconds: 4), )); return; } } } bool currentIsB = GlobalData.latoCorrente == 'B'; String rimorchioFinale = _rimorchio.text.trim(); String statoImm2Finale = _statoImm2.text.trim(); if (rimorchioFinale.isEmpty) { rimorchioFinale = "/ / / / / /"; statoImm2Finale = "/ / / / / / / / / /"; } else { rimorchioFinale = rimorchioFinale.toUpperCase(); statoImm2Finale = statoImm2Finale.isEmpty ? "ITALIA" : statoImm2Finale.toUpperCase(); } if (currentIsB) { GlobalData.Cognome_contraente_B = _cognome.text.trim().toUpperCase(); GlobalData.Nome_contraente_B = _nome.text.trim().toUpperCase(); GlobalData.Codice_Fiscale_contraente_B = cfInserito; GlobalData.Indirizzo_contraente_B = _indirizzo.text.trim().toUpperCase(); GlobalData.CAP_contraente_B = _cap.text.trim(); GlobalData.Stato_contraente_B = _stato.text.trim().toUpperCase(); GlobalData.N_telefono_mail_contraente_B = _tel.text.trim().toUpperCase(); GlobalData.Marca_e_Tipo_B = _marca.text.trim().toUpperCase(); GlobalData.Targa_B = _targa.text.trim().toUpperCase(); GlobalData.Stato_immatricolazione_B = _statoImm.text.trim().toUpperCase(); GlobalData.Rimorchio_B = rimorchioFinale; GlobalData.Stato_immatricolazione2_B = statoImm2Finale; } else { GlobalData.Cognome_contraente_A = _cognome.text.trim().toUpperCase(); GlobalData.Nome_contraente_A = _nome.text.trim().toUpperCase(); GlobalData.Codice_Fiscale_contraente_A = cfInserito; GlobalData.Indirizzo_contraente_A = _indirizzo.text.trim().toUpperCase(); GlobalData.CAP_contraente_A = _cap.text.trim(); GlobalData.Stato_contraente_A = _stato.text.trim().toUpperCase(); GlobalData.N_telefono_mail_contraente_A = _tel.text.trim().toUpperCase(); GlobalData.Marca_e_Tipo_A = _marca.text.trim().toUpperCase(); GlobalData.Targa_A = _targa.text.trim().toUpperCase(); GlobalData.Stato_immatricolazione_A = _statoImm.text.trim().toUpperCase(); GlobalData.Rimorchio_A = rimorchioFinale; GlobalData.Stato_immatricolazione2_A = statoImm2Finale; } // <-- SALVATAGGIO IN MEMORIA DEI DATI (Per il Popup) --> await _salvaDatiSulDispositivo(); // <-- SALVATAGGIO DEI DATI NELLA TASTIERA DI SISTEMA (Per l'Autofill) --> TextInput.finishAutofillContext(); if (!mounted) return; Navigator.push(context, MaterialPageRoute(builder: (c) => const Comp8Screen())); } @override Widget build(BuildContext context) { Color mainCol = isB ? Colors.amber.shade700 : Colors.blue.shade900; Color bgCol = isB ? const Color(0xFFFFF9C4) : const Color(0xFFE3F2FD); if (!_isReady) { return Scaffold(backgroundColor: bgCol, body: Container()); } return PopScope( canPop: true, child: GestureDetector( onTap: () => FocusScope.of(context).unfocus(), child: Scaffold( backgroundColor: bgCol, resizeToAvoidBottomInset: true, appBar: AppBar( title: Text("Sez. 6-7: Veicolo ${GlobalData.latoCorrente}"), backgroundColor: mainCol, foregroundColor: isB ? Colors.black : Colors.white, ), body: SafeArea( child: Form( // <-- FORM PER L'AUTOFILL child: AutofillGroup( // <-- GRUPPO AUTOFILL child: CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ SliverPadding( padding: const EdgeInsets.all(16), sliver: SliverList( delegate: SliverChildListDelegate([ _buildSectionCard( titolo: "6. CONTRAENTE / ASSICURATO", accentColor: mainCol, children: [ _buildField(_cognome, "Cognome *", Icons.person, autofillHints: const [AutofillHints.familyName], keyboardType: TextInputType.name), _buildField(_nome, "Nome *", Icons.person_outline, autofillHints: const [AutofillHints.givenName], keyboardType: TextInputType.name), // Sostituito "Codice Fiscale *" con "Cod. Fiscale / P. IVA" _buildField( _cf, "Cod. Fiscale / P. IVA", Icons.badge, isCF: true, focusNode: _cfFocusNode, onChanged: _controllaCfInTempoReale, ), _buildField(_indirizzo, "Indirizzo *", Icons.home, autofillHints: const [AutofillHints.streetAddressLine1, AutofillHints.fullStreetAddress], keyboardType: TextInputType.streetAddress), Row( children: [ Expanded(child: _buildField(_cap, "C.A.P. *", Icons.location_on, isNumeric: true, autofillHints: const [AutofillHints.postalCode], keyboardType: const TextInputType.numberWithOptions(signed: true))), const SizedBox(width: 10), Expanded(child: _buildField(_stato, "Stato *", Icons.flag, autofillHints: const [AutofillHints.countryName], keyboardType: TextInputType.text)), ] ), _buildField(_tel, "Tel / Email *", Icons.email, autofillHints: const [AutofillHints.telephoneNumber, AutofillHints.email], keyboardType: TextInputType.emailAddress), ], ), _buildSectionCard( titolo: "7. VEICOLO A MOTORE", accentColor: mainCol, children: [ _buildField(_marca, "Marca e Tipo *", Icons.directions_car), const SizedBox(height: 10), Row( children: [ Expanded(child: _buildField(_targa, "Targa *", Icons.numbers, isUpper: true)), const SizedBox(width: 10), Expanded(child: _buildField(_rimorchio, "Rimorchio (Opz)", Icons.rv_hookup)), ] ), const SizedBox(height: 10), Row( children: [ Expanded(child: _buildField(_statoImm, "Stato Imm. *", Icons.public)), const SizedBox(width: 10), Expanded(child: _buildField(_statoImm2, "Stato Rimorchio", Icons.public_off)), ] ), ], ), ]), ), ), SliverFillRemaining( hasScrollBody: false, child: Align( alignment: Alignment.bottomCenter, child: Padding( padding: const EdgeInsets.all(16), child: _navButtons(mainCol), ), ), ), ], ), ), ), ), ), ), ); } // WIDGET FIELD CON HINTS E KEYBOARD TYPES Widget _buildField( TextEditingController controller, String label, IconData icon, { bool isUpper = false, bool isNumeric = false, bool isCF = false, FocusNode? focusNode, Function(String)? onChanged, Iterable? autofillHints, TextInputType? keyboardType, } ) { List formatters = []; if (isNumeric) { formatters.add(FilteringTextInputFormatter.digitsOnly); formatters.add(LengthLimitingTextInputFormatter(5)); } else if (isCF) { formatters.add(FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]"))); formatters.add(UpperCaseTextFormatter()); formatters.add(LengthLimitingTextInputFormatter(16)); } else if (isUpper) { formatters.add(UpperCaseTextFormatter()); formatters.add(FilteringTextInputFormatter.deny(RegExp(r'\s'))); } TextInputType finalKeyboardType = keyboardType ?? (isNumeric ? TextInputType.number : TextInputType.text); return Padding( padding: const EdgeInsets.only(bottom: 12), child: TextField( controller: controller, focusNode: focusNode, onChanged: onChanged, keyboardType: finalKeyboardType, inputFormatters: formatters, autofillHints: autofillHints, // <- Suggerimenti OS nativi textCapitalization: (isUpper || isCF) ? TextCapitalization.characters : TextCapitalization.sentences, decoration: InputDecoration( labelText: label, prefixIcon: Icon(icon, size: 20, color: isB ? Colors.orange.shade800 : Colors.blue.shade700), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)), filled: true, fillColor: Colors.white, ), ), ); } Widget _buildSectionCard({required String titolo, required List children, required Color accentColor}) { return Card( elevation: 2, margin: const EdgeInsets.only(bottom: 16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( titolo, style: TextStyle(fontWeight: FontWeight.bold, color: accentColor, fontSize: 16) ), const Divider(height: 25), ...children, ] ), ), ); } Widget _navButtons(Color col) { 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: col, foregroundColor: isB ? Colors.black : Colors.white, minimumSize: const Size(0, 55) ), onPressed: _salvaTutto, child: const Text("SALVA E PROSEGUI", style: TextStyle(fontWeight: FontWeight.bold)) ) ), ] ); } @override void dispose() { _cfFocusNode.removeListener(_onCfFocusChange); _cfFocusNode.dispose(); _cognome.dispose(); _nome.dispose(); _cf.dispose(); _indirizzo.dispose(); _cap.dispose(); _stato.dispose(); _tel.dispose(); _marca.dispose(); _targa.dispose(); _rimorchio.dispose(); _statoImm.dispose(); _statoImm2.dispose(); super.dispose(); } }