This commit is contained in:
Diogo
2026-03-13 14:57:46 +00:00
parent cae3bbfe3b
commit 142f088763
4 changed files with 266 additions and 133 deletions

208
lib/calibrador_page.dart Normal file
View File

@@ -0,0 +1,208 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:math' as math;
class CalibradorPage extends StatefulWidget {
const CalibradorPage({super.key});
@override
State<CalibradorPage> createState() => _CalibradorPageState();
}
class _CalibradorPageState extends State<CalibradorPage> {
// --- 👇 VALORES INICIAIS 👇 ---
double hoopBaseX = 0.08;
double arcRadius = 0.28;
double cornerY = 0.40;
// -----------------------------------------------------
@override
void initState() {
super.initState();
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft,
]);
}
@override
void dispose() {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
super.dispose();
}
@override
Widget build(BuildContext context) {
final double wScreen = MediaQuery.of(context).size.width;
final double hScreen = MediaQuery.of(context).size.height;
// O MESMO CÁLCULO EXATO DO PLACAR
final double sf = math.min(wScreen / 1150, hScreen / 720);
return Scaffold(
backgroundColor: const Color(0xFF266174),
body: SafeArea(
top: false,
bottom: false,
child: Stack(
children: [
// 👇 1. O CAMPO COM AS MARGENS EXATAS DO PLACAR 👇
Container(
margin: EdgeInsets.only(left: 65 * sf, right: 65 * sf, bottom: 55 * sf),
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 2.5),
image: const DecorationImage(
image: AssetImage('assets/campo.png'),
fit: BoxFit.fill,
),
),
child: LayoutBuilder(
builder: (context, constraints) {
return CustomPaint(
painter: LinePainter(
hoopBaseX: hoopBaseX,
arcRadius: arcRadius,
cornerY: cornerY,
color: Colors.redAccent,
width: constraints.maxWidth,
height: constraints.maxHeight,
),
);
},
),
),
// 👇 2. TOPO: MOSTRADORES DE VALORES COM FITTEDBOX (Não transborda) 👇
Positioned(
top: 0, left: 0, right: 0,
child: Container(
color: Colors.black87.withOpacity(0.8),
padding: EdgeInsets.symmetric(vertical: 5 * sf, horizontal: 15 * sf),
child: FittedBox( // Isto impede o ecrã de dar o erro dos 179 pixels!
fit: BoxFit.scaleDown,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildValueDisplay("Aro X", hoopBaseX, sf),
SizedBox(width: 20 * sf),
_buildValueDisplay("Raio", arcRadius, sf),
SizedBox(width: 20 * sf),
_buildValueDisplay("Canto", cornerY, sf),
SizedBox(width: 30 * sf),
ElevatedButton.icon(
onPressed: () => Navigator.pop(context),
icon: Icon(Icons.check, size: 18 * sf),
label: Text("FECHAR", style: TextStyle(fontSize: 14 * sf, fontWeight: FontWeight.bold)),
style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
)
],
),
),
),
),
// 👇 3. FUNDO: SLIDERS (Com altura fixa para não dar o erro "hasSize") 👇
Positioned(
bottom: 0, left: 0, right: 0,
child: Container(
color: Colors.black87.withOpacity(0.8),
height: 80 * sf, // Altura segura para os sliders
child: Row(
children: [
Expanded(child: _buildSlider("Pos. do Aro", hoopBaseX, 0.0, 0.25, (val) => setState(() => hoopBaseX = val), sf)),
Expanded(child: _buildSlider("Tam. da Curva", arcRadius, 0.1, 0.5, (val) => setState(() => arcRadius = val), sf)),
Expanded(child: _buildSlider("Pos. do Canto", cornerY, 0.2, 0.5, (val) => setState(() => cornerY = val), sf)),
],
),
),
),
],
),
),
);
}
Widget _buildValueDisplay(String label, double value, double sf) {
return Row(
children: [
Text("$label: ", style: TextStyle(color: Colors.white70, fontSize: 16 * sf)),
Text(value.toStringAsFixed(3), style: TextStyle(color: Colors.yellow, fontSize: 20 * sf, fontWeight: FontWeight.bold)),
],
);
}
Widget _buildSlider(String label, double value, double min, double max, ValueChanged<double> onChanged, double sf) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(label, style: TextStyle(color: Colors.white, fontSize: 12 * sf)),
SizedBox(
height: 40 * sf, // Altura exata para o Slider não crashar
child: Slider(
value: value, min: min, max: max,
activeColor: Colors.yellow, inactiveColor: Colors.white24,
onChanged: onChanged,
),
),
],
);
}
}
// ==============================================================
// 📐 PINTOR: DESENHA A LINHA MATEMÁTICA NA TELA
// ==============================================================
class LinePainter extends CustomPainter {
final double hoopBaseX;
final double arcRadius;
final double cornerY;
final Color color;
final double width;
final double height;
LinePainter({
required this.hoopBaseX, required this.arcRadius, required this.cornerY,
required this.color, required this.width, required this.height,
});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = 4;
double aspectRatio = width / height;
double hoopY = 0.50 * height;
// O cornerY controla a que distância do meio (50%) estão as linhas retas
double cornerDistY = cornerY * height;
// --- CESTO ESQUERDO ---
double hoopLX = hoopBaseX * width;
canvas.drawLine(Offset(0, hoopY - cornerDistY), Offset(width * 0.35, hoopY - cornerDistY), paint); // Cima
canvas.drawLine(Offset(0, hoopY + cornerDistY), Offset(width * 0.35, hoopY + cornerDistY), paint); // Baixo
canvas.drawArc(
Rect.fromCenter(center: Offset(hoopLX, hoopY), width: arcRadius * width * 2 / aspectRatio, height: arcRadius * height * 2),
-math.pi / 2, math.pi, false, paint,
);
// --- CESTO DIREITO ---
double hoopRX = (1.0 - hoopBaseX) * width;
canvas.drawLine(Offset(width, hoopY - cornerDistY), Offset(width * 0.65, hoopY - cornerDistY), paint); // Cima
canvas.drawLine(Offset(width, hoopY + cornerDistY), Offset(width * 0.65, hoopY + cornerDistY), paint); // Baixo
canvas.drawArc(
Rect.fromCenter(center: Offset(hoopRX, hoopY), width: arcRadius * width * 2 / aspectRatio, height: arcRadius * height * 2),
math.pi / 2, math.pi, false, paint,
);
}
@override
bool shouldRepaint(covariant LinePainter oldDelegate) {
return oldDelegate.hoopBaseX != hoopBaseX || oldDelegate.arcRadius != arcRadius || oldDelegate.cornerY != cornerY;
}
}