618 lines
24 KiB
Dart
618 lines
24 KiB
Dart
|
|
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))),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
]),
|
||
|
|
),
|
||
|
|
)
|
||
|
|
]),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|