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:flutter/services.dart';
import 'package:playmaker/controllers/placar_controller.dart'; import 'package:playmaker/controllers/placar_controller.dart';
import 'package:playmaker/utils/size_extension.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 'dart:math' as math;
import 'package:playmaker/zone_map_dialog.dart'; 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 { class TopScoreboard extends StatelessWidget {
final PlacarController controller; final PlacarController controller;
@@ -19,43 +19,44 @@ class TopScoreboard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( 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( decoration: BoxDecoration(
color: AppTheme.placarDarkSurface, // 🎨 USANDO TEMA color: AppTheme.placarDarkSurface,
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(22 * sf), bottomLeft: Radius.circular(22 * sf),
bottomRight: 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( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
_buildTeamSection(controller.myTeam, controller.myScore, controller.myFouls, controller.myTimeoutsUsed, AppTheme.myTeamBlue, false, sf), // 🎨 USANDO TEMA _buildTeamSection(controller.myTeam, controller.myScore, controller.myFouls, controller.myTimeoutsUsed, AppTheme.myTeamBlue, false, sf),
SizedBox(width: 30 * sf), SizedBox(width: 20 * sf), // 👇 Reduzido espaçamento central
Column( Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Container( Container(
padding: EdgeInsets.symmetric(horizontal: 18 * sf, vertical: 5 * sf), padding: EdgeInsets.symmetric(horizontal: 14 * sf, vertical: 4 * sf), // 👇 Reduzido
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppTheme.placarTimerBg, // 🎨 USANDO TEMA color: AppTheme.placarTimerBg,
borderRadius: BorderRadius.circular(9 * sf) borderRadius: BorderRadius.circular(9 * sf)
), ),
child: Text( child: Text(
controller.formatTime(), 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( Text(
"PERÍODO ${controller.currentQuarter}", "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), SizedBox(width: 20 * sf), // 👇 Reduzido espaçamento central
_buildTeamSection(controller.opponentTeam, controller.opponentScore, controller.opponentFouls, controller.opponentTimeoutsUsed, AppTheme.oppTeamRed, true, sf), // 🎨 USANDO TEMA _buildTeamSection(controller.opponentTeam, controller.opponentScore, controller.opponentFouls, controller.opponentTimeoutsUsed, AppTheme.oppTeamRed, true, sf),
], ],
), ),
); );
@@ -67,12 +68,12 @@ class TopScoreboard extends StatelessWidget {
final timeoutIndicators = Row( final timeoutIndicators = Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: List.generate(3, (index) => Container( children: List.generate(3, (index) => Container(
margin: EdgeInsets.symmetric(horizontal: 3.5 * sf), margin: EdgeInsets.symmetric(horizontal: 2.5 * sf), // 👇 Reduzido
width: 12 * sf, height: 12 * sf, width: 10 * sf, height: 10 * sf, // 👇 Bolas de timeout menores
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: index < timeouts ? AppTheme.warningAmber : Colors.grey.shade600, 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( Column(
children: [ children: [
_scoreBox(score, color, sf), _scoreBox(score, color, sf),
SizedBox(height: 7 * sf), SizedBox(height: 5 * sf), // 👇 Reduzido
timeoutIndicators timeoutIndicators
] ]
), ),
SizedBox(width: 18 * sf), SizedBox(width: 12 * sf), // 👇 Reduzido
Column( Column(
crossAxisAlignment: isOpp ? CrossAxisAlignment.start : CrossAxisAlignment.end, crossAxisAlignment: isOpp ? CrossAxisAlignment.start : CrossAxisAlignment.end,
children: [ children: [
Text( Text(
name.toUpperCase(), 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( Text(
"FALTAS: $displayFouls", "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( 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, alignment: Alignment.center,
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(7 * sf)), decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(6 * sf)),
child: Text(score.toString(), style: TextStyle(color: Colors.white, fontSize: 26 * sf, fontWeight: FontWeight.w900)), 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 { class BenchPlayersList extends StatelessWidget {
final PlacarController controller; final PlacarController controller;
@@ -126,7 +127,7 @@ class BenchPlayersList extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bench = isOpponent ? controller.oppBench : controller.myBench; 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_"; final prefix = isOpponent ? "bench_opp_" : "bench_my_";
return Column( return Column(
@@ -137,20 +138,20 @@ class BenchPlayersList extends StatelessWidget {
final bool isFouledOut = fouls >= 5; final bool isFouledOut = fouls >= 5;
Widget avatarUI = Container( Widget avatarUI = Container(
margin: EdgeInsets.only(bottom: 7 * sf), margin: EdgeInsets.only(bottom: 5 * sf), // 👇 Reduzido
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 1.8 * sf), border: Border.all(color: Colors.white, width: 1.5 * sf), // 👇 Reduzido
boxShadow: [BoxShadow(color: Colors.black45, blurRadius: 5 * sf, offset: Offset(0, 2.5 * sf))] boxShadow: [BoxShadow(color: Colors.black45, blurRadius: 4 * sf, offset: Offset(0, 2.0 * sf))]
), ),
child: CircleAvatar( child: CircleAvatar(
radius: 22 * sf, radius: 18 * sf, // 👇 Avatar do banco menor
backgroundColor: isFouledOut ? Colors.grey.shade800 : teamColor, backgroundColor: isFouledOut ? Colors.grey.shade800 : teamColor,
child: Text( child: Text(
num, num,
style: TextStyle( style: TextStyle(
color: isFouledOut ? Colors.red.shade300 : Colors.white, color: isFouledOut ? Colors.red.shade300 : Colors.white,
fontSize: 16 * sf, fontSize: 14 * sf, // 👇 Fonte reduzida
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none
) )
@@ -170,12 +171,12 @@ class BenchPlayersList extends StatelessWidget {
feedback: Material( feedback: Material(
color: Colors.transparent, color: Colors.transparent,
child: CircleAvatar( child: CircleAvatar(
radius: 28 * sf, radius: 22 * sf, // 👇 Avatar ao arrastar menor
backgroundColor: teamColor, 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, child: avatarUI,
); );
}).toList(), }).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 { class PlayerCourtCard extends StatelessWidget {
final PlacarController controller; final PlacarController controller;
@@ -196,7 +197,7 @@ class PlayerCourtCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { 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 stats = controller.playerStats[name]!;
final number = controller.playerNumbers[name]!; final number = controller.playerNumbers[name]!;
final prefix = isOpponent ? "player_opp_" : "player_my_"; final prefix = isOpponent ? "player_opp_" : "player_my_";
@@ -206,9 +207,9 @@ class PlayerCourtCard extends StatelessWidget {
feedback: Material( feedback: Material(
color: Colors.transparent, color: Colors.transparent,
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), // 👇 Reduzido
decoration: BoxDecoration(color: teamColor.withOpacity(0.9), borderRadius: BorderRadius.circular(8)), decoration: BoxDecoration(color: teamColor.withOpacity(0.9), borderRadius: BorderRadius.circular(6)),
child: Text(name, style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)), 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)), 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; String displayName = name.length > 12 ? "${name.substring(0, 10)}..." : name;
return Container( 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( decoration: BoxDecoration(
color: bgColor, borderRadius: BorderRadius.circular(12), border: Border.all(color: borderColor, width: 2), color: bgColor, borderRadius: BorderRadius.circular(8), border: Border.all(color: borderColor, width: 1.5), // 👇 Bordas reduzidas
boxShadow: const [BoxShadow(color: Colors.black26, blurRadius: 6, offset: Offset(0, 3))], boxShadow: const [BoxShadow(color: Colors.black26, blurRadius: 4, offset: Offset(0, 2))],
), ),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(9 * sf), borderRadius: BorderRadius.circular(6 * sf),
child: IntrinsicHeight( child: IntrinsicHeight(
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Container( Container(
padding: EdgeInsets.symmetric(horizontal: 16 * sf), padding: EdgeInsets.symmetric(horizontal: 10 * sf), // 👇 Padding do número menor
color: isFouledOut ? Colors.grey[700] : teamColor, color: isFouledOut ? Colors.grey[700] : teamColor,
alignment: Alignment.center, 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(
padding: EdgeInsets.symmetric(horizontal: 12 * sf, vertical: 7 * sf), padding: EdgeInsets.symmetric(horizontal: 8 * sf, vertical: 4 * sf), // 👇 Padding dos stats menor
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
displayName, 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( Text(
"${stats["pts"]} Pts | FG: $fgm/$fga ($fgPercent%)", "${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( Text(
"${stats["ast"]} Ast | ${stats["orb"]! + stats["drb"]!} Rbs | ${stats["fls"]} Fls", "${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 { class ActionButtonsPanel extends StatelessWidget {
final PlacarController controller; final PlacarController controller;
@@ -325,26 +326,27 @@ class ActionButtonsPanel extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final double baseSize = 65 * sf; // 👇👇 Ajuste para o "Meio-Termo" ideal 👇👇
final double feedSize = 82 * sf; final double baseSize = 58 * sf; // Aumentado de 50 para 58
final double gap = 7 * sf; final double feedSize = 73 * sf; // Aumentado de 65 para 73
final double gap = 5 * sf;
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
_columnBtn([ _columnBtn([
_dragAndTargetBtn("M1", AppTheme.actionMiss, "miss_1", baseSize, feedSize, sf), // 🎨 TEMA _dragAndTargetBtn("M1", AppTheme.actionMiss, "miss_1", baseSize, feedSize, sf),
_dragAndTargetBtn("1", AppTheme.actionPoints, "add_pts_1", baseSize, feedSize, sf), // 🎨 TEMA _dragAndTargetBtn("1", AppTheme.actionPoints, "add_pts_1", baseSize, feedSize, sf),
_dragAndTargetBtn("1", AppTheme.actionPoints, "sub_pts_1", baseSize, feedSize, sf, isX: true), _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), ], gap),
SizedBox(width: gap * 1), SizedBox(width: gap * 1),
_columnBtn([ _columnBtn([
_dragAndTargetBtn("M2", AppTheme.actionMiss, "miss_2", baseSize, feedSize, sf), _dragAndTargetBtn("M2", AppTheme.actionMiss, "miss_2", baseSize, feedSize, sf),
_dragAndTargetBtn("2", AppTheme.actionPoints, "add_pts_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("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), ], gap),
SizedBox(width: gap * 1), SizedBox(width: gap * 1),
_columnBtn([ _columnBtn([
@@ -355,9 +357,9 @@ class ActionButtonsPanel extends StatelessWidget {
], gap), ], gap),
SizedBox(width: gap * 1), SizedBox(width: gap * 1),
_columnBtn([ _columnBtn([
_dragAndTargetBtn("ORB", AppTheme.actionRebound, "add_orb", baseSize, feedSize, sf, icon: Icons.sports_basketball), // 🎨 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), // 🎨 TEMA _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), // 🎨 TEMA _dragAndTargetBtn("BLK", AppTheme.actionBlock, "add_blk", baseSize, feedSize, sf, icon: Icons.front_hand),
], gap), ], gap),
], ],
); );
@@ -452,6 +454,9 @@ class ActionButtonsPanel extends StatelessWidget {
} }
} }
// ============================================================================
// 5. PÁGINA DO PLACAR
// ============================================================================
class PlacarPage extends StatefulWidget { class PlacarPage extends StatefulWidget {
final String gameId, myTeam, opponentTeam; final String gameId, myTeam, opponentTeam;
@@ -505,20 +510,20 @@ class _PlacarPageState extends State<PlacarPage> {
feedback: Material( feedback: Material(
color: Colors.transparent, color: Colors.transparent,
child: CircleAvatar( child: CircleAvatar(
radius: 30 * sf, radius: 25 * sf, // 👇 Botão flutuante de falta menor
backgroundColor: color.withOpacity(0.8), 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( child: Column(
children: [ children: [
CircleAvatar( CircleAvatar(
radius: 27 * sf, radius: 22 * sf, // 👇 Botão flutuante menor
backgroundColor: color, backgroundColor: color,
child: Icon(icon, color: Colors.white, size: 28 * sf), child: Icon(icon, color: Colors.white, size: 22 * sf),
), ),
SizedBox(height: 5 * 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,15 +537,16 @@ class _PlacarPageState extends State<PlacarPage> {
child: FloatingActionButton( child: FloatingActionButton(
heroTag: heroTag, heroTag: heroTag,
backgroundColor: color, 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, elevation: 5,
onPressed: isLoading ? null : onTap, onPressed: isLoading ? null : onTap,
child: isLoading 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), : Icon(icon, color: Colors.white, size: size * 0.55),
), ),
); );
} }
void _showHeatmap(BuildContext context) { void _showHeatmap(BuildContext context) {
showDialog( showDialog(
context: context, context: context,
@@ -559,12 +565,13 @@ void _showHeatmap(BuildContext context) {
final double wScreen = MediaQuery.of(context).size.width; final double wScreen = MediaQuery.of(context).size.width;
final double hScreen = MediaQuery.of(context).size.height; final double hScreen = MediaQuery.of(context).size.height;
final double sf = math.min(wScreen / 1150, hScreen / 720); // 👇👇 DICA EXTRA APLICADA AQUI: Aumentei o divisor base para que o sf seja menor por defeito
final double cornerBtnSize = 48 * sf; 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) { if (_controller.isLoading) {
return Scaffold( return Scaffold(
backgroundColor: AppTheme.placarDarkSurface, // 🎨 TEMA backgroundColor: AppTheme.placarDarkSurface,
body: Center( body: Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -582,7 +589,7 @@ void _showHeatmap(BuildContext context) {
"Os jogadores estão a terminar o aquecimento..." "Os jogadores estão a terminar o aquecimento..."
]; ];
String frase = frases[DateTime.now().second % frases.length]; 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( return Scaffold(
backgroundColor: AppTheme.placarBackground, // 🎨 TEMA (Fundo azul bonito) backgroundColor: AppTheme.placarBackground,
body: SafeArea( body: SafeArea(
top: false, top: false,
bottom: false, bottom: false,
@@ -600,9 +607,8 @@ void _showHeatmap(BuildContext context) {
ignoring: _controller.isSaving, ignoring: _controller.isSaving,
child: Stack( child: Stack(
children: [ children: [
// --- 1. O CAMPO LIMPO ---
Container( 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)), decoration: BoxDecoration(border: Border.all(color: Colors.white, width: 2.5)),
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
@@ -642,21 +648,21 @@ void _showHeatmap(BuildContext context) {
], ],
if (!_controller.isSelectingShotLocation) ...[ if (!_controller.isSelectingShotLocation) ...[
_buildFloatingFoulBtn("FALTA +", AppTheme.actionPoints, "add_foul", Icons.sports, w * 0.39, 0.0, 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), // 🎨 TEMA _buildFloatingFoulBtn("FALTA -", AppTheme.actionMiss, "sub_foul", Icons.block, 0.0, w * 0.39, h * 0.31, sf),
], ],
if (!_controller.isSelectingShotLocation) if (!_controller.isSelectingShotLocation)
Positioned( Positioned(
top: (h * 0.32) + (40 * sf), top: (h * 0.38) + (30 * sf), // 👇 Ajustado posição
left: 0, right: 0, left: 0, right: 0,
child: Center( child: Center(
child: GestureDetector( child: GestureDetector(
onTap: () => _controller.toggleTimer(context), onTap: () => _controller.toggleTimer(context),
child: CircleAvatar( child: CircleAvatar(
radius: 68 * sf, radius: 60 * sf, // 👇 Botão de play/pause menor
backgroundColor: Colors.grey.withOpacity(0.5), 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))), 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) if (_controller.isSelectingShotLocation)
Positioned( Positioned(
top: h * 0.4, left: 0, right: 0, top: h * 0.4, left: 0, right: 0,
child: Center( child: Center(
child: Container( child: Container(
padding: EdgeInsets.symmetric(horizontal: 35 * sf, vertical: 18 * sf), padding: EdgeInsets.symmetric(horizontal: 25 * sf, vertical: 12 * sf), // 👇 Aviso menor
decoration: BoxDecoration(color: Colors.black87, borderRadius: BorderRadius.circular(11 * sf), border: Border.all(color: Colors.white, width: 1.5 * sf)), 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: 27 * sf, fontWeight: FontWeight.bold)), 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( Positioned(
top: 50 * sf, left: 12 * sf, top: 40 * sf, left: 8 * sf,
child: _buildCornerBtn( child: _buildCornerBtn(
heroTag: 'btn_save_exit', heroTag: 'btn_save_exit',
icon: Icons.save_alt, icon: Icons.save_alt,
color: AppTheme.oppTeamRed, // 🎨 TEMA color: AppTheme.oppTeamRed,
size: cornerBtnSize, size: cornerBtnSize,
isLoading: _controller.isSaving, isLoading: _controller.isSaving,
onTap: () async { onTap: () async {
@@ -705,9 +706,8 @@ void _showHeatmap(BuildContext context) {
), ),
), ),
// Topo Direito: Mapa de Calor
Positioned( Positioned(
top: 50 * sf, right: 12 * sf, top: 40 * sf, right: 8 * sf,
child: _buildCornerBtn( child: _buildCornerBtn(
heroTag: 'btn_heatmap', heroTag: 'btn_heatmap',
icon: Icons.local_fire_department, icon: Icons.local_fire_department,
@@ -717,46 +717,44 @@ void _showHeatmap(BuildContext context) {
), ),
), ),
// Base Esquerda: Banco + TIMEOUT DA CASA
Positioned( Positioned(
bottom: 55 * sf, left: 12 * sf, bottom: 45 * sf, left: 8 * sf,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (_controller.showMyBench) BenchPlayersList(controller: _controller, isOpponent: false, sf: sf), if (_controller.showMyBench) BenchPlayersList(controller: _controller, isOpponent: false, sf: sf),
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(); }), // 🎨 TEMA _buildCornerBtn(heroTag: 'btn_sub_home', icon: Icons.swap_horiz, color: AppTheme.myTeamBlue, size: cornerBtnSize, onTap: () { _controller.showMyBench = !_controller.showMyBench; _controller.onUpdate(); }),
SizedBox(height: 12 * sf), SizedBox(height: 8 * sf),
_buildCornerBtn( _buildCornerBtn(
heroTag: 'btn_to_home', heroTag: 'btn_to_home',
icon: Icons.timer, icon: Icons.timer,
color: _controller.myTimeoutsUsed >= 3 ? Colors.grey : AppTheme.myTeamBlue, // 🎨 TEMA color: _controller.myTimeoutsUsed >= 3 ? Colors.grey : AppTheme.myTeamBlue,
size: cornerBtnSize, size: cornerBtnSize,
onTap: _controller.myTimeoutsUsed >= 3 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) : () => _controller.useTimeout(false)
), ),
], ],
), ),
), ),
// Base Direita: Banco + TIMEOUT DO VISITANTE
Positioned( Positioned(
bottom: 55 * sf, right: 12 * sf, bottom: 45 * sf, right: 8 * sf,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (_controller.showOppBench) BenchPlayersList(controller: _controller, isOpponent: true, sf: sf), if (_controller.showOppBench) BenchPlayersList(controller: _controller, isOpponent: true, sf: sf),
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(); }), // 🎨 TEMA _buildCornerBtn(heroTag: 'btn_sub_away', icon: Icons.swap_horiz, color: AppTheme.oppTeamRed, size: cornerBtnSize, onTap: () { _controller.showOppBench = !_controller.showOppBench; _controller.onUpdate(); }),
SizedBox(height: 12 * sf), SizedBox(height: 8 * sf),
_buildCornerBtn( _buildCornerBtn(
heroTag: 'btn_to_away', heroTag: 'btn_to_away',
icon: Icons.timer, icon: Icons.timer,
color: _controller.opponentTimeoutsUsed >= 3 ? Colors.grey : AppTheme.oppTeamRed, // 🎨 TEMA color: _controller.opponentTimeoutsUsed >= 3 ? Colors.grey : AppTheme.oppTeamRed,
size: cornerBtnSize, size: cornerBtnSize,
onTap: _controller.opponentTimeoutsUsed >= 3 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) : () => _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 { class HeatmapDialog extends StatefulWidget {
final List<ShotRecord> shots; final List<ShotRecord> shots;
@@ -813,21 +811,14 @@ class _HeatmapDialogState extends State<HeatmapDialog> {
final double dialogHeight = screenHeight * 0.95; final double dialogHeight = screenHeight * 0.95;
final double dialogWidth = dialogHeight * 1.0; final double dialogWidth = dialogHeight * 1.0;
// 1. DEFINIR QUAIS BOLINHAS APARECEM CONFORME OS FILTROS
List<ShotRecord> filteredShots = widget.shots.where((shot) { List<ShotRecord> filteredShots = widget.shots.where((shot) {
// Filtro de Equipa
if (_selectedTeam == widget.myTeamName && !widget.myPlayers.contains(shot.playerName)) return false; if (_selectedTeam == widget.myTeamName && !widget.myPlayers.contains(shot.playerName)) return false;
if (_selectedTeam == widget.oppTeamName && !widget.oppPlayers.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; if (_selectedPlayer != 'Todos os Jogadores' && shot.playerName != _selectedPlayer) return false;
return true; return true;
}).toList(); }).toList();
// 2. DEFINIR AS OPÇÕES DOS DROPDOWNS
List<String> teamOptions = ['Ambas as Equipas', widget.myTeamName, widget.oppTeamName]; List<String> teamOptions = ['Ambas as Equipas', widget.myTeamName, widget.oppTeamName];
List<String> playerOptions = ['Todos os Jogadores']; List<String> playerOptions = ['Todos os Jogadores'];
Set<String> activePlayers = widget.shots.map((s) => s.playerName).toSet(); 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))); 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)) { if (!playerOptions.contains(_selectedPlayer)) {
_selectedPlayer = 'Todos os Jogadores'; _selectedPlayer = 'Todos os Jogadores';
} }
@@ -854,21 +844,15 @@ class _HeatmapDialogState extends State<HeatmapDialog> {
width: dialogWidth, width: dialogWidth,
child: Column( child: Column(
children: [ children: [
// CABEÇALHO COM OS DOIS FILTROS
Container( Container(
height: 50, // Aumentei um pouco para caber bem os dropdowns height: 50,
color: headerColor, color: headerColor,
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row( child: Row(
children: [ children: [
const Text( const Text("📊 Mapa de Calor", style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)),
"📊 Mapa de Calor",
style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold),
),
const Spacer(), const Spacer(),
// 👇 FILTRO DE EQUIPA 👇
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(color: Colors.black12, borderRadius: BorderRadius.circular(6)), 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), icon: const Icon(Icons.arrow_drop_down, color: Colors.white),
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13), style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13),
items: teamOptions.map((String team) { items: teamOptions.map((String team) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(value: team, child: Text(team));
value: team,
child: Text(team),
);
}).toList(), }).toList(),
onChanged: (String? newValue) { onChanged: (String? newValue) {
setState(() { setState(() { _selectedTeam = newValue!; });
_selectedTeam = newValue!;
});
}, },
), ),
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
// 👇 FILTRO DE JOGADOR 👇
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(color: Colors.black12, borderRadius: BorderRadius.circular(6)), 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), icon: const Icon(Icons.arrow_drop_down, color: Colors.white),
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13), style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13),
items: playerOptions.map((String player) { items: playerOptions.map((String player) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(value: player, child: Text(player));
value: player,
child: Text(player),
);
}).toList(), }).toList(),
onChanged: (String? newValue) { onChanged: (String? newValue) {
setState(() { setState(() { _selectedPlayer = newValue!; });
_selectedPlayer = newValue!;
});
}, },
), ),
), ),
), ),
const SizedBox(width: 15), const SizedBox(width: 15),
InkWell( InkWell(
onTap: () => Navigator.pop(context), onTap: () => Navigator.pop(context),
child: Container( child: Container(
@@ -933,20 +902,15 @@ class _HeatmapDialogState extends State<HeatmapDialog> {
], ],
), ),
), ),
// O CAMPO DESENHADO
Expanded( Expanded(
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
return Stack( return Stack(
children: [ children: [
// O Desenho das Linhas do Campo (Usamos um pintor simples)
CustomPaint( CustomPaint(
size: Size(constraints.maxWidth, constraints.maxHeight), size: Size(constraints.maxWidth, constraints.maxHeight),
painter: HeatmapCourtPainter(), painter: HeatmapCourtPainter(),
), ),
// As Bolinhas por cima do desenho
...filteredShots.map((shot) => Positioned( ...filteredShots.map((shot) => Positioned(
left: (shot.relativeX * constraints.maxWidth) - 8, left: (shot.relativeX * constraints.maxWidth) - 8,
top: (shot.relativeY * constraints.maxHeight) - 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 { class HeatmapCourtPainter extends CustomPainter {
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {