2026-02-27 23:26:13 +01:00
// Versione: FINAL - FIX CONTATORE VISIBILE
import ' package:flutter/material.dart ' ;
import ' global_data.dart ' ;
import ' comp_12.dart ' ;
class Comp10Screen extends StatefulWidget {
const Comp10Screen ( { super . key } ) ;
@ override
_Comp10ScreenState createState ( ) = > _Comp10ScreenState ( ) ;
}
class _Comp10ScreenState extends State < Comp10Screen > {
// Set per gestire selezioni multiple uniche
Set < String > _selectedSectors = { } ;
final double _canvasWidth = 300.0 ;
final double _canvasHeight = 220.0 ;
late TextEditingController _controllerDanni ;
late TextEditingController _controllerOsservazioni ;
final bool isB = GlobalData . latoCorrente = = ' B ' ;
late Map < String , Rect > _hitboxes ;
@ override
void initState ( ) {
super . initState ( ) ;
_selectedSectors = isB
? Set . from ( GlobalData . puntiUrtoB_List )
: Set . from ( GlobalData . puntiUrtoA_List ) ;
_controllerDanni = TextEditingController ( text: isB ? GlobalData . danni_visibili_B : GlobalData . danni_visibili_A ) ;
_controllerOsservazioni = TextEditingController ( text: isB ? GlobalData . osservazioni_B : GlobalData . osservazioni_A ) ;
_hitboxes = isB ? _getHitboxesB ( ) : _getHitboxesA ( ) ;
// MOSTRA IL POPUP INFORMATIVO ANIMATO AL CARICAMENTO
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
_mostraInfoPopup ( context ) ;
} ) ;
}
// --- POPUP INFORMATIVO ---
void _mostraInfoPopup ( BuildContext context ) {
Color activeColor = isB ? Colors . amber . shade700 : 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 . minor_crash , color: activeColor , size: 28 ) ,
const SizedBox ( width: 10 ) ,
const Expanded ( child: Text ( " Danni e Osservazioni " , style: TextStyle ( fontWeight: FontWeight . bold , fontSize: 18 ) ) ) ,
] ,
) ,
content: SingleChildScrollView (
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
mainAxisSize: MainAxisSize . min ,
children: [
Text ( " In questa sezione indicherai i danni subiti dal Veicolo ${ GlobalData . latoCorrente } . " , style: const TextStyle ( fontSize: 15 ) ) ,
const SizedBox ( height: 16 ) ,
_buildPopupRow ( Icons . touch_app , " Punto d'urto " , " Tocca direttamente sull'immagine per inserire una 'X' nel punto d'urto. Puoi selezionare più punti. Tocca di nuovo per rimuovere la 'X'. " ) ,
const SizedBox ( height: 12 ) ,
_buildPopupRow ( Icons . edit_document , " Danni Visibili " , " Descrivi brevemente i danni visibili (es. 'Paraurti rotto'). " ) ,
const SizedBox ( height: 12 ) ,
_buildPopupRow ( Icons . comment , " Osservazioni " , " Aggiungi eventuali commenti personali sulla dinamica (opzionale). " ) ,
] ,
) ,
) ,
actions: [
SizedBox (
width: double . infinity ,
child: ElevatedButton (
style: ElevatedButton . styleFrom (
backgroundColor: activeColor ,
foregroundColor: isB ? Colors . black87 : Colors . white ,
shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 12 ) ) ,
padding: const EdgeInsets . symmetric ( vertical: 14 ) ,
) ,
onPressed: ( ) = > Navigator . pop ( context ) ,
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 ) ,
] ,
) ,
) ,
) ,
] ,
) ;
}
// --- MAPPATURA LATO A (BLU) ---
Map < String , Rect > _getHitboxesA ( ) {
return {
' 9 ' : const Rect . fromLTWH ( 35 , 85 , 40 , 40 ) ,
' 10 ' : const Rect . fromLTWH ( 25 , 110 , 25 , 50 ) ,
' 11 ' : const Rect . fromLTWH ( 60 , 110 , 25 , 50 ) ,
' 12 ' : const Rect . fromLTWH ( 35 , 175 , 40 , 40 ) ,
' 1 ' : const Rect . fromLTWH ( 90 , 50 , 35 , 40 ) ,
' 8 ' : const Rect . fromLTWH ( 127 , 50 , 35 , 40 ) ,
' 2 ' : const Rect . fromLTWH ( 87 , 90 , 30 , 60 ) ,
' 7 ' : const Rect . fromLTWH ( 138 , 90 , 30 , 60 ) ,
' 3 ' : const Rect . fromLTWH ( 80 , 150 , 35 , 40 ) ,
' 6 ' : const Rect . fromLTWH ( 130 , 150 , 30 , 40 ) ,
' 4 ' : const Rect . fromLTWH ( 100 , 190 , 25 , 30 ) ,
' 5 ' : const Rect . fromLTWH ( 135 , 190 , 25 , 30 ) ,
' 13 ' : const Rect . fromLTWH ( 180 , 35 , 30 , 40 ) ,
' 14 ' : const Rect . fromLTWH ( 210 , 35 , 30 , 40 ) ,
' 15 ' : const Rect . fromLTWH ( 240 , 35 , 30 , 40 ) ,
' 16 ' : const Rect . fromLTWH ( 172 , 75 , 30 , 70 ) ,
' 17 ' : const Rect . fromLTWH ( 240 , 75 , 30 , 70 ) ,
' 19 ' : const Rect . fromLTWH ( 165 , 145 , 40 , 60 ) ,
' 18 ' : const Rect . fromLTWH ( 238 , 145 , 40 , 60 ) ,
' 20 ' : const Rect . fromLTWH ( 200 , 195 , 50 , 25 ) ,
} ;
}
// --- MAPPATURA LATO B (GIALLO) ---
Map < String , Rect > _getHitboxesB ( ) {
return {
' M9 ' : const Rect . fromLTWH ( 35 , 78 , 40 , 40 ) ,
' M10 ' : const Rect . fromLTWH ( 25 , 110 , 25 , 50 ) ,
' M11 ' : const Rect . fromLTWH ( 60 , 110 , 25 , 50 ) ,
' M12 ' : const Rect . fromLTWH ( 35 , 175 , 40 , 40 ) ,
' 21 ' : const Rect . fromLTWH ( 88 , 50 , 35 , 40 ) ,
' 22 ' : const Rect . fromLTWH ( 130 , 50 , 35 , 40 ) ,
' 23 ' : const Rect . fromLTWH ( 85 , 90 , 30 , 60 ) ,
' 24 ' : const Rect . fromLTWH ( 140 , 90 , 30 , 60 ) ,
' 25 ' : const Rect . fromLTWH ( 95 , 190 , 25 , 30 ) ,
' 26 ' : const Rect . fromLTWH ( 138 , 190 , 25 , 30 ) ,
' 27 ' : const Rect . fromLTWH ( 180 , 35 , 30 , 40 ) ,
' 34 ' : const Rect . fromLTWH ( 210 , 35 , 30 , 40 ) ,
' 28 ' : const Rect . fromLTWH ( 240 , 35 , 30 , 40 ) ,
' 29 ' : const Rect . fromLTWH ( 170 , 75 , 30 , 90 ) ,
' 30 ' : const Rect . fromLTWH ( 245 , 75 , 30 , 90 ) ,
' 31 ' : const Rect . fromLTWH ( 168 , 171 , 40 , 60 ) ,
' 33 ' : const Rect . fromLTWH ( 208 , 178 , 31 , 60 ) ,
' 32 ' : const Rect . fromLTWH ( 239 , 171 , 40 , 60 ) ,
} ;
}
void _handleTap ( TapUpDetails details ) {
Offset touchPosition = details . localPosition ;
String ? foundSector ;
_hitboxes . forEach ( ( key , rect ) {
if ( rect . contains ( touchPosition ) ) {
foundSector = key ;
}
} ) ;
if ( foundSector ! = null ) {
setState ( ( ) {
if ( _selectedSectors . contains ( foundSector ) ) {
_selectedSectors . remove ( foundSector ) ;
} else {
_selectedSectors . add ( foundSector ! ) ;
}
} ) ;
}
}
void _salvaEProsegui ( ) {
setState ( ( ) {
if ( isB ) {
GlobalData . puntiUrtoB_List = _selectedSectors . toList ( ) ;
GlobalData . danni_visibili_B = _controllerDanni . text . toUpperCase ( ) ;
GlobalData . osservazioni_B = _controllerOsservazioni . text . toUpperCase ( ) ;
} else {
GlobalData . puntiUrtoA_List = _selectedSectors . toList ( ) ;
GlobalData . danni_visibili_A = _controllerDanni . text . toUpperCase ( ) ;
GlobalData . osservazioni_A = _controllerOsservazioni . text . toUpperCase ( ) ;
}
} ) ;
Navigator . push ( context , MaterialPageRoute ( builder: ( c ) = > const Comp12Screen ( ) ) ) ;
}
@ override
Widget build ( BuildContext context ) {
Color mainCol = isB ? Colors . amber . shade700 : Colors . blue . shade900 ;
Color bgCol = isB ? const Color ( 0xFFFFF9C4 ) : const Color ( 0xFFE3F2FD ) ;
return GestureDetector (
onTap: ( ) = > FocusScope . of ( context ) . unfocus ( ) ,
child: Scaffold (
backgroundColor: bgCol ,
appBar: AppBar (
title: Text ( " 10, 11, 14. Dettagli ( ${ GlobalData . latoCorrente } ) " ) ,
backgroundColor: mainCol ,
foregroundColor: isB ? Colors . black : Colors . white ,
) ,
// --- SLIVER LAYOUT ---
body: SafeArea (
child: CustomScrollView (
physics: const BouncingScrollPhysics ( ) ,
slivers: [
SliverPadding (
padding: const EdgeInsets . all ( 16 ) ,
sliver: SliverList (
delegate: SliverChildListDelegate ( [
_buildTitle ( " 10. PUNTO D'URTO INIZIALE (Seleziona anche più di uno) " , mainCol ) ,
const SizedBox ( height: 10 ) ,
Center (
child: Card (
elevation: 4 ,
shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 15 ) ) ,
child: GestureDetector (
onTapUp: _handleTap ,
child: Container (
width: _canvasWidth ,
height: _canvasHeight ,
decoration: BoxDecoration ( color: Colors . white , borderRadius: BorderRadius . circular ( 15 ) ) ,
child: Stack (
children: [
Center ( child: Image . asset (
isB ? ' assets/punti_danni_B.png ' : ' assets/punti_danni_A.png ' ,
width: _canvasWidth , height: _canvasHeight , fit: BoxFit . contain ,
) ) ,
. . . _selectedSectors . map ( ( sectorId ) {
if ( _hitboxes . containsKey ( sectorId ) ) {
return CustomPaint (
painter: XPainter ( _hitboxes [ sectorId ] ! ) ,
size: Size ( _canvasWidth , _canvasHeight ) ,
) ;
}
return Container ( ) ;
} ) ,
] ,
) ,
) ,
) ,
) ,
) ,
if ( _selectedSectors . isNotEmpty )
Padding (
padding: const EdgeInsets . only ( top: 8.0 ) ,
child: Text ( " Punti: ${ _selectedSectors . join ( ' , ' ) } " ,
style: TextStyle ( color: mainCol , fontWeight: FontWeight . bold ) ) ,
) ,
const Divider ( height: 30 ) ,
_buildTitle ( " 11. DANNI VISIBILI " , mainCol ) ,
TextField (
controller: _controllerDanni ,
// Max 45 caratteri per stare nelle 2 righe del PDF (20+25)
maxLength: 45 ,
maxLines: 2 ,
keyboardType: TextInputType . multiline ,
textCapitalization: TextCapitalization . characters ,
decoration: _inputDeco ( " Es: Paraurti ant, Faro sx... " ) ,
) ,
const SizedBox ( height: 10 ) ,
_buildTitle ( " 14. OSSERVAZIONI " , mainCol ) ,
TextField (
controller: _controllerOsservazioni ,
maxLength: 55 ,
maxLines: 2 ,
keyboardType: TextInputType . multiline ,
textCapitalization: TextCapitalization . sentences ,
decoration: _inputDeco ( " Es: Ho ragione io perché... " ) ,
) ,
] ) ,
) ,
) ,
// --- STICKY FOOTER ---
SliverFillRemaining (
hasScrollBody: false ,
child: Align (
alignment: Alignment . bottomCenter ,
child: Container (
padding: const EdgeInsets . all ( 16 ) ,
child: _navButtons ( context , mainCol ) ,
) ,
) ,
) ,
] ,
) ,
) ,
) ,
) ;
}
Widget _buildTitle ( String text , Color color ) {
return Align ( alignment: Alignment . centerLeft , child: Padding (
padding: const EdgeInsets . only ( bottom: 8.0 , top: 10.0 ) ,
child: Text ( text , style: TextStyle ( fontWeight: FontWeight . bold , fontSize: 16 , color: color ) ) ,
) ) ;
}
InputDecoration _inputDeco ( String hint ) {
return InputDecoration (
hintText: hint ,
filled: true ,
fillColor: Colors . white ,
border: OutlineInputBorder ( borderRadius: BorderRadius . circular ( 10 ) ) ,
contentPadding: const EdgeInsets . all ( 15 ) ,
// counterText: "", <-- RIMOSSO: Ora il contatore si vede!
) ;
}
Widget _navButtons ( BuildContext context , Color color ) {
return Row (
children: [
Expanded (
flex: 4 ,
child: OutlinedButton (
onPressed: ( ) = > Navigator . pop ( context ) ,
style: OutlinedButton . styleFrom (
minimumSize: const Size ( 0 , 55 ) ,
padding: const EdgeInsets . symmetric ( horizontal: 5 ) ,
) ,
child: const FittedBox (
child: Text ( " INDIETRO " , style: TextStyle ( fontWeight: FontWeight . bold ) )
)
)
) ,
const SizedBox ( width: 15 ) ,
Expanded (
flex: 6 ,
child: ElevatedButton (
style: ElevatedButton . styleFrom (
backgroundColor: color ,
foregroundColor: isB ? Colors . black : Colors . white ,
minimumSize: const Size ( 0 , 55 )
) ,
onPressed: _salvaEProsegui ,
child: const FittedBox (
child: Text ( " SALVA E PROSEGUI " , style: TextStyle ( fontWeight: FontWeight . bold ) )
)
)
) ,
] ,
) ;
}
}
class XPainter extends CustomPainter {
final Rect targetRect ;
XPainter ( this . targetRect ) ;
@ override
void paint ( Canvas canvas , Size size ) {
final Paint paint = Paint ( ) . . color = Colors . red . . strokeWidth = 3.0 . . strokeCap = StrokeCap . round . . style = PaintingStyle . stroke ;
double cx = targetRect . left + targetRect . width / 2 ;
double cy = targetRect . top + targetRect . height / 2 ;
double iconSize = 10.0 ;
canvas . drawLine ( Offset ( cx - iconSize , cy - iconSize ) , Offset ( cx + iconSize , cy + iconSize ) , paint ) ;
canvas . drawLine ( Offset ( cx + iconSize , cy - iconSize ) , Offset ( cx - iconSize , cy + iconSize ) , paint ) ;
}
@ override
bool shouldRepaint ( covariant CustomPainter oldDelegate ) = > true ;
}