529 lines
23 KiB
Dart
529 lines
23 KiB
Dart
|
|
// 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<Comp9Screen> {
|
||
|
|
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<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),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 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<void> _selezionaData(BuildContext context, TextEditingController controller, Function(String) onChanged) async {
|
||
|
|
DateTime initialDate = DateTime.now();
|
||
|
|
|
||
|
|
if (controller.text.length == 10) {
|
||
|
|
try {
|
||
|
|
List<String> 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<String> 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<String>(
|
||
|
|
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<Widget> 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();
|
||
|
|
}
|
||
|
|
}
|