bora vamos

This commit is contained in:
2026-03-18 12:39:03 +00:00
parent b77ae2eac6
commit 8adea3f7b6

View File

@@ -2,13 +2,13 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:playmaker/controllers/placar_controller.dart';
import 'package:playmaker/utils/size_extension.dart';
import 'package:playmaker/classe/theme.dart'; // 👇 IMPORT DO TEU TEMA!
import 'package:playmaker/classe/theme.dart';
import 'dart:math' as math;
import 'package:playmaker/zone_map_dialog.dart';
// ============================================================================
// 1. PLACAR SUPERIOR (CRONÓMETRO E RESULTADO)
// 1. PLACAR SUPERIOR (CRONÓMETRO E RESULTADO) - TAMANHO REDUZIDO
// ============================================================================
class TopScoreboard extends StatelessWidget {
final PlacarController controller;
@@ -19,43 +19,44 @@ class TopScoreboard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(vertical: 10 * sf, horizontal: 35 * sf),
// 👇 Reduzido padding vertical e horizontal
padding: EdgeInsets.symmetric(vertical: 6 * sf, horizontal: 20 * sf),
decoration: BoxDecoration(
color: AppTheme.placarDarkSurface, // 🎨 USANDO TEMA
color: AppTheme.placarDarkSurface,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(22 * sf),
bottomRight: Radius.circular(22 * sf)
),
border: Border.all(color: Colors.white, width: 2.5 * sf),
border: Border.all(color: Colors.white, width: 2.0 * sf),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_buildTeamSection(controller.myTeam, controller.myScore, controller.myFouls, controller.myTimeoutsUsed, AppTheme.myTeamBlue, false, sf), // 🎨 USANDO TEMA
SizedBox(width: 30 * sf),
_buildTeamSection(controller.myTeam, controller.myScore, controller.myFouls, controller.myTimeoutsUsed, AppTheme.myTeamBlue, false, sf),
SizedBox(width: 20 * sf), // 👇 Reduzido espaçamento central
Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 18 * sf, vertical: 5 * sf),
padding: EdgeInsets.symmetric(horizontal: 14 * sf, vertical: 4 * sf), // 👇 Reduzido
decoration: BoxDecoration(
color: AppTheme.placarTimerBg, // 🎨 USANDO TEMA
color: AppTheme.placarTimerBg,
borderRadius: BorderRadius.circular(9 * sf)
),
child: Text(
controller.formatTime(),
style: TextStyle(color: Colors.white, fontSize: 28 * sf, fontWeight: FontWeight.w900, fontFamily: 'monospace', letterSpacing: 2 * sf)
style: TextStyle(color: Colors.white, fontSize: 24 * sf, fontWeight: FontWeight.w900, fontFamily: 'monospace', letterSpacing: 1.5 * sf) // 👇 Fonte reduzida
),
),
SizedBox(height: 5 * sf),
SizedBox(height: 4 * sf),
Text(
"PERÍODO ${controller.currentQuarter}",
style: TextStyle(color: AppTheme.warningAmber, fontSize: 14 * sf, fontWeight: FontWeight.w900)
style: TextStyle(color: AppTheme.warningAmber, fontSize: 12 * sf, fontWeight: FontWeight.w900) // 👇 Fonte reduzida
),
],
),
SizedBox(width: 30 * sf),
_buildTeamSection(controller.opponentTeam, controller.opponentScore, controller.opponentFouls, controller.opponentTimeoutsUsed, AppTheme.oppTeamRed, true, sf), // 🎨 USANDO TEMA
SizedBox(width: 20 * sf), // 👇 Reduzido espaçamento central
_buildTeamSection(controller.opponentTeam, controller.opponentScore, controller.opponentFouls, controller.opponentTimeoutsUsed, AppTheme.oppTeamRed, true, sf),
],
),
);
@@ -67,12 +68,12 @@ class TopScoreboard extends StatelessWidget {
final timeoutIndicators = Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(3, (index) => Container(
margin: EdgeInsets.symmetric(horizontal: 3.5 * sf),
width: 12 * sf, height: 12 * sf,
margin: EdgeInsets.symmetric(horizontal: 2.5 * sf), // 👇 Reduzido
width: 10 * sf, height: 10 * sf, // 👇 Bolas de timeout menores
decoration: BoxDecoration(
shape: BoxShape.circle,
color: index < timeouts ? AppTheme.warningAmber : Colors.grey.shade600,
border: Border.all(color: Colors.white54, width: 1.5 * sf)
border: Border.all(color: Colors.white54, width: 1.0 * sf)
),
)),
);
@@ -81,22 +82,22 @@ class TopScoreboard extends StatelessWidget {
Column(
children: [
_scoreBox(score, color, sf),
SizedBox(height: 7 * sf),
SizedBox(height: 5 * sf), // 👇 Reduzido
timeoutIndicators
]
),
SizedBox(width: 18 * sf),
SizedBox(width: 12 * sf), // 👇 Reduzido
Column(
crossAxisAlignment: isOpp ? CrossAxisAlignment.start : CrossAxisAlignment.end,
children: [
Text(
name.toUpperCase(),
style: TextStyle(color: Colors.white, fontSize: 20 * sf, fontWeight: FontWeight.w900, letterSpacing: 1.2 * sf)
style: TextStyle(color: Colors.white, fontSize: 16 * sf, fontWeight: FontWeight.w900, letterSpacing: 1.0 * sf) // 👇 Fonte reduzida
),
SizedBox(height: 5 * sf),
SizedBox(height: 3 * sf), // 👇 Reduzido
Text(
"FALTAS: $displayFouls",
style: TextStyle(color: displayFouls >= 5 ? AppTheme.actionMiss : AppTheme.warningAmber, fontSize: 13 * sf, fontWeight: FontWeight.bold)
style: TextStyle(color: displayFouls >= 5 ? AppTheme.actionMiss : AppTheme.warningAmber, fontSize: 11 * sf, fontWeight: FontWeight.bold) // 👇 Fonte reduzida
),
],
)
@@ -106,15 +107,15 @@ class TopScoreboard extends StatelessWidget {
}
Widget _scoreBox(int score, Color color, double sf) => Container(
width: 58 * sf, height: 45 * sf,
width: 45 * sf, height: 35 * sf, // 👇 Caixa de pontuação menor
alignment: Alignment.center,
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(7 * sf)),
child: Text(score.toString(), style: TextStyle(color: Colors.white, fontSize: 26 * sf, fontWeight: FontWeight.w900)),
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(6 * sf)),
child: Text(score.toString(), style: TextStyle(color: Colors.white, fontSize: 20 * sf, fontWeight: FontWeight.w900)), // 👇 Fonte reduzida
);
}
// ============================================================================
// 2. BANCO DE SUPLENTES (DRAG & DROP)
// 2. BANCO DE SUPLENTES (DRAG & DROP) - TAMANHO REDUZIDO
// ============================================================================
class BenchPlayersList extends StatelessWidget {
final PlacarController controller;
@@ -126,7 +127,7 @@ class BenchPlayersList extends StatelessWidget {
@override
Widget build(BuildContext context) {
final bench = isOpponent ? controller.oppBench : controller.myBench;
final teamColor = isOpponent ? AppTheme.oppTeamRed : AppTheme.myTeamBlue; // 🎨 TEMA
final teamColor = isOpponent ? AppTheme.oppTeamRed : AppTheme.myTeamBlue;
final prefix = isOpponent ? "bench_opp_" : "bench_my_";
return Column(
@@ -137,20 +138,20 @@ class BenchPlayersList extends StatelessWidget {
final bool isFouledOut = fouls >= 5;
Widget avatarUI = Container(
margin: EdgeInsets.only(bottom: 7 * sf),
margin: EdgeInsets.only(bottom: 5 * sf), // 👇 Reduzido
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 1.8 * sf),
boxShadow: [BoxShadow(color: Colors.black45, blurRadius: 5 * sf, offset: Offset(0, 2.5 * sf))]
border: Border.all(color: Colors.white, width: 1.5 * sf), // 👇 Reduzido
boxShadow: [BoxShadow(color: Colors.black45, blurRadius: 4 * sf, offset: Offset(0, 2.0 * sf))]
),
child: CircleAvatar(
radius: 22 * sf,
radius: 18 * sf, // 👇 Avatar do banco menor
backgroundColor: isFouledOut ? Colors.grey.shade800 : teamColor,
child: Text(
num,
style: TextStyle(
color: isFouledOut ? Colors.red.shade300 : Colors.white,
fontSize: 16 * sf,
fontSize: 14 * sf, // 👇 Fonte reduzida
fontWeight: FontWeight.bold,
decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none
)
@@ -170,12 +171,12 @@ class BenchPlayersList extends StatelessWidget {
feedback: Material(
color: Colors.transparent,
child: CircleAvatar(
radius: 28 * sf,
radius: 22 * sf, // 👇 Avatar ao arrastar menor
backgroundColor: teamColor,
child: Text(num, style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 18 * sf))
child: Text(num, style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16 * sf))
)
),
childWhenDragging: Opacity(opacity: 0.5, child: SizedBox(width: 45 * sf, height: 45 * sf)),
childWhenDragging: Opacity(opacity: 0.5, child: SizedBox(width: 36 * sf, height: 36 * sf)), // 👇 Placeholder menor
child: avatarUI,
);
}).toList(),
@@ -184,7 +185,7 @@ class BenchPlayersList extends StatelessWidget {
}
// ============================================================================
// 3. CARTÃO DO JOGADOR NO CAMPO (TARGET DE FALTAS/PONTOS/SUBSTITUIÇÕES)
// 3. CARTÃO DO JOGADOR NO CAMPO - TAMANHO REDUZIDO
// ============================================================================
class PlayerCourtCard extends StatelessWidget {
final PlacarController controller;
@@ -196,7 +197,7 @@ class PlayerCourtCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final teamColor = isOpponent ? AppTheme.oppTeamRed : AppTheme.myTeamBlue; // 🎨 TEMA
final teamColor = isOpponent ? AppTheme.oppTeamRed : AppTheme.myTeamBlue;
final stats = controller.playerStats[name]!;
final number = controller.playerNumbers[name]!;
final prefix = isOpponent ? "player_opp_" : "player_my_";
@@ -206,9 +207,9 @@ class PlayerCourtCard extends StatelessWidget {
feedback: Material(
color: Colors.transparent,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(color: teamColor.withOpacity(0.9), borderRadius: BorderRadius.circular(8)),
child: Text(name, style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), // 👇 Reduzido
decoration: BoxDecoration(color: teamColor.withOpacity(0.9), borderRadius: BorderRadius.circular(6)),
child: Text(name, style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)),
),
),
childWhenDragging: Opacity(opacity: 0.5, child: _playerCardUI(number, name, stats, teamColor, false, false, sf)),
@@ -266,42 +267,42 @@ class PlayerCourtCard extends StatelessWidget {
String displayName = name.length > 12 ? "${name.substring(0, 10)}..." : name;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), // 👇 Reduzido padding do cartão
decoration: BoxDecoration(
color: bgColor, borderRadius: BorderRadius.circular(12), border: Border.all(color: borderColor, width: 2),
boxShadow: const [BoxShadow(color: Colors.black26, blurRadius: 6, offset: Offset(0, 3))],
color: bgColor, borderRadius: BorderRadius.circular(8), border: Border.all(color: borderColor, width: 1.5), // 👇 Bordas reduzidas
boxShadow: const [BoxShadow(color: Colors.black26, blurRadius: 4, offset: Offset(0, 2))],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(9 * sf),
borderRadius: BorderRadius.circular(6 * sf),
child: IntrinsicHeight(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 16 * sf),
padding: EdgeInsets.symmetric(horizontal: 10 * sf), // 👇 Padding do número menor
color: isFouledOut ? Colors.grey[700] : teamColor,
alignment: Alignment.center,
child: Text(number, style: TextStyle(color: Colors.white, fontSize: 22 * sf, fontWeight: FontWeight.bold)),
child: Text(number, style: TextStyle(color: Colors.white, fontSize: 18 * sf, fontWeight: FontWeight.bold)), // 👇 Fonte do número menor
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 12 * sf, vertical: 7 * sf),
padding: EdgeInsets.symmetric(horizontal: 8 * sf, vertical: 4 * sf), // 👇 Padding dos stats menor
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
displayName,
style: TextStyle(fontSize: 16 * sf, fontWeight: FontWeight.bold, color: isFouledOut ? AppTheme.actionMiss : Colors.black87, decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none)
style: TextStyle(fontSize: 14 * sf, fontWeight: FontWeight.bold, color: isFouledOut ? AppTheme.actionMiss : Colors.black87, decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none) // 👇 Nome menor
),
SizedBox(height: 2.5 * sf),
SizedBox(height: 1.5 * sf), // 👇 Espaçamento menor
Text(
"${stats["pts"]} Pts | FG: $fgm/$fga ($fgPercent%)",
style: TextStyle(fontSize: 12 * sf, color: isFouledOut ? AppTheme.actionMiss : Colors.grey[700], fontWeight: FontWeight.w600)
style: TextStyle(fontSize: 10 * sf, color: isFouledOut ? AppTheme.actionMiss : Colors.grey[700], fontWeight: FontWeight.w600) // 👇 Stats menores
),
Text(
"${stats["ast"]} Ast | ${stats["orb"]! + stats["drb"]!} Rbs | ${stats["fls"]} Fls",
style: TextStyle(fontSize: 12 * sf, color: isFouledOut ? AppTheme.actionMiss : Colors.grey[500], fontWeight: FontWeight.w600)
style: TextStyle(fontSize: 10 * sf, color: isFouledOut ? AppTheme.actionMiss : Colors.grey[500], fontWeight: FontWeight.w600) // 👇 Stats menores
),
],
),
@@ -315,7 +316,7 @@ class PlayerCourtCard extends StatelessWidget {
}
// ============================================================================
// 4. PAINEL DE BOTÕES DE AÇÃO (PONTOS, RESSALTOS, ETC)
// 4. PAINEL DE BOTÕES DE AÇÃO - TAMANHO REDUZIDO
// ============================================================================
class ActionButtonsPanel extends StatelessWidget {
final PlacarController controller;
@@ -323,28 +324,29 @@ class ActionButtonsPanel extends StatelessWidget {
const ActionButtonsPanel({super.key, required this.controller, required this.sf});
@override
@override
Widget build(BuildContext context) {
final double baseSize = 65 * sf;
final double feedSize = 82 * sf;
final double gap = 7 * sf;
// 👇👇 Ajuste para o "Meio-Termo" ideal 👇👇
final double baseSize = 58 * sf; // Aumentado de 50 para 58
final double feedSize = 73 * sf; // Aumentado de 65 para 73
final double gap = 5 * sf;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
_columnBtn([
_dragAndTargetBtn("M1", AppTheme.actionMiss, "miss_1", baseSize, feedSize, sf), // 🎨 TEMA
_dragAndTargetBtn("1", AppTheme.actionPoints, "add_pts_1", baseSize, feedSize, sf), // 🎨 TEMA
_dragAndTargetBtn("M1", AppTheme.actionMiss, "miss_1", baseSize, feedSize, sf),
_dragAndTargetBtn("1", AppTheme.actionPoints, "add_pts_1", baseSize, feedSize, sf),
_dragAndTargetBtn("1", AppTheme.actionPoints, "sub_pts_1", baseSize, feedSize, sf, isX: true),
_dragAndTargetBtn("STL", AppTheme.actionSteal, "add_stl", baseSize, feedSize, sf), // 🎨 TEMA
_dragAndTargetBtn("STL", AppTheme.actionSteal, "add_stl", baseSize, feedSize, sf),
], gap),
SizedBox(width: gap * 1),
_columnBtn([
_dragAndTargetBtn("M2", AppTheme.actionMiss, "miss_2", baseSize, feedSize, sf),
_dragAndTargetBtn("2", AppTheme.actionPoints, "add_pts_2", baseSize, feedSize, sf),
_dragAndTargetBtn("2", AppTheme.actionPoints, "sub_pts_2", baseSize, feedSize, sf, isX: true),
_dragAndTargetBtn("AST", AppTheme.actionAssist, "add_ast", baseSize, feedSize, sf), // 🎨 TEMA
_dragAndTargetBtn("AST", AppTheme.actionAssist, "add_ast", baseSize, feedSize, sf),
], gap),
SizedBox(width: gap * 1),
_columnBtn([
@@ -355,9 +357,9 @@ class ActionButtonsPanel extends StatelessWidget {
], gap),
SizedBox(width: gap * 1),
_columnBtn([
_dragAndTargetBtn("ORB", AppTheme.actionRebound, "add_orb", baseSize, feedSize, sf, icon: Icons.sports_basketball), // 🎨 TEMA
_dragAndTargetBtn("DRB", AppTheme.actionRebound, "add_drb", baseSize, feedSize, sf, icon: Icons.sports_basketball), // 🎨 TEMA
_dragAndTargetBtn("BLK", AppTheme.actionBlock, "add_blk", baseSize, feedSize, sf, icon: Icons.front_hand), // 🎨 TEMA
_dragAndTargetBtn("ORB", AppTheme.actionRebound, "add_orb", baseSize, feedSize, sf, icon: Icons.sports_basketball),
_dragAndTargetBtn("DRB", AppTheme.actionRebound, "add_drb", baseSize, feedSize, sf, icon: Icons.sports_basketball),
_dragAndTargetBtn("BLK", AppTheme.actionBlock, "add_blk", baseSize, feedSize, sf, icon: Icons.front_hand),
], gap),
],
);
@@ -452,6 +454,9 @@ class ActionButtonsPanel extends StatelessWidget {
}
}
// ============================================================================
// 5. PÁGINA DO PLACAR
// ============================================================================
class PlacarPage extends StatefulWidget {
final String gameId, myTeam, opponentTeam;
@@ -505,20 +510,20 @@ class _PlacarPageState extends State<PlacarPage> {
feedback: Material(
color: Colors.transparent,
child: CircleAvatar(
radius: 30 * sf,
radius: 25 * sf, // 👇 Botão flutuante de falta menor
backgroundColor: color.withOpacity(0.8),
child: Icon(icon, color: Colors.white, size: 30 * sf)
child: Icon(icon, color: Colors.white, size: 25 * sf)
),
),
child: Column(
children: [
CircleAvatar(
radius: 27 * sf,
radius: 22 * sf, // 👇 Botão flutuante menor
backgroundColor: color,
child: Icon(icon, color: Colors.white, size: 28 * sf),
child: Icon(icon, color: Colors.white, size: 22 * sf),
),
SizedBox(height: 5 * sf),
Text(label, style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12 * sf)),
Text(label, style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 10 * sf)), // 👇 Texto menor
],
),
),
@@ -532,16 +537,17 @@ class _PlacarPageState extends State<PlacarPage> {
child: FloatingActionButton(
heroTag: heroTag,
backgroundColor: color,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14 * (size / 50))),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10 * (size / 40))), // 👇 Curvatura ajustada ao novo tamanho
elevation: 5,
onPressed: isLoading ? null : onTap,
child: isLoading
? SizedBox(width: size * 0.45, height: size * 0.45, child: const CircularProgressIndicator(color: Colors.white, strokeWidth: 2.5))
? SizedBox(width: size * 0.45, height: size * 0.45, child: const CircularProgressIndicator(color: Colors.white, strokeWidth: 2.0))
: Icon(icon, color: Colors.white, size: size * 0.55),
),
);
}
void _showHeatmap(BuildContext context) {
void _showHeatmap(BuildContext context) {
showDialog(
context: context,
builder: (ctx) => HeatmapDialog(
@@ -559,12 +565,13 @@ void _showHeatmap(BuildContext context) {
final double wScreen = MediaQuery.of(context).size.width;
final double hScreen = MediaQuery.of(context).size.height;
final double sf = math.min(wScreen / 1150, hScreen / 720);
final double cornerBtnSize = 48 * sf;
// 👇👇 DICA EXTRA APLICADA AQUI: Aumentei o divisor base para que o sf seja menor por defeito
final double sf = math.min(wScreen / 1250, hScreen / 800);
final double cornerBtnSize = 40 * sf; // 👇 Botões dos cantos (Salvar, Mapa, Banco) menores
if (_controller.isLoading) {
return Scaffold(
backgroundColor: AppTheme.placarDarkSurface, // 🎨 TEMA
backgroundColor: AppTheme.placarDarkSurface,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
@@ -582,7 +589,7 @@ void _showHeatmap(BuildContext context) {
"Os jogadores estão a terminar o aquecimento..."
];
String frase = frases[DateTime.now().second % frases.length];
return Text(frase, style: TextStyle(color: AppTheme.actionPoints.withOpacity(0.7), fontSize: 26 * sf, fontStyle: FontStyle.italic)); // 🎨 TEMA
return Text(frase, style: TextStyle(color: AppTheme.actionPoints.withOpacity(0.7), fontSize: 26 * sf, fontStyle: FontStyle.italic));
},
),
],
@@ -592,7 +599,7 @@ void _showHeatmap(BuildContext context) {
}
return Scaffold(
backgroundColor: AppTheme.placarBackground, // 🎨 TEMA (Fundo azul bonito)
backgroundColor: AppTheme.placarBackground,
body: SafeArea(
top: false,
bottom: false,
@@ -600,9 +607,8 @@ void _showHeatmap(BuildContext context) {
ignoring: _controller.isSaving,
child: Stack(
children: [
// --- 1. O CAMPO LIMPO ---
Container(
margin: EdgeInsets.only(left: 65 * sf, right: 65 * sf, bottom: 55 * sf),
margin: EdgeInsets.only(left: 55 * sf, right: 55 * sf, bottom: 45 * sf), // 👇 Margens reduzidas para dar mais espaço ao campo
decoration: BoxDecoration(border: Border.all(color: Colors.white, width: 2.5)),
child: LayoutBuilder(
builder: (context, constraints) {
@@ -642,21 +648,21 @@ void _showHeatmap(BuildContext context) {
],
if (!_controller.isSelectingShotLocation) ...[
_buildFloatingFoulBtn("FALTA +", AppTheme.actionPoints, "add_foul", Icons.sports, w * 0.39, 0.0, h * 0.31, sf), // 🎨 TEMA
_buildFloatingFoulBtn("FALTA -", AppTheme.actionMiss, "sub_foul", Icons.block, 0.0, w * 0.39, h * 0.31, sf), // 🎨 TEMA
_buildFloatingFoulBtn("FALTA +", AppTheme.actionPoints, "add_foul", Icons.sports, w * 0.39, 0.0, h * 0.31, sf),
_buildFloatingFoulBtn("FALTA -", AppTheme.actionMiss, "sub_foul", Icons.block, 0.0, w * 0.39, h * 0.31, sf),
],
if (!_controller.isSelectingShotLocation)
Positioned(
top: (h * 0.32) + (40 * sf),
top: (h * 0.38) + (30 * sf), // 👇 Ajustado posição
left: 0, right: 0,
child: Center(
child: GestureDetector(
onTap: () => _controller.toggleTimer(context),
child: CircleAvatar(
radius: 68 * sf,
radius: 60 * sf, // 👇 Botão de play/pause menor
backgroundColor: Colors.grey.withOpacity(0.5),
child: Icon(_controller.isRunning ? Icons.pause : Icons.play_arrow, color: Colors.white, size: 58 * sf)
child: Icon(_controller.isRunning ? Icons.pause : Icons.play_arrow, color: Colors.white, size: 50 * sf)
),
),
),
@@ -664,16 +670,16 @@ void _showHeatmap(BuildContext context) {
Positioned(top: 0, left: 0, right: 0, child: Center(child: TopScoreboard(controller: _controller, sf: sf))),
if (!_controller.isSelectingShotLocation) Positioned(bottom: -10 * sf, left: 0, right: 0, child: ActionButtonsPanel(controller: _controller, sf: sf)),
if (!_controller.isSelectingShotLocation) Positioned(bottom: -5 * sf, left: 0, right: 0, child: ActionButtonsPanel(controller: _controller, sf: sf)),
if (_controller.isSelectingShotLocation)
Positioned(
top: h * 0.4, left: 0, right: 0,
child: Center(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 35 * sf, vertical: 18 * sf),
decoration: BoxDecoration(color: Colors.black87, borderRadius: BorderRadius.circular(11 * sf), border: Border.all(color: Colors.white, width: 1.5 * sf)),
child: Text("TOQUE NO CAMPO PARA MARCAR O LOCAL DO LANÇAMENTO", style: TextStyle(color: Colors.white, fontSize: 27 * sf, fontWeight: FontWeight.bold)),
padding: EdgeInsets.symmetric(horizontal: 25 * sf, vertical: 12 * sf), // 👇 Aviso menor
decoration: BoxDecoration(color: Colors.black87, borderRadius: BorderRadius.circular(8 * sf), border: Border.all(color: Colors.white, width: 1.5 * sf)),
child: Text("TOQUE NO CAMPO PARA MARCAR O LOCAL DO LANÇAMENTO", style: TextStyle(color: Colors.white, fontSize: 20 * sf, fontWeight: FontWeight.bold)), // 👇 Fonte menor
),
),
),
@@ -683,17 +689,12 @@ void _showHeatmap(BuildContext context) {
),
),
// ==========================================
// BOTÕES LATERAIS DE FORA DO CAMPO
// ==========================================
// Topo Esquerdo: Guardar e Sair
Positioned(
top: 50 * sf, left: 12 * sf,
top: 40 * sf, left: 8 * sf,
child: _buildCornerBtn(
heroTag: 'btn_save_exit',
icon: Icons.save_alt,
color: AppTheme.oppTeamRed, // 🎨 TEMA
color: AppTheme.oppTeamRed,
size: cornerBtnSize,
isLoading: _controller.isSaving,
onTap: () async {
@@ -705,9 +706,8 @@ void _showHeatmap(BuildContext context) {
),
),
// Topo Direito: Mapa de Calor
Positioned(
top: 50 * sf, right: 12 * sf,
top: 40 * sf, right: 8 * sf,
child: _buildCornerBtn(
heroTag: 'btn_heatmap',
icon: Icons.local_fire_department,
@@ -717,46 +717,44 @@ void _showHeatmap(BuildContext context) {
),
),
// Base Esquerda: Banco + TIMEOUT DA CASA
Positioned(
bottom: 55 * sf, left: 12 * sf,
bottom: 45 * sf, left: 8 * sf,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (_controller.showMyBench) BenchPlayersList(controller: _controller, isOpponent: false, sf: sf),
SizedBox(height: 12 * sf),
_buildCornerBtn(heroTag: 'btn_sub_home', icon: Icons.swap_horiz, color: AppTheme.myTeamBlue, size: cornerBtnSize, onTap: () { _controller.showMyBench = !_controller.showMyBench; _controller.onUpdate(); }), // 🎨 TEMA
SizedBox(height: 12 * sf),
SizedBox(height: 8 * sf),
_buildCornerBtn(heroTag: 'btn_sub_home', icon: Icons.swap_horiz, color: AppTheme.myTeamBlue, size: cornerBtnSize, onTap: () { _controller.showMyBench = !_controller.showMyBench; _controller.onUpdate(); }),
SizedBox(height: 8 * sf),
_buildCornerBtn(
heroTag: 'btn_to_home',
icon: Icons.timer,
color: _controller.myTimeoutsUsed >= 3 ? Colors.grey : AppTheme.myTeamBlue, // 🎨 TEMA
color: _controller.myTimeoutsUsed >= 3 ? Colors.grey : AppTheme.myTeamBlue,
size: cornerBtnSize,
onTap: _controller.myTimeoutsUsed >= 3
? () => ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: const Text('🛑 A equipa da casa já usou os 3 Timeouts deste período!'), backgroundColor: AppTheme.actionMiss)) // 🎨 TEMA
? () => ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: const Text('🛑 A equipa da casa já usou os 3 Timeouts deste período!'), backgroundColor: AppTheme.actionMiss))
: () => _controller.useTimeout(false)
),
],
),
),
// Base Direita: Banco + TIMEOUT DO VISITANTE
Positioned(
bottom: 55 * sf, right: 12 * sf,
bottom: 45 * sf, right: 8 * sf,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (_controller.showOppBench) BenchPlayersList(controller: _controller, isOpponent: true, sf: sf),
SizedBox(height: 12 * sf),
_buildCornerBtn(heroTag: 'btn_sub_away', icon: Icons.swap_horiz, color: AppTheme.oppTeamRed, size: cornerBtnSize, onTap: () { _controller.showOppBench = !_controller.showOppBench; _controller.onUpdate(); }), // 🎨 TEMA
SizedBox(height: 12 * sf),
SizedBox(height: 8 * sf),
_buildCornerBtn(heroTag: 'btn_sub_away', icon: Icons.swap_horiz, color: AppTheme.oppTeamRed, size: cornerBtnSize, onTap: () { _controller.showOppBench = !_controller.showOppBench; _controller.onUpdate(); }),
SizedBox(height: 8 * sf),
_buildCornerBtn(
heroTag: 'btn_to_away',
icon: Icons.timer,
color: _controller.opponentTimeoutsUsed >= 3 ? Colors.grey : AppTheme.oppTeamRed, // 🎨 TEMA
color: _controller.opponentTimeoutsUsed >= 3 ? Colors.grey : AppTheme.oppTeamRed,
size: cornerBtnSize,
onTap: _controller.opponentTimeoutsUsed >= 3
? () => ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: const Text('🛑 A equipa visitante já usou os 3 Timeouts deste período!'), backgroundColor: AppTheme.actionMiss)) // 🎨 TEMA
? () => ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: const Text('🛑 A equipa visitante já usou os 3 Timeouts deste período!'), backgroundColor: AppTheme.actionMiss))
: () => _controller.useTimeout(true)
),
],
@@ -778,7 +776,7 @@ void _showHeatmap(BuildContext context) {
}
// ============================================================================
// 👇 O TEU NOVO MAPA DE CALOR AVANÇADO (FILTRO POR EQUIPA E JOGADOR) 👇
// MAPA DE CALOR
// ============================================================================
class HeatmapDialog extends StatefulWidget {
final List<ShotRecord> shots;
@@ -813,21 +811,14 @@ class _HeatmapDialogState extends State<HeatmapDialog> {
final double dialogHeight = screenHeight * 0.95;
final double dialogWidth = dialogHeight * 1.0;
// 1. DEFINIR QUAIS BOLINHAS APARECEM CONFORME OS FILTROS
List<ShotRecord> filteredShots = widget.shots.where((shot) {
// Filtro de Equipa
if (_selectedTeam == widget.myTeamName && !widget.myPlayers.contains(shot.playerName)) return false;
if (_selectedTeam == widget.oppTeamName && !widget.oppPlayers.contains(shot.playerName)) return false;
// Filtro de Jogador
if (_selectedPlayer != 'Todos os Jogadores' && shot.playerName != _selectedPlayer) return false;
return true;
}).toList();
// 2. DEFINIR AS OPÇÕES DOS DROPDOWNS
List<String> teamOptions = ['Ambas as Equipas', widget.myTeamName, widget.oppTeamName];
List<String> playerOptions = ['Todos os Jogadores'];
Set<String> activePlayers = widget.shots.map((s) => s.playerName).toSet();
@@ -839,7 +830,6 @@ class _HeatmapDialogState extends State<HeatmapDialog> {
playerOptions.addAll(activePlayers.where((p) => widget.oppPlayers.contains(p)));
}
// Se a equipa mudar e o jogador antigo não pertencer à equipa nova, reseta para "Todos"
if (!playerOptions.contains(_selectedPlayer)) {
_selectedPlayer = 'Todos os Jogadores';
}
@@ -854,21 +844,15 @@ class _HeatmapDialogState extends State<HeatmapDialog> {
width: dialogWidth,
child: Column(
children: [
// CABEÇALHO COM OS DOIS FILTROS
Container(
height: 50, // Aumentei um pouco para caber bem os dropdowns
height: 50,
color: headerColor,
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
const Text(
"📊 Mapa de Calor",
style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold),
),
const Text("📊 Mapa de Calor", style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)),
const Spacer(),
// 👇 FILTRO DE EQUIPA 👇
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(color: Colors.black12, borderRadius: BorderRadius.circular(6)),
@@ -879,23 +863,15 @@ class _HeatmapDialogState extends State<HeatmapDialog> {
icon: const Icon(Icons.arrow_drop_down, color: Colors.white),
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13),
items: teamOptions.map((String team) {
return DropdownMenuItem<String>(
value: team,
child: Text(team),
);
return DropdownMenuItem<String>(value: team, child: Text(team));
}).toList(),
onChanged: (String? newValue) {
setState(() {
_selectedTeam = newValue!;
});
setState(() { _selectedTeam = newValue!; });
},
),
),
),
const SizedBox(width: 10),
// 👇 FILTRO DE JOGADOR 👇
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(color: Colors.black12, borderRadius: BorderRadius.circular(6)),
@@ -906,22 +882,15 @@ class _HeatmapDialogState extends State<HeatmapDialog> {
icon: const Icon(Icons.arrow_drop_down, color: Colors.white),
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13),
items: playerOptions.map((String player) {
return DropdownMenuItem<String>(
value: player,
child: Text(player),
);
return DropdownMenuItem<String>(value: player, child: Text(player));
}).toList(),
onChanged: (String? newValue) {
setState(() {
_selectedPlayer = newValue!;
});
setState(() { _selectedPlayer = newValue!; });
},
),
),
),
const SizedBox(width: 15),
InkWell(
onTap: () => Navigator.pop(context),
child: Container(
@@ -933,20 +902,15 @@ class _HeatmapDialogState extends State<HeatmapDialog> {
],
),
),
// O CAMPO DESENHADO
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return Stack(
children: [
// O Desenho das Linhas do Campo (Usamos um pintor simples)
CustomPaint(
size: Size(constraints.maxWidth, constraints.maxHeight),
painter: HeatmapCourtPainter(),
),
// As Bolinhas por cima do desenho
...filteredShots.map((shot) => Positioned(
left: (shot.relativeX * constraints.maxWidth) - 8,
top: (shot.relativeY * constraints.maxHeight) - 8,
@@ -968,7 +932,6 @@ class _HeatmapDialogState extends State<HeatmapDialog> {
}
}
// Pintor dedicado apenas a desenhar as linhas (sem lógica de sombras do ZoneMap)
class HeatmapCourtPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {