cid_app/lib/temp/comp_16.dart

618 lines
24 KiB
Dart
Raw Permalink Normal View History

2026-02-27 23:26:13 +01:00
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<Comp16Screen> createState() => _Comp16ScreenState();
}
class _Comp16ScreenState extends State<Comp16Screen> 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<void> _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<void> _eseguiPuliziaFirebase({required bool notificaAltri}) async {
setState(() {
_isLoading = true;
_cancellazioneAvviataDaMe = true;
});
await _roomSubscription?.cancel();
_roomSubscription = null;
Set<String> 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<void> _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<void> _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<void> _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<void> _generaDocumenti() async {
if (!mounted) return;
setState(() => _isLoading = true);
try {
final List<int> 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<void> _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<void> _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<String> 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<void> _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<void> _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<void> _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))),
),
),
]),
),
)
]),
);
}
}