import 'dart:io'; import 'dart:async'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_email_sender/flutter_email_sender.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:path_provider/path_provider.dart'; import 'package:printing/printing.dart'; import 'package:share_plus/share_plus.dart'; import 'scambio_dati_screen.dart'; import 'pdf_engine.dart'; import 'global_data.dart'; import 'main.dart'; import 'comp_6-7.dart'; import 'comp_1-5.dart'; class Comp16Screen extends StatefulWidget { const Comp16Screen({super.key}); @override State createState() => _Comp16ScreenState(); } class _Comp16ScreenState extends State with WidgetsBindingObserver { bool _scambioEffettuato = false; bool _datiPresenti = false; bool _ioHoApprovato = false; bool _tuttiHannoApprovato = false; bool _staCancellando = false; bool _cancellazioneAvviataDaMe = false; String _statusText = "Esegui lo Scambio Dati per iniziare."; Color _statusColor = Colors.orange.shade800; IconData _statusIcon = Icons.warning_amber_rounded; File? _filePdfReale; Uint8List? _immagineAnteprima; bool _isLoading = false; StreamSubscription? _roomSubscription; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); _puliziaIngresso(); } Future _puliziaIngresso() async { if (GlobalData.idScambioTemporaneo == null && GlobalData.idSessione != null) { GlobalData.idScambioTemporaneo = GlobalData.idSessione; } if (GlobalData.idScambioTemporaneo == null) { if (GlobalData.latoCorrente == 'A') GlobalData.resetB(); else GlobalData.resetA(); } if (mounted) _verificaStatoPostScambio(); } void _verificaStatoPostScambio() { bool datiOk = false; if (GlobalData.latoCorrente == 'A') { datiOk = GlobalData.Cognome_contraente_B.trim().isNotEmpty && GlobalData.Targa_B.trim().isNotEmpty; } else { datiOk = GlobalData.Cognome_contraente_A.trim().isNotEmpty && GlobalData.Targa_A.trim().isNotEmpty; } if (mounted) { setState(() { if (datiOk) { _scambioEffettuato = true; _datiPresenti = true; _statusText = "Dati ricevuti. Generazione anteprima..."; _statusColor = Colors.blue.shade800; _statusIcon = Icons.pending_actions; _generaDocumenti(); _attivaAscoltoStanza(); } else { _resetStatiUI(); } }); } } void _resetStatiUI() { _scambioEffettuato = false; _datiPresenti = false; _ioHoApprovato = false; _tuttiHannoApprovato = false; _statusText = "Esegui lo Scambio Dati per iniziare."; _statusColor = Colors.orange.shade800; _statusIcon = Icons.warning_amber_rounded; _filePdfReale = null; _immagineAnteprima = null; } void _attivaAscoltoStanza() { String? idDaAscoltare = GlobalData.idScambioTemporaneo ?? GlobalData.idSessione; if (idDaAscoltare == null || _roomSubscription != null) return; _roomSubscription = FirebaseFirestore.instance .collection('scambi_cid') .doc(idDaAscoltare) .snapshots() .listen((snapshot) async { if (!snapshot.exists) { if (_ioHoApprovato) { _roomSubscription?.cancel(); _roomSubscription = null; return; } if (!_staCancellando && !_cancellazioneAvviataDaMe && mounted) { _gestisciCancellazioneAltrui(); } return; } final data = snapshot.data(); if (data == null) return; if (data['status'] == 'retry') { if (!_cancellazioneAvviataDaMe) _gestisciCancellazioneAltrui(); return; } bool appA = data['approved_A'] == true; bool appB = data['approved_B'] == true; if (appA && appB) { if (mounted) { setState(() { _tuttiHannoApprovato = true; _ioHoApprovato = true; _statusText = "DATI APPROVATI!\nPDF creato procedi con il salvataggio sul dispositivo o invialo"; _statusColor = Colors.green.shade800; _statusIcon = Icons.check_circle; }); String? id = GlobalData.idSessione ?? GlobalData.idScambioTemporaneo; if (id != null) FirebaseFirestore.instance.collection('scambi_cid').doc(id).delete().catchError((_){}); } } else if (_ioHoApprovato) { if (mounted) { setState(() { _statusText = "Hai approvato. In attesa dell'altro utente..."; _statusColor = Colors.amber.shade800; _statusIcon = Icons.hourglass_top; }); } } }); } // =========================================================================== // GESTIONE CANCELLAZIONE // =========================================================================== Future _eseguiPuliziaFirebase({required bool notificaAltri}) async { setState(() { _isLoading = true; _cancellazioneAvviataDaMe = true; }); await _roomSubscription?.cancel(); _roomSubscription = null; Set idsDaCancellare = {}; if (GlobalData.idScambioTemporaneo != null) idsDaCancellare.add(GlobalData.idScambioTemporaneo!); if (GlobalData.idSessione != null) idsDaCancellare.add(GlobalData.idSessione!); for (String id in idsDaCancellare) { if (notificaAltri) { try { await FirebaseFirestore.instance.collection('scambi_cid').doc(id).update({'status': 'retry'}) .timeout(const Duration(seconds: 2)); await Future.delayed(const Duration(milliseconds: 300)); } catch (_) {} } try { await FirebaseFirestore.instance.collection('scambi_cid').doc(id).delete(); } catch (_) {} } } Future _tornaIndietroConPulizia() async { await _eseguiPuliziaFirebase(notificaAltri: true); _resetDatiLocali(); if (mounted) { setState(() => _isLoading = false); if (GlobalData.latoCorrente == 'A') { Navigator.pushReplacement(context, MaterialPageRoute(builder: (c) => const Comp1_5Screen())); } else { Navigator.pushReplacement(context, MaterialPageRoute(builder: (c) => const Comp6_7Screen())); } } } Future _ioApprovo() async { String? id = GlobalData.idSessione ?? GlobalData.idScambioTemporaneo; if (id != null) { try { String field = (GlobalData.latoCorrente == 'A') ? 'approved_A' : 'approved_B'; await FirebaseFirestore.instance.collection('scambi_cid').doc(id).update({field: true}); } catch (_) {} } if (mounted) { setState(() { _ioHoApprovato = true; }); } } Future _concludiEHome() async { await _eseguiPuliziaFirebase(notificaAltri: false); GlobalData.reset(); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); if (mounted) { Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (c) => const HomeScreen()), (r) => false); } } void _resetDatiLocali() { if (GlobalData.latoCorrente == 'A') GlobalData.resetB(); else GlobalData.resetA(); GlobalData.idScambioTemporaneo = null; GlobalData.idSessione = null; } void _gestisciCancellazioneAltrui() { _roomSubscription?.cancel(); _roomSubscription = null; if (mounted) { // Chiude eventuali altri dialoghi aperti (es. quello dell'anteprima) Navigator.of(context).popUntil((route) => route.isFirst || route.settings.name == null); showDialog( context: context, barrierDismissible: false, // L'utente DEVE premere il tasto builder: (ctx) => AlertDialog( // 1. Forma moderna con angoli molto arrotondati shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24.0)), backgroundColor: Colors.white, surfaceTintColor: Colors.transparent, // Evita tinte strane su Material 3 // 2. Icona grande in cima al titolo icon: Icon( Icons.warning_amber_rounded, size: 60, color: Colors.amber.shade800 ), iconPadding: const EdgeInsets.only(top: 24, bottom: 16), // 3. Titolo in grassetto title: Text( "Attenzione", textAlign: TextAlign.center, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 22, color: Colors.amber.shade900 ) ), // 4. Contenuto con il tuo testo, centrato e leggibile content: const Padding( padding: EdgeInsets.symmetric(vertical: 8.0), child: Text( "L'altro utente ha deciso di modificare i propri dati o non ha accettato i tuoi.\n\nSarai riportato alla schermata iniziale dove potrai eventualmente apporre modifiche.", textAlign: TextAlign.center, style: TextStyle(fontSize: 16, height: 1.4, color: Colors.black87), ), ), // 5. Spaziatura azioni actionsPadding: const EdgeInsets.fromLTRB(24, 0, 24, 24), actions: [ // 6. Pulsante moderno full-width SizedBox( width: double.infinity, // Occupa tutta la larghezza child: ElevatedButton( onPressed: () { Navigator.pop(ctx); // Chiude il dialog _resetDatiLocali(); // Pulisce la RAM // Torna alla schermata di input corretta if (GlobalData.latoCorrente == 'A') { Navigator.pushReplacement(context, MaterialPageRoute(builder: (c) => const Comp1_5Screen())); } else { Navigator.pushReplacement(context, MaterialPageRoute(builder: (c) => const Comp6_7Screen())); } }, style: ElevatedButton.styleFrom( backgroundColor: Colors.amber.shade800, // Colore coerente con l'icona foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), elevation: 0, // Look più piatto e moderno ), child: const Text( "HO CAPITO", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16) ) ), ) ], ), ); } } Future _generaDocumenti() async { if (!mounted) return; setState(() => _isLoading = true); try { final List pdfBytes = await PdfEngine.generaDocumentoCai(); if (pdfBytes.isEmpty) throw Exception("PDF vuoto"); final tempDir = await getTemporaryDirectory(); final file = File('${tempDir.path}/CID_${DateTime.now().millisecondsSinceEpoch}.pdf'); await file.writeAsBytes(pdfBytes, flush: true); Uint8List? anteprima; await for (final page in Printing.raster(Uint8List.fromList(pdfBytes), pages: [0], dpi: 150)) { anteprima = await page.toPng(); break; } if (mounted) { setState(() { _filePdfReale = file; _immagineAnteprima = anteprima; _isLoading = false; }); } } catch (e) { if (mounted) setState(() => _isLoading = false); } } Future _vaiAScambioDati() async { await Navigator.push(context, MaterialPageRoute(builder: (context) => const ScambioDatiScreen())); _verificaStatoPostScambio(); } void _apriAnteprimaSchermoIntero() { if (!_scambioEffettuato || !_datiPresenti || _immagineAnteprima == null || _filePdfReale == null) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Dati non pronti!"))); return; } Navigator.push(context, MaterialPageRoute(builder: (context) => ImageViewerScreen( imageBytes: _immagineAnteprima!, pdfFile: _filePdfReale!, isAlreadyApproved: _ioHoApprovato, onConfirmCorrection: _tornaIndietroConPulizia, onConfirmApproval: _ioApprovo ))); } Future _inviaMailConAllegato(BuildContext context) async { if (_filePdfReale == null) return; try { bool isA = GlobalData.latoCorrente == 'A'; String polizzaChiScrive = (isA ? GlobalData.Numero_Polizza_A : GlobalData.Numero_Polizza_B).trim(); String targaChiScrive = (isA ? GlobalData.Targa_A : GlobalData.Targa_B).trim(); String firmaChiScrive = "${isA ? GlobalData.Nome_contraente_A : GlobalData.Nome_contraente_B} ${isA ? GlobalData.Cognome_contraente_A : GlobalData.Cognome_contraente_B}"; String contattoChiScrive = (isA ? GlobalData.N_telefono_mail_contraente_A : GlobalData.N_telefono_mail_contraente_B).trim(); String compagniaUtente = (isA ? GlobalData.Denominazione_A : GlobalData.Denominazione_B).trim().toUpperCase(); String emailDestinatario = ""; if (GlobalData.assicurazioni.containsKey(compagniaUtente)) { emailDestinatario = GlobalData.assicurazioni[compagniaUtente]!; } else { for (var key in GlobalData.assicurazioni.keys) { if (key.isNotEmpty && (compagniaUtente.contains(key) || key.contains(compagniaUtente))) { emailDestinatario = GlobalData.assicurazioni[key]!; break; } } } List listaCC = []; if (contattoChiScrive.contains("@")) listaCC.add(contattoChiScrive); String oggetto = "DENUNCIA SINISTRO - Polizza n. $polizzaChiScrive - Targa $targaChiScrive"; String corpo = "Spett.le Compagnia,\n\n" "Con la presente inoltro in allegato il modulo CAI relativo al sinistro avvenuto in data ${GlobalData.data_incidente} alle ore ${GlobalData.ora} nel comune di ${GlobalData.luogo}.\n\n" "Rimaniamo in attesa dell'apertura del fascicolo.\n\n" "Cordiali saluti,\n$firmaChiScrive\nContatto: $contattoChiScrive"; final Email email = Email( subject: oggetto, body: corpo, recipients: emailDestinatario.isNotEmpty ? [emailDestinatario] : [], cc: listaCC, attachmentPaths: [_filePdfReale!.path], isHTML: false, ); await FlutterEmailSender.send(email); } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Errore mail: $e"))); } } Future _salvaPdfLocale(BuildContext context) async { if (_filePdfReale == null) return; await Share.shareXFiles([XFile(_filePdfReale!.path, mimeType: 'application/pdf')], subject: 'Modulo CAI', text: 'Ecco il modulo CAI compilato.'); } @override void dispose() { _roomSubscription?.cancel(); WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override Widget build(BuildContext context) { bool pdfPronto = !_isLoading && _filePdfReale != null && _immagineAnteprima != null; bool abilitaAnteprima = _scambioEffettuato && _datiPresenti && pdfPronto; bool abilitaFinali = _tuttiHannoApprovato && pdfPronto; String testoAnteprima = !_scambioEffettuato ? "2. ANTEPRIMA (Prima fai Scambio)" : (_ioHoApprovato ? "ANTEPRIMA (IN ATTESA...)" : "2. APRI ANTEPRIMA E APPROVA"); if (_tuttiHannoApprovato) testoAnteprima = "ANTEPRIMA (COMPLETATA)"; return PopScope( canPop: false, onPopInvoked: (didPop) async { if (didPop) return; if (_ioHoApprovato) _concludiEHome(); else _tornaIndietroConPulizia(); }, child: Scaffold( extendBodyBehindAppBar: true, appBar: AppBar( title: const Text("Invio e Salvataggio", style: TextStyle(fontWeight: FontWeight.w800, fontSize: 20)), centerTitle: true, backgroundColor: Colors.blue.shade900.withOpacity(0.95), foregroundColor: Colors.white, elevation: 10, leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: _ioHoApprovato ? _concludiEHome : _tornaIndietroConPulizia ), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(bottom: Radius.circular(20))), ), body: Stack(children: [ Positioned.fill(child: Comp16BackgroundImage()), SafeArea(child: SingleChildScrollView(padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 20), child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildStatusCard(), const SizedBox(height: 20), _btn("1. SCAMBIO DATI (QR CODE)", Icons.qr_code_scanner, Colors.orange.shade800, onTap: _vaiAScambioDati, disabled: _ioHoApprovato), const SizedBox(height: 20), _btn(testoAnteprima, Icons.visibility, _statusColor, onTap: abilitaAnteprima ? _apriAnteprimaSchermoIntero : null, disabled: !abilitaAnteprima), const SizedBox(height: 8), Divider(color: Colors.white.withOpacity(0.5), thickness: 1), const SizedBox(height: 8), Builder(builder: (ctx) => _btn("SALVA SUL DISPOSITIVO", Icons.save_alt, Colors.green.shade700, onTap: abilitaFinali ? () => _salvaPdfLocale(ctx) : null, disabled: !abilitaFinali)), const SizedBox(height: 20), Builder(builder: (ctx) => _btn("INVIA ALL'ASSICURAZIONE", Icons.send_rounded, Colors.green.shade700, onTap: abilitaFinali ? () => _inviaMailConAllegato(ctx) : null, disabled: !abilitaFinali)), const SizedBox(height: 40), _btn( _tuttiHannoApprovato ? "TORNA ALLA HOME" : "CANCELLA TUTTO E ESCI", _tuttiHannoApprovato ? Icons.home : Icons.delete_sweep, _tuttiHannoApprovato ? Colors.green.shade800 : Colors.red.shade900, onTap: _tuttiHannoApprovato ? _concludiEHome : _tornaIndietroConPulizia, disabled: false ), const SizedBox(height: 30) ]))), if (_isLoading) Container(color: Colors.black54, child: const Center(child: Column(mainAxisSize: MainAxisSize.min, children: [CircularProgressIndicator(color: Colors.white), SizedBox(height: 20), Text("Elaborazione in corso...", style: TextStyle(color: Colors.white))]))), ]), ), ); } Widget _btn(String label, IconData icon, Color color, {VoidCallback? onTap, bool disabled = false}) { bool on = onTap != null && !disabled; return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), boxShadow: on ? [BoxShadow(color: Colors.black.withOpacity(0.3), offset: const Offset(0, 4), blurRadius: 5)] : [], ), child: ElevatedButton( onPressed: on ? onTap : null, style: ElevatedButton.styleFrom( backgroundColor: on ? color : Colors.grey, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 20), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), ), child: Row(children: [ Icon(icon, size: 28), const SizedBox(width: 20), Expanded(child: Text(label, textAlign: TextAlign.center, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16))), Icon(Icons.lock, size: 20, color: Colors.transparent) ]) ), ); } Widget _buildStatusCard() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: Colors.white.withOpacity(0.9), borderRadius: BorderRadius.circular(16), border: Border.all(color: _statusColor, width: 2), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 6, offset: const Offset(0, 3))]), child: Row(children: [ Icon(_statusIcon, color: _statusColor, size: 36), const SizedBox(width: 15), Expanded(child: Text(_statusText, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: _statusColor))) ]) ); } } // Widget Sfondo Modificato: Ingrandito (1.3x) e Spostato in basso (+100px) class Comp16BackgroundImage extends StatelessWidget { const Comp16BackgroundImage({super.key}); @override Widget build(BuildContext context) { return Container( height: double.infinity, width: double.infinity, color: const Color(0xFFF0F4F8), child: Transform.translate( offset: const Offset(0, 100), // Sposta in basso di 100px child: Transform.scale( scale: 1.3, // Ingrandisce del 30% child: Image.asset( 'assets/sfondo_mappa.jpg', fit: BoxFit.cover, alignment: Alignment.center, // Centrale per zoomare uniforme color: const Color(0xFFF0F4F8).withOpacity(0.6), colorBlendMode: BlendMode.lighten, errorBuilder: (c, e, s) => Container(color: Colors.grey.shade200), ), ), ), ); } } class ImageViewerScreen extends StatelessWidget { final Uint8List imageBytes; final File pdfFile; final bool isAlreadyApproved; final Function onConfirmCorrection; final Function onConfirmApproval; const ImageViewerScreen({super.key, required this.imageBytes, required this.pdfFile, required this.isAlreadyApproved, required this.onConfirmCorrection, required this.onConfirmApproval}); Future _askCorrection(BuildContext context) async { String titolo = isAlreadyApproved ? "Chiudere?" : "Richiedere correzione?"; String testo = isAlreadyApproved ? "Hai già approvato. Uscendo tornerai alla schermata precedente in attesa dell'altro utente." : "Questo annullerà lo scambio per entrambi e vi riporterà alla modifica."; String tasto = isAlreadyApproved ? "CHIUDI" : "CORREGGI"; bool? conf = await showDialog(context: context, builder: (c) => AlertDialog( title: Text(titolo), content: Text(testo), actions: [ TextButton(onPressed: () => Navigator.pop(c, false), child: const Text("ANNULLA")), ElevatedButton(onPressed: () => Navigator.pop(c, true), child: Text(tasto)) ] )); if (conf == true) { Navigator.pop(context); if (!isAlreadyApproved) onConfirmCorrection(); } } Future _askApproval(BuildContext context) async { Navigator.pop(context); onConfirmApproval(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar(title: const Text("Verifica Dati"), backgroundColor: Colors.black, foregroundColor: Colors.white), body: Column(children: [ Expanded(child: InteractiveViewer(minScale: 0.5, maxScale: 4.0, child: Center(child: Container(color: Colors.white, child: Image.memory(imageBytes, fit: BoxFit.contain))))), Container( padding: const EdgeInsets.all(16.0), decoration: BoxDecoration(color: Colors.white, boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 10, offset: const Offset(0, -2))]), child: SafeArea( child: Row(children: [ Expanded( child: ElevatedButton.icon( onPressed: () => _askCorrection(context), icon: Icon(isAlreadyApproved ? Icons.arrow_back : Icons.edit), label: Text(isAlreadyApproved ? "INDIETRO" : "CORREGGI"), style: ElevatedButton.styleFrom(backgroundColor: Colors.orange.shade800, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))), ), ), const SizedBox(width: 16), Expanded( child: ElevatedButton.icon( onPressed: isAlreadyApproved ? null : () => _askApproval(context), icon: isAlreadyApproved ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : const Icon(Icons.check_circle), label: Text(isAlreadyApproved ? "IN ATTESA..." : "APPROVA"), style: ElevatedButton.styleFrom(backgroundColor: isAlreadyApproved ? Colors.grey : Colors.green.shade700, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))), ), ), ]), ), ) ]), ); } }