720 lines
No EOL
30 KiB
Dart
720 lines
No EOL
30 KiB
Dart
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<Comp6_7Screen> {
|
|
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<void> _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<void> _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<void> _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<bool>(
|
|
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<Offset>(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<int> 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<int> 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<void> _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<String>? autofillHints,
|
|
TextInputType? keyboardType,
|
|
}
|
|
) {
|
|
List<TextInputFormatter> 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<Widget> 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();
|
|
}
|
|
} |