cid_app/lib/comp_9.dart

529 lines
No EOL
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();
}
}