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 ' ;
2026-04-25 01:00:02 +02:00
import ' screens/paywall_screen.dart ' ;
2026-02-27 23:26:13 +01:00
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 ( ) ;
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
_mostraInfoPopup ( context ) ;
} ) ;
}
void _mostraInfoPopup ( BuildContext context ) {
Color activeColor = Colors . blue . shade900 ;
showGeneralDialog (
context: context ,
barrierDismissible: false ,
barrierLabel: " Popup " ,
2026-04-24 23:00:16 +02:00
barrierColor: Colors . black . withValues ( alpha: 0.5 ) ,
2026-02-27 23:26:13 +01:00
transitionDuration: const Duration ( milliseconds: 400 ) ,
pageBuilder: ( context , animation , secondaryAnimation ) {
return AlertDialog (
shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 20 ) ) ,
title: Row (
children: [
Icon ( Icons . sync_alt , color: activeColor , size: 28 ) ,
const SizedBox ( width: 10 ) ,
const Expanded ( child: Text ( " Scambio e Invio " , style: TextStyle ( fontWeight: FontWeight . bold , fontSize: 18 ) ) ) ,
] ,
) ,
content: SingleChildScrollView (
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
mainAxisSize: MainAxisSize . min ,
children: [
const Text ( " Questa è la fase finale. Segui questi tre passaggi per concludere il modulo: " , style: TextStyle ( fontSize: 15 ) ) ,
const SizedBox ( height: 16 ) ,
_buildPopupRow ( Icons . qr_code_scanner , " 1. Scambio Dati " , " Inquadra il QR Code dell'altro conducente oppure inserisci a mano il suo codice PIN. " ) ,
const SizedBox ( height: 12 ) ,
_buildPopupRow ( Icons . visibility , " 2. Anteprima " , " Apri l'anteprima per verificare che i dati di entrambi siano impaginati correttamente sul documento. " ) ,
const SizedBox ( height: 12 ) ,
_buildPopupRow ( Icons . check_circle , " 3. Approvazione " , " Se tutto è esatto, clicca su Approva. Quando entrambi avrete approvato, il file sarà pronto per l'invio. " ) ,
] ,
) ,
) ,
actions: [
SizedBox (
width: double . infinity ,
child: ElevatedButton (
style: ElevatedButton . styleFrom (
backgroundColor: activeColor ,
foregroundColor: Colors . white ,
shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 12 ) ) ,
padding: const EdgeInsets . symmetric ( vertical: 14 ) ,
) ,
onPressed: ( ) = > Navigator . pop ( context ) ,
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 ) ,
] ,
) ,
) ,
) ,
] ,
) ;
}
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! \n PDF creato, procedi con il salvataggio o l'invio " ;
_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 ;
} ) ;
}
}
} ) ;
}
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 > _abbandonaScambioEHome ( ) async {
await _eseguiPuliziaFirebase ( notificaAltri: true ) ;
GlobalData . reset ( ) ;
if ( mounted ) {
setState ( ( ) = > _isLoading = false ) ;
Navigator . pushAndRemoveUntil (
context ,
MaterialPageRoute ( builder: ( c ) = > const HomeScreen ( ) ) ,
( route ) = > false
) ;
}
}
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 ( ) ;
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 ) {
Navigator . of ( context ) . popUntil ( ( route ) = > route . isFirst | | route . settings . name = = null ) ;
showDialog (
context: context ,
barrierDismissible: false ,
builder: ( ctx ) = > AlertDialog (
shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 24.0 ) ) ,
backgroundColor: Colors . white ,
surfaceTintColor: Colors . transparent ,
icon: Icon ( Icons . warning_amber_rounded , size: 60 , color: Colors . amber . shade800 ) ,
iconPadding: const EdgeInsets . only ( top: 24 , bottom: 16 ) ,
title: Text ( " Attenzione " , textAlign: TextAlign . center , style: TextStyle ( fontWeight: FontWeight . bold , fontSize: 22 , color: Colors . amber . shade900 ) ) ,
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 \n Sarai riportato alla schermata iniziale dove potrai eventualmente apporre modifiche. " ,
textAlign: TextAlign . center ,
style: TextStyle ( fontSize: 16 , height: 1.4 , color: Colors . black87 ) ,
) ,
) ,
actionsPadding: const EdgeInsets . fromLTRB ( 24 , 0 , 24 , 24 ) ,
actions: [
SizedBox (
width: double . infinity ,
child: ElevatedButton (
onPressed: ( ) {
Navigator . pop ( ctx ) ;
_resetDatiLocali ( ) ;
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 , foregroundColor: Colors . white , padding: const EdgeInsets . symmetric ( vertical: 16 ) , shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 16 ) ) , elevation: 0 ) ,
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 appDocDir = await getApplicationDocumentsDirectory ( ) ;
final file = File ( ' ${ appDocDir . 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 {
2026-04-25 01:00:02 +02:00
if ( ! GlobalData . isPro ) {
// Mostra il Paywall se l'utente non ha un abbonamento attivo
await Navigator . push (
context ,
MaterialPageRoute (
builder: ( context ) = > PaywallScreen (
onSuccess: ( ) async {
// Se l'acquisto va a buon fine, naviga subito alla schermata del QR
await Navigator . pushReplacement (
context ,
MaterialPageRoute ( builder: ( context ) = > const ScambioDatiScreen ( ) ) ,
) ;
_verificaStatoPostScambio ( ) ;
} ,
) ,
) ,
) ;
} else {
// Utente PRO, vai direttamente alla schermata di scambio
await Navigator . push ( context , MaterialPageRoute ( builder: ( context ) = > const ScambioDatiScreen ( ) ) ) ;
_verificaStatoPostScambio ( ) ;
}
2026-02-27 23:26:13 +01:00
}
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 \n Contatto: $ 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 ( const SnackBar ( content: Text ( " Nessuna app Mail predefinita trovata. Apro la condivisione... " ) , duration: Duration ( seconds: 3 ) , backgroundColor: Colors . orange ) ) ;
_apriCondivisione ( context ) ;
}
}
}
Future < void > _apriCondivisione ( BuildContext context ) async {
if ( _filePdfReale = = null ) return ;
final box = context . findRenderObject ( ) as RenderBox ? ;
await Share . shareXFiles (
[ XFile ( _filePdfReale ! . path , mimeType: ' application/pdf ' ) ] ,
subject: ' Modulo CAI ' ,
text: ' Ecco il modulo CAI compilato. ' ,
sharePositionOrigin: box ! = null ? ( box . localToGlobal ( Offset . zero ) & box . size ) : null ,
) ;
}
Future < void > _salvaPdfLocale ( BuildContext context ) async {
await _apriCondivisione ( context ) ;
}
@ 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 Container (
width: double . infinity ,
height: double . infinity ,
decoration: BoxDecoration (
color: const Color ( 0xFFF0F4F8 ) ,
image: DecorationImage (
image: const AssetImage ( ' assets/sfondo_mappa.jpg ' ) ,
fit: BoxFit . cover ,
colorFilter: ColorFilter . mode (
2026-04-24 23:00:16 +02:00
const Color ( 0xFFF0F4F8 ) . withValues ( alpha: 0.6 ) ,
2026-02-27 23:26:13 +01:00
BlendMode . lighten ,
) ,
) ,
) ,
child: PopScope (
canPop: false ,
onPopInvoked: ( didPop ) async {
if ( didPop ) return ;
if ( _ioHoApprovato ) _concludiEHome ( ) ; else _tornaIndietroConPulizia ( ) ;
} ,
child: Scaffold (
backgroundColor: Colors . transparent ,
extendBodyBehindAppBar: true ,
appBar: AppBar (
title: const Text ( " Invio e Salvataggio " , style: TextStyle ( fontWeight: FontWeight . w800 , fontSize: 20 ) ) ,
centerTitle: true ,
2026-04-24 23:00:16 +02:00
backgroundColor: Colors . blue . shade900 . withValues ( alpha: 0.95 ) ,
2026-02-27 23:26:13 +01:00
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: [
SafeArea (
child: SingleChildScrollView (
padding: const EdgeInsets . symmetric ( horizontal: 25 , vertical: 20 ) ,
child: Column ( crossAxisAlignment: CrossAxisAlignment . stretch , children: [
_buildStatusCard ( ) ,
const SizedBox ( height: 20 ) ,
// VA DIRETTO ALLA PAGINA DI SCAMBIO
_btn ( " 1. SCAMBIO DATI " , Icons . sync_alt , 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 ) ,
2026-04-24 23:00:16 +02:00
Divider ( color: Colors . white . withValues ( alpha: 0.5 ) , thickness: 1 ) ,
2026-02-27 23:26:13 +01:00
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 : _abbandonaScambioEHome ,
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 ) ,
2026-04-24 23:00:16 +02:00
boxShadow: on ? [ BoxShadow ( color: Colors . black . withValues ( alpha: 0.3 ) , offset: const Offset ( 0 , 4 ) , blurRadius: 5 ) ] : [ ] ,
2026-02-27 23:26:13 +01:00
) ,
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 ) ) ) ,
const Icon ( Icons . lock , size: 20 , color: Colors . transparent )
] )
) ,
) ;
}
Widget _buildStatusCard ( ) {
return Container (
padding: const EdgeInsets . all ( 16 ) ,
2026-04-24 23:00:16 +02:00
decoration: BoxDecoration ( color: Colors . white . withValues ( alpha: 0.9 ) , borderRadius: BorderRadius . circular ( 16 ) , border: Border . all ( color: _statusColor , width: 2 ) , boxShadow: [ BoxShadow ( color: Colors . black . withValues ( alpha: 0.1 ) , blurRadius: 6 , offset: const Offset ( 0 , 3 ) ) ] ) ,
2026-02-27 23:26:13 +01:00
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 ) ) )
] )
) ;
}
}
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 ) ) ) ,
) ,
) ,
] ) ,
) ,
)
] ) ,
) ;
}
}