import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'global_data.dart'; import 'models.dart'; import 'comp_15.dart'; class Comp13Screen extends StatefulWidget { const Comp13Screen({super.key}); @override State createState() => _Comp13ScreenState(); } class _Comp13ScreenState extends State { List _elementi = []; List _tratti = []; String modo = 'penna'; @override void initState() { super.initState(); _elementi = List.from(GlobalData.elementi); _tratti = List.from(GlobalData.tratti); // Proviamo a chiedere il landscape al sistema (non sempre funziona su iPad) _setLandscape(); WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) _mostraIstruzioni(); }); } Future _setLandscape() async { await SystemChrome.setPreferredOrientations([ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]); } Future _setPortrait() async { await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); } Future _esci() async { GlobalData.tratti = List.from(_tratti); GlobalData.elementi = List.from(_elementi); await _setPortrait(); if (mounted) Navigator.pop(context); } Future _vaiAvanti() async { GlobalData.tratti = List.from(_tratti); GlobalData.elementi = List.from(_elementi); // Prima di cambiare pagina, rimettiamo in verticale // await _setPortrait(); if (mounted) { await Navigator.push(context, MaterialPageRoute(builder: (c) => const Comp15Screen())); // Al ritorno, forziamo di nuovo orizzontale await _setLandscape(); } } // --- FUNZIONE HELPER PER RUOTARE I DIALOGHI --- // Se l'iPad รจ verticale, ruota il contenuto del dialogo di 90 gradi Widget _ruotaSeNecessario(BuildContext context, Widget child) { return OrientationBuilder( builder: (context, orientation) { return orientation == Orientation.portrait ? RotatedBox(quarterTurns: 1, child: child) : child; }, ); } void _mostraIstruzioni() { showDialog( context: context, builder: (ctx) => _ruotaSeNecessario(ctx, AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), title: Row( children: [ Icon(Icons.help_outline, color: Colors.blue.shade900), const SizedBox(width: 10), const Text("Guida al Disegno", style: TextStyle(fontWeight: FontWeight.bold)), ], ), content: SizedBox( width: 500, // Larghezza fissa per evitare problemi di layout ruotato height: 300, // Altezza fissa per scrollare comodamente child: Scrollbar( thumbVisibility: true, child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: const Text( "โ€ข ๐Ÿ–Š Usa la penna per disegnare le strade\n" "โ€ข ๐Ÿš— ๐Ÿ๏ธ ๐Ÿš› Seleziona e tocca per aggiungere veicoli\n" "โ€ข โ†—๏ธ Inserisci frecce di direzione\n" "โ€ข ๐Ÿ“ Aggiungi testo (es. nomi vie)\n" "โ€ข ๐Ÿ”„ Tocca un elemento per ruotarlo\n" "โ€ข โŒ Premi a lungo un oggetto per eliminarlo\n" "โ€ข ๐Ÿ—‘๏ธ Usa il tasto Cestino per resettare tutto", style: TextStyle(fontSize: 16, height: 1.6, color: Colors.black87), ), ), ), ), actions: [ SizedBox( width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.blue.shade900, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), onPressed: () => Navigator.pop(ctx), child: const Text("HO CAPITO", style: TextStyle(fontWeight: FontWeight.bold)), ), ) ], )), ); } void _gestisciInserimento(Offset pos) async { if (modo == 'testo') { TextEditingController tc = TextEditingController(); await showDialog( context: context, builder: (c) => _ruotaSeNecessario(c, Dialog( backgroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), child: Container( width: 300, padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text("Inserisci Testo", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 10), TextField( controller: tc, autofocus: true, decoration: const InputDecoration(hintText: "Nome via...", border: OutlineInputBorder()) ), const SizedBox(height: 20), ElevatedButton( onPressed: () { if (tc.text.isNotEmpty) { setState(() => _elementi.add(ElementoGrafico(pos, 'testo', label: tc.text))); } Navigator.pop(c); }, child: const Text("INSERISCI") ) ], ), ), )), ); } else if (['auto', 'moto', 'furgone'].contains(modo)) { int numeroVeicoli = _elementi.where((e) => e.tipo.startsWith('auto') || e.tipo.startsWith('moto') || e.tipo.startsWith('furgone')).length; String prossimaLettera = String.fromCharCode(65 + numeroVeicoli); setState(() { _elementi.add(ElementoGrafico(pos, '$modo$prossimaLettera', label: prossimaLettera)); }); } } @override Widget build(BuildContext context) { // 1. Logica di orientamento principale (Body) // Se siamo verticali, ruotiamo tutto di 90 gradi. return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) async { if (!didPop) await _esci(); }, child: OrientationBuilder( builder: (context, orientation) { final bool isPortrait = orientation == Orientation.portrait; return Scaffold( backgroundColor: Colors.white, resizeToAvoidBottomInset: false, // Se Portrait -> Ruota Body. Se Landscape -> Body normale. body: isPortrait ? RotatedBox(quarterTurns: 1, child: _buildBodyContent(context)) : _buildBodyContent(context), ); }, ), ); } // Contenuto principale estratto per facilitare la rotazione Widget _buildBodyContent(BuildContext context) { // Calcoliamo dimensioni sicure final size = MediaQuery.of(context).size; final double maxToolbarHeight = size.shortestSide * 0.8; return SafeArea( child: Stack( children: [ Column( children: [ Container( height: 50, color: Colors.blueGrey[50], padding: const EdgeInsets.symmetric(horizontal: 10), child: Row( children: [ IconButton( icon: const Icon(Icons.arrow_back, color: Colors.black), onPressed: _esci, ), const Expanded( child: Text( "13. Grafico", textAlign: TextAlign.center, style: TextStyle(color: Colors.black, fontSize: 18, fontWeight: FontWeight.bold), ), ), IconButton( icon: const Icon(Icons.help_outline, color: Colors.black), onPressed: _mostraIstruzioni ), const SizedBox(width: 15), ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: Colors.green, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8) ), icon: const Icon(Icons.check), label: const Text("SALVA"), onPressed: _vaiAvanti, ), ], ), ), Expanded( child: GestureDetector( onLongPressStart: (d) { setState(() { _elementi.removeWhere((e) => e.contiene(d.localPosition)); _tratti.removeWhere((t) => t.contiene(d.localPosition)); }); }, onTapDown: (d) { bool colpito = false; for (var e in _elementi) { if (e.contiene(d.localPosition)) { setState(() => e.rotazione += 0.785); colpito = true; break; } } if (!colpito && (['auto', 'moto', 'furgone', 'testo'].contains(modo))) { _gestisciInserimento(d.localPosition); } }, onPanStart: (d) { if (modo == 'penna' || modo == 'freccia') { setState(() => _tratti.add(TrattoPenna([d.localPosition], tipo: modo))); } }, onPanUpdate: (d) { if (modo == 'penna' || modo == 'freccia') { setState(() => _tratti.last.punti.add(d.localPosition)); } }, child: Container( color: Colors.white, width: double.infinity, height: double.infinity, child: CustomPaint( painter: PainterV40(_tratti, _elementi), size: Size.infinite, ), ), ), ), ], ), Positioned( left: 10, top: 60, child: Container( width: 55, constraints: BoxConstraints(maxHeight: maxToolbarHeight), decoration: BoxDecoration( color: Colors.white.withOpacity(0.95), borderRadius: BorderRadius.circular(25), boxShadow: [const BoxShadow(color: Colors.black26, blurRadius: 4)], border: Border.all(color: Colors.grey.shade300), ), child: ClipRRect( borderRadius: BorderRadius.circular(25), child: SingleChildScrollView( padding: const EdgeInsets.symmetric(vertical: 8), child: Column( mainAxisSize: MainAxisSize.min, children: [ _toolBtn(Icons.edit, 'penna', Colors.black), _toolBtn(Icons.trending_flat, 'freccia', Colors.black), _toolBtn(Icons.title, 'testo', Colors.black), const Divider(indent: 8, endIndent: 8, height: 15), _toolBtn(Icons.directions_car, 'auto', Colors.blue), _toolBtn(Icons.two_wheeler, 'moto', Colors.orange.shade800), _toolBtn(Icons.local_shipping, 'furgone', Colors.green), const Divider(indent: 8, endIndent: 8, height: 15), IconButton( icon: const Icon(Icons.delete_forever, color: Colors.red), tooltip: "Cancella tutto", onPressed: () => setState(() { _tratti.clear(); _elementi.clear(); }), ), ], ), ), ), ), ), ], ), ); } Widget _toolBtn(IconData icon, String tool, Color activeColor) { bool isSelected = modo == tool; return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(20), onTap: () => setState(() => modo = tool), child: Container( width: 40, height: 40, decoration: BoxDecoration( color: isSelected ? activeColor.withOpacity(0.15) : Colors.transparent, border: isSelected ? Border.all(color: activeColor, width: 2) : null, shape: BoxShape.circle, ), child: Icon( icon, color: activeColor, size: 24, ), ), ), ), ); } } class PainterV40 extends CustomPainter { final List tr; final List el; PainterV40(this.tr, this.el); final List palette = [Colors.blue, Colors.orange, Colors.green, Colors.purple, Colors.red, Colors.teal]; @override void paint(Canvas canvas, Size size) { Paint pStrada = Paint()..color = Colors.black..strokeWidth = 3.0..style = PaintingStyle.stroke..strokeCap = StrokeCap.round; for (var t in tr) { if (t.punti.length > 1) { // Disegno il tratto stradale Path path = Path()..moveTo(t.punti[0].dx, t.punti[0].dy); for (var pt in t.punti) path.lineTo(pt.dx, pt.dy); canvas.drawPath(path, pStrada); // --- CORREZIONE FRECCIA --- if (t.tipo == 'freccia') { Offset pTip = t.punti.last; Offset pBack = t.punti[t.punti.length - 2]; // STABILIZZAZIONE: Cerco un punto precedente che sia distante almeno 10 pixel // dalla punta. Questo ignora i micro-movimenti finali del dito (jitter) // che causavano l'inversione della freccia. for (int i = t.punti.length - 2; i >= 0; i--) { if ((t.punti[i] - pTip).distance > 10.0) { pBack = t.punti[i]; break; } } _disegnaPunta(canvas, pBack, pTip, pStrada); } // -------------------------- } } for (var e in el) { canvas.save(); canvas.translate(e.posizione.dx, e.posizione.dy); canvas.rotate(e.rotazione); if (e.tipo == 'testo') { _disegnaTesto(canvas, e.label ?? ""); } else if (e.tipo.startsWith('auto')) { _disegnaAuto(canvas, e); } else if (e.tipo.startsWith('moto')) { _disegnaMoto(canvas, e); } else if (e.tipo.startsWith('furgone')) { _disegnaFurgone(canvas, e); } canvas.restore(); } } // ... (TUTTI GLI ALTRI METODI RESTANO IDENTICI) ... Color _getColoreDaLettera(String lettera) { if (lettera.isEmpty) return Colors.grey; int idx = (lettera.codeUnitAt(0) - 65) % palette.length; return palette[idx]; } void _disegnaLettera(Canvas canvas, String lettera, {double fontSize = 16}) { final tp = TextPainter( text: TextSpan(text: lettera, style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: fontSize)), textDirection: TextDirection.ltr )..layout(); tp.paint(canvas, Offset(-tp.width / 2, -tp.height / 2)); } void _disegnaAuto(Canvas canvas, ElementoGrafico e) { String lettera = e.label ?? "A"; Color colore = _getColoreDaLettera(lettera); double w = 48.0; double h = 24.0; Paint pBody = Paint()..color = colore; Paint pBorder = Paint()..style = PaintingStyle.stroke..color = Colors.black..strokeWidth = 1.5; RRect bodyRect = RRect.fromRectAndRadius(Rect.fromCenter(center: Offset.zero, width: w, height: h), const Radius.circular(5)); canvas.drawRRect(bodyRect, pBody); Paint pCabin = Paint()..color = Colors.black.withOpacity(0.2); canvas.drawRect(Rect.fromLTRB(-w/2 + 6, -h/2 + 3, w/4, h/2 - 3), pCabin); Paint pLights = Paint()..color = Colors.yellow; canvas.drawCircle(Offset(w/2 - 2, h/2 - 4), 2.5, pLights); canvas.drawCircle(Offset(w/2 - 2, -h/2 + 4), 2.5, pLights); canvas.drawRRect(bodyRect, pBorder); _disegnaLettera(canvas, lettera); } void _disegnaMoto(Canvas canvas, ElementoGrafico e) { String lettera = e.label ?? "A"; Color colore = _getColoreDaLettera(lettera); double len = 40.0; double wid = 14.0; Paint pBody = Paint()..color = colore; Paint pBlack = Paint()..color = Colors.black; Paint pBorder = Paint()..style = PaintingStyle.stroke..color = Colors.black..strokeWidth = 1.0; canvas.drawCircle(Offset(-len/2 + 4, 0), 5.0, pBlack); canvas.drawCircle(Offset(len/2 - 4, 0), 5.0, pBlack); Rect bodyRect = Rect.fromCenter(center: Offset.zero, width: len - 10, height: wid - 4); canvas.drawRRect(RRect.fromRectAndRadius(bodyRect, const Radius.circular(4)), pBody); canvas.drawRRect(RRect.fromRectAndRadius(bodyRect, const Radius.circular(4)), pBorder); Rect sellaRect = Rect.fromCenter(center: Offset(-5, 0), width: 12, height: 8); canvas.drawRect(sellaRect, pBlack); Paint pManubrio = Paint()..color = Colors.black..strokeWidth = 3.0..strokeCap = StrokeCap.round; double xHandle = len/2 - 12; canvas.drawLine(Offset(xHandle, -10), Offset(xHandle, 10), pManubrio); Paint pLights = Paint()..color = Colors.yellow; canvas.drawCircle(Offset(len/2, 0), 3.0, pLights); _disegnaLettera(canvas, lettera, fontSize: 10); } void _disegnaFurgone(Canvas canvas, ElementoGrafico e) { String lettera = e.label ?? "A"; Color colore = _getColoreDaLettera(lettera); double w = 60.0; double h = 26.0; Paint pBody = Paint()..color = colore; Paint pBorder = Paint()..style = PaintingStyle.stroke..color = Colors.black..strokeWidth = 1.5; Rect caricoRect = Rect.fromLTRB(-w/2, -h/2, w/4, h/2); canvas.drawRect(caricoRect, pBody); canvas.drawRect(caricoRect, pBorder); Rect cabinaRect = Rect.fromLTRB(w/4, -h/2 + 1, w/2, h/2 - 1); canvas.drawRect(cabinaRect, pBody); canvas.drawRect(cabinaRect, pBorder); Paint pVetro = Paint()..color = Colors.black.withOpacity(0.3); canvas.drawRect(Rect.fromLTRB(w/4 + 2, -h/2 + 3, w/2 - 2, h/2 - 3), pVetro); Paint pLights = Paint()..color = Colors.yellow; canvas.drawRect(Rect.fromLTWH(w/2 - 2, -h/2 + 2, 2, 4), pLights); canvas.drawRect(Rect.fromLTWH(w/2 - 2, h/2 - 6, 2, 4), pLights); _disegnaLettera(canvas, lettera); } void _disegnaTesto(Canvas canvas, String txt) { final tp = TextPainter( text: TextSpan(text: txt, style: const TextStyle(color: Colors.black, fontSize: 20, fontWeight: FontWeight.bold)), textDirection: TextDirection.ltr )..layout(); tp.paint(canvas, Offset(-tp.width/2, -tp.height/2)); } void _disegnaPunta(Canvas canvas, Offset p1, Offset p2, Paint paint) { double angle = (p2 - p1).direction; canvas.drawLine(p2, p2 - Offset.fromDirection(angle - 0.5, 10), paint); canvas.drawLine(p2, p2 - Offset.fromDirection(angle + 0.5, 10), paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; }