cid_app/lib/comp_10.dart

403 lines
No EOL
15 KiB
Dart

// 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",
barrierColor: Colors.black.withOpacity(0.5),
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;
}