Files
PlayMaker/lib/pages/PlacarPage.dart

875 lines
37 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// NOVA CLASSE PARA GUARDAR OS LANÇAMENTOS NO CAMPO
class ShotRecord {
final Offset position;
final bool isMake;
ShotRecord(this.position, this.isMake);
}
class PlacarPage extends StatefulWidget {
final String gameId, myTeam, opponentTeam;
const PlacarPage({super.key, required this.gameId, required this.myTeam, required this.opponentTeam});
@override
State<PlacarPage> createState() => _PlacarPageState();
}
class _PlacarPageState extends State<PlacarPage> {
int _myScore = 0;
int _opponentScore = 0;
int _myFouls = 0;
int _opponentFouls = 0;
int _currentQuarter = 1;
int _myTimeoutsUsed = 0;
int _opponentTimeoutsUsed = 0;
List<String> _myCourt = ["Russell", "Reaves", "Davis", "James", "Hachimura"];
List<String> _myBench = ["Reddish", "Wood", "Hayes", "Prince", "Christie"];
List<String> _oppCourt = ["Kyle", "Serge", "Kawhi", "Danny", "Fred"];
List<String> _oppBench = ["Gasol", "Ibaka", "Siakam", "Lowry", "Powell"];
bool _showMyBench = false;
bool _showOppBench = false;
// --- VARIÁVEIS PARA O MAPA DE LANÇAMENTOS ---
bool _isSelectingShotLocation = false;
String? _pendingAction;
String? _pendingPlayer;
List<ShotRecord> _matchShots = []; // Guarda as marcas na quadra
final Map<String, String> _playerNumbers = {
"Russell": "1", "Reaves": "15", "Davis": "3", "James": "6", "Hachimura": "28",
"Reddish": "5", "Wood": "35", "Hayes": "11", "Prince": "12", "Christie": "10",
"Kyle": "7", "Serge": "9", "Kawhi": "2", "Danny": "14", "Fred": "23",
"Gasol": "33", "Ibaka": "25", "Siakam": "43", "Lowry": "7", "Powell": "24",
};
final Map<String, Map<String, int>> _playerStats = {
"Russell": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Reaves": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Davis": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"James": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Hachimura": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Reddish": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Wood": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Hayes": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Prince": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Christie": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Kyle": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Serge": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Kawhi": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Danny": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Fred": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Gasol": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Ibaka": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Siakam": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Lowry": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
"Powell": {"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "fls": 0, "fgm": 0, "fga": 0},
};
Duration _duration = const Duration(minutes: 10);
Timer? _timer;
bool _isRunning = false;
@override
void initState() {
super.initState();
SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeRight, DeviceOrientation.landscapeLeft]);
}
void _toggleTimer() {
if (_isRunning) {
_timer?.cancel();
} else {
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
if (_duration.inSeconds > 0) {
_duration -= const Duration(seconds: 1);
} else {
_timer?.cancel();
_isRunning = false;
if (_currentQuarter < 4) {
_currentQuarter++;
_duration = const Duration(minutes: 10);
_myFouls = 0;
_opponentFouls = 0;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Período $_currentQuarter iniciado. Faltas de equipa resetadas!'), backgroundColor: Colors.blue),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('FIM DO JOGO!'), backgroundColor: Colors.red),
);
}
}
});
});
}
setState(() => _isRunning = !_isRunning);
}
void _useTimeout(bool isOpponent) {
setState(() {
if (isOpponent) {
if (_opponentTimeoutsUsed < 3) _opponentTimeoutsUsed++;
} else {
if (_myTimeoutsUsed < 3) _myTimeoutsUsed++;
}
_isRunning = false;
_timer?.cancel();
});
}
String _formatTime(Duration d) =>
"${d.inMinutes.toString().padLeft(2, '0')}:${d.inSeconds.remainder(60).toString().padLeft(2, '0')}";
@override
void dispose() {
_timer?.cancel();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
super.dispose();
}
// --- 1. INTERCETAR A AÇÃO PARA VER SE PRECISA DE LOCALIZAÇÃO ---
void _handleActionDrag(String action, String playerData) {
bool isOpponent = playerData.startsWith("player_opp_");
String name = playerData.replaceAll("player_my_", "").replaceAll("player_opp_", "");
final stats = _playerStats[name]!;
if (stats["fls"]! >= 5 && action != "sub_foul") {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('🛑 $name atingiu 5 faltas e está expulso!'), backgroundColor: Colors.red),
);
return;
}
// Se for 2 Pts, 3 Pts, Miss 2 ou Miss 3 -> Abre o mapa para marcar local!
if (action == "add_pts_2" || action == "add_pts_3" || action == "miss_2" || action == "miss_3") {
setState(() {
_pendingAction = action;
_pendingPlayer = playerData;
_isSelectingShotLocation = true; // Oculta a UI e pede o clique na tela
});
} else {
// Outras estatísticas (Lances Livres, Ressaltos, Faltas) aplicam direto
_commitStat(action, playerData);
}
}
// --- 2. SALVAR A POSIÇÃO DO CLIQUE NA QUADRA ---
void _registerShotLocation(Offset position) {
setState(() {
// Guarda a bolinha no mapa (Verde para pts, Vermelha para miss)
bool isMake = _pendingAction!.startsWith("add_pts_");
_matchShots.add(ShotRecord(position, isMake));
// Aplica a estatística de facto
_commitStat(_pendingAction!, _pendingPlayer!);
// Restaura a tela
_isSelectingShotLocation = false;
_pendingAction = null;
_pendingPlayer = null;
});
}
// CANCELAR A ESCOLHA DE LOCALIZAÇÃO (Caso o user se arrependa)
void _cancelShotLocation() {
setState(() {
_isSelectingShotLocation = false;
_pendingAction = null;
_pendingPlayer = null;
});
}
// --- 3. APLICAR A ESTATÍSTICA FINAL ---
void _commitStat(String action, String playerData) {
bool isOpponent = playerData.startsWith("player_opp_");
String name = playerData.replaceAll("player_my_", "").replaceAll("player_opp_", "");
final stats = _playerStats[name]!;
setState(() {
if (action.startsWith("add_pts_")) {
int pts = int.parse(action.split("_").last);
if (isOpponent) _opponentScore += pts; else _myScore += pts;
stats["pts"] = stats["pts"]! + pts;
if (pts == 2 || pts == 3) {
stats["fgm"] = stats["fgm"]! + 1;
stats["fga"] = stats["fga"]! + 1;
}
}
else if (action.startsWith("sub_pts_")) {
int pts = int.parse(action.split("_").last);
if (isOpponent) { _opponentScore = (_opponentScore - pts < 0) ? 0 : _opponentScore - pts; }
else { _myScore = (_myScore - pts < 0) ? 0 : _myScore - pts; }
stats["pts"] = (stats["pts"]! - pts < 0) ? 0 : stats["pts"]! - pts;
if (pts == 2 || pts == 3) {
if (stats["fgm"]! > 0) stats["fgm"] = stats["fgm"]! - 1;
if (stats["fga"]! > 0) stats["fga"] = stats["fga"]! - 1;
}
}
else if (action == "miss_2" || action == "miss_3") {
stats["fga"] = stats["fga"]! + 1;
}
else if (action == "add_rbs") { stats["rbs"] = stats["rbs"]! + 1; }
else if (action == "add_ast") { stats["ast"] = stats["ast"]! + 1; }
else if (action == "add_stl") { stats["stl"] = stats["stl"]! + 1; }
else if (action == "add_tov") { stats["tov"] = stats["tov"]! + 1; }
else if (action == "add_blk") { stats["blk"] = stats["blk"]! + 1; }
else if (action == "add_foul") {
stats["fls"] = stats["fls"]! + 1;
if (isOpponent) { _opponentFouls++; } else { _myFouls++; }
}
else if (action == "sub_foul") {
if (stats["fls"]! > 0) stats["fls"] = stats["fls"]! - 1;
if (isOpponent) { if (_opponentFouls > 0) _opponentFouls--; } else { if (_myFouls > 0) _myFouls--; }
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF266174),
body: Stack(
children: [
Container(
margin: const EdgeInsets.only(left: 60, right: 60, bottom: 50),
decoration: BoxDecoration(border: Border.all(color: Colors.white, width: 2.0)),
child: LayoutBuilder(
builder: (context, constraints) {
final innerWidth = constraints.maxWidth;
final innerHeight = constraints.maxHeight;
return Stack(
children: [
// GESTURE DETECTOR ABRANGE TODA A QUADRA PARA RECEBER O CLIQUE
GestureDetector(
onTapDown: (details) {
if (_isSelectingShotLocation) {
_registerShotLocation(details.localPosition);
}
},
child: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/campo.png'),
fit: BoxFit.cover,
alignment: Alignment(0.0, 0.2)
),
),
// DESENHA AS BOLINHAS DE LANÇAMENTO NA QUADRA
child: Stack(
children: _matchShots.map((shot) => Positioned(
left: shot.position.dx - 8, // Centraliza a bolinha
top: shot.position.dy - 8,
child: CircleAvatar(
radius: 8,
backgroundColor: shot.isMake ? Colors.green : Colors.red,
child: Icon(shot.isMake ? Icons.check : Icons.close, size: 10, color: Colors.white),
),
)).toList(),
),
),
),
// --- MODO NORMAL DE JOGO ---
if (!_isSelectingShotLocation) ..._buildTacticalFormation(innerWidth, innerHeight),
if (!_isSelectingShotLocation)
Positioned(
top: innerHeight * 0.26, left: innerWidth * 0.40,
child: _dragAndTargetBtn("F", Colors.orange, "add_foul", icon: Icons.sports),
),
if (!_isSelectingShotLocation)
Positioned(
top: innerHeight * 0.26, right: innerWidth * 0.40,
child: _dragAndTargetBtn("F", Colors.orange, "sub_foul", icon: Icons.block),
),
if (!_isSelectingShotLocation)
Positioned(
top: (innerHeight * 0.30) + 70, left: 0, right: 0,
child: Center(
child: GestureDetector(
onTap: _toggleTimer,
child: CircleAvatar(
radius: 60,
backgroundColor: Colors.grey.withOpacity(0.5),
child: Icon(_isRunning ? Icons.pause : Icons.play_arrow, color: Colors.white, size: 50),
),
),
),
),
Positioned(top: 0, left: 0, right: 0, child: Center(child: _buildTopScoreboard())),
if (!_isSelectingShotLocation)
Positioned(bottom: 10, left: 0, right: 0, child: _buildActionButtonsPanel()),
// --- MODO SELEÇÃO DE LOCALIZAÇÃO DO LANÇAMENTO ---
if (_isSelectingShotLocation)
Positioned(
top: innerHeight * 0.4, left: 0, right: 0,
child: Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.white)
),
child: const Text(
"TOQUE NO CAMPO PARA MARCAR O LOCAL DO LANÇAMENTO",
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
),
),
),
),
if (_isSelectingShotLocation)
Positioned(
bottom: 20, right: 20,
child: FloatingActionButton.extended(
onPressed: _cancelShotLocation,
backgroundColor: Colors.red,
icon: const Icon(Icons.cancel, color: Colors.white),
label: const Text("Cancelar", style: TextStyle(color: Colors.white)),
),
)
],
);
},
),
),
// BOTÕES LATERAIS E BANCO OCULTOS DURANTE A SELEÇÃO DA QUADRA
if (!_isSelectingShotLocation)
Positioned(
top: 20, left: 10,
child: FloatingActionButton(
heroTag: 'btn_save', backgroundColor: const Color(0xFF16202C), mini: true,
onPressed: () => ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Jogo Guardado!'))),
child: const Icon(Icons.save, color: Colors.white),
),
),
if (!_isSelectingShotLocation)
Positioned(
top: 70, left: 10,
child: FloatingActionButton(
heroTag: 'btn_exit', backgroundColor: const Color(0xFFD92C2C), mini: true,
onPressed: () => Navigator.pop(context),
child: const Icon(Icons.exit_to_app, color: Colors.white),
),
),
if (!_isSelectingShotLocation)
Positioned(
bottom: 50, left: 10,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (_showMyBench) ..._buildBenchPlayers(_myBench, false),
const SizedBox(height: 10),
FloatingActionButton(
heroTag: 'btn_sub_home', backgroundColor: const Color(0xFF1E5BB2), mini: true,
onPressed: () => setState(() => _showMyBench = !_showMyBench),
child: const Icon(Icons.swap_horiz, color: Colors.white),
),
],
),
),
if (!_isSelectingShotLocation)
Positioned(
bottom: 50, right: 10,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (_showOppBench) ..._buildBenchPlayers(_oppBench, true),
const SizedBox(height: 10),
FloatingActionButton(
heroTag: 'btn_sub_away', backgroundColor: const Color(0xFFD92C2C), mini: true,
onPressed: () => setState(() => _showOppBench = !_showOppBench),
child: const Icon(Icons.swap_horiz, color: Colors.white),
),
],
),
),
],
),
);
}
List<Widget> _buildTacticalFormation(double w, double h) {
return [
Positioned(top: h * 0.25, left: w * 0.02, child: _buildPlayerCard(_playerNumbers[_myCourt[0]]!, _myCourt[0], false)),
Positioned(top: h * 0.68, left: w * 0.02, child: _buildPlayerCard(_playerNumbers[_myCourt[1]]!, _myCourt[1], false)),
Positioned(top: h * 0.45, left: w * 0.25, child: _buildPlayerCard(_playerNumbers[_myCourt[2]]!, _myCourt[2], false)),
Positioned(top: h * 0.15, left: w * 0.20, child: _buildPlayerCard(_playerNumbers[_myCourt[3]]!, _myCourt[3], false)),
Positioned(top: h * 0.80, left: w * 0.20, child: _buildPlayerCard(_playerNumbers[_myCourt[4]]!, _myCourt[4], false)),
Positioned(top: h * 0.25, right: w * 0.02, child: _buildPlayerCard(_playerNumbers[_oppCourt[0]]!, _oppCourt[0], true)),
Positioned(top: h * 0.68, right: w * 0.02, child: _buildPlayerCard(_playerNumbers[_oppCourt[1]]!, _oppCourt[1], true)),
Positioned(top: h * 0.45, right: w * 0.25, child: _buildPlayerCard(_playerNumbers[_oppCourt[2]]!, _oppCourt[2], true)),
Positioned(top: h * 0.15, right: w * 0.20, child: _buildPlayerCard(_playerNumbers[_oppCourt[3]]!, _oppCourt[3], true)),
Positioned(top: h * 0.80, right: w * 0.20, child: _buildPlayerCard(_playerNumbers[_oppCourt[4]]!, _oppCourt[4], true)),
];
}
List<Widget> _buildBenchPlayers(List<String> bench, bool isOpponent) {
final teamColor = isOpponent ? const Color(0xFFD92C2C) : const Color(0xFF1E5BB2);
// CORREÇÃO: Utilização do prefixo 'bench_' em vez de 'sub_'
final prefix = isOpponent ? "bench_opp_" : "bench_my_";
return bench.map((playerName) {
final num = _playerNumbers[playerName]!;
final int fouls = _playerStats[playerName]!["fls"]!;
final bool isFouledOut = fouls >= 5;
Widget avatarUI = Container(
margin: const EdgeInsets.only(bottom: 5),
child: CircleAvatar(
backgroundColor: isFouledOut ? Colors.grey.shade700 : teamColor,
child: Text(
num,
style: TextStyle(
color: isFouledOut ? Colors.red.shade300 : Colors.white,
fontSize: 14,
decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none
)
),
),
);
if (isFouledOut) {
return GestureDetector(
onTap: () => ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('🛑 $playerName não pode voltar ao jogo (Expulso com 5 faltas).'), backgroundColor: Colors.red),
),
child: avatarUI,
);
}
return Draggable<String>(
data: "$prefix$playerName",
feedback: Material(
color: Colors.transparent,
child: CircleAvatar(backgroundColor: teamColor, child: Text(num, style: const TextStyle(color: Colors.white))),
),
childWhenDragging: const Opacity(opacity: 0.5, child: SizedBox(width: 40, height: 40)),
child: avatarUI,
);
}).toList();
}
Widget _buildPlayerCard(String number, String name, bool isOpponent) {
final teamColor = isOpponent ? const Color(0xFFD92C2C) : const Color(0xFF1E5BB2);
final stats = _playerStats[name]!;
final prefix = isOpponent ? "player_opp_" : "player_my_";
return Draggable<String>(
data: "$prefix$name",
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)),
),
),
childWhenDragging: Opacity(opacity: 0.5, child: _playerCardUI(number, name, stats, teamColor, false, false)),
child: DragTarget<String>(
onAcceptWithDetails: (details) {
final action = details.data;
if (action.startsWith("add_") || action.startsWith("sub_") || action.startsWith("miss_")) {
_handleActionDrag(action, "$prefix$name");
}
// CORREÇÃO: Nova lógica que processa apenas ações que comecem por 'bench_' para substituições
else if (action.startsWith("bench_")) {
setState(() {
if (action.startsWith("bench_my_") && !isOpponent) {
String benchPlayer = action.replaceAll("bench_my_", "");
if (_playerStats[benchPlayer]!["fls"]! >= 5) return;
int courtIndex = _myCourt.indexOf(name);
int benchIndex = _myBench.indexOf(benchPlayer);
_myCourt[courtIndex] = benchPlayer;
_myBench[benchIndex] = name;
_showMyBench = false;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Sai $name, Entra $benchPlayer')));
}
if (action.startsWith("bench_opp_") && isOpponent) {
String benchPlayer = action.replaceAll("bench_opp_", "");
if (_playerStats[benchPlayer]!["fls"]! >= 5) return;
int courtIndex = _oppCourt.indexOf(name);
int benchIndex = _oppBench.indexOf(benchPlayer);
_oppCourt[courtIndex] = benchPlayer;
_oppBench[benchIndex] = name;
_showOppBench = false;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Sai $name, Entra $benchPlayer')));
}
});
}
},
builder: (context, candidateData, rejectedData) {
// CORREÇÃO: Atualização da verificação de hover com base no novo prefixo
bool isSubbing = candidateData.any((data) => data != null && (data.startsWith("bench_my_") || data.startsWith("bench_opp_")));
bool isActionHover = candidateData.any((data) => data != null && (data.startsWith("add_") || data.startsWith("sub_") || data.startsWith("miss_")));
return _playerCardUI(number, name, stats, teamColor, isSubbing, isActionHover);
},
),
);
}
Widget _playerCardUI(String number, String name, Map<String, int> stats, Color teamColor, bool isSubbing, bool isActionHover) {
bool isFouledOut = stats["fls"]! >= 5;
Color bgColor = isFouledOut ? Colors.red.shade100 : Colors.white;
Color borderColor = isFouledOut ? Colors.redAccent : Colors.transparent;
if (isSubbing) {
bgColor = Colors.blue.shade50;
borderColor = Colors.blue;
} else if (isActionHover && !isFouledOut) {
bgColor = Colors.orange.shade50;
borderColor = Colors.orange;
}
int fgm = stats["fgm"]!;
int fga = stats["fga"]!;
String fgPercent = fga > 0 ? ((fgm / fga) * 100).toStringAsFixed(0) : "0";
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
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))],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 40, height: 40,
decoration: BoxDecoration(color: isFouledOut ? Colors.grey : teamColor, borderRadius: BorderRadius.circular(8)),
alignment: Alignment.center,
child: Text(number, style: const TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold)),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
name,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: isFouledOut ? Colors.red : Colors.black87,
decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none
)
),
const SizedBox(height: 1),
Text(
"${stats["pts"]} Pts | FG: $fgm/$fga ($fgPercent%)",
style: TextStyle(fontSize: 11, color: isFouledOut ? Colors.red : Colors.grey[700], fontWeight: FontWeight.w600)
),
Text(
"${stats["ast"]} Ast | ${stats["rbs"]} Rbs | ${stats["fls"]} Fls",
style: TextStyle(fontSize: 11, color: isFouledOut ? Colors.red : Colors.grey, fontWeight: FontWeight.w500)
),
],
),
],
),
);
}
Widget _buildTopScoreboard() {
return Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 30),
decoration: BoxDecoration(
color: const Color(0xFF16202C),
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(15), bottomRight: Radius.circular(15)),
border: Border.all(color: Colors.white, width: 2),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_buildTeamSection(widget.myTeam, _myScore, _myFouls, _myTimeoutsUsed, const Color(0xFF1E5BB2), false),
const SizedBox(width: 25),
Column(
children: [
_timeDisplay(),
const SizedBox(height: 5),
Text("PERÍODO $_currentQuarter", style: const TextStyle(color: Colors.orangeAccent, fontSize: 14, fontWeight: FontWeight.bold)),
],
),
const SizedBox(width: 25),
_buildTeamSection(widget.opponentTeam, _opponentScore, _opponentFouls, _opponentTimeoutsUsed, const Color(0xFFD92C2C), true),
],
),
);
}
Widget _buildTeamSection(String name, int score, int fouls, int timeouts, Color color, bool isOpp) {
final timeoutIndicators = Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(3, (index) => Container(
margin: const EdgeInsets.symmetric(horizontal: 3),
width: 12, height: 12,
decoration: BoxDecoration(shape: BoxShape.circle, color: index < timeouts ? Colors.yellow : Colors.grey.shade600, border: Border.all(color: Colors.black26)),
)),
);
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: isOpp
? [
Column(children: [_scoreBox(score, color), const SizedBox(height: 4), Text("FALTAS: $fouls", style: TextStyle(color: fouls >= 5 ? Colors.red : Colors.yellowAccent, fontSize: 12, fontWeight: FontWeight.bold)), timeoutIndicators]),
const SizedBox(width: 15),
Text(name.toUpperCase(), style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold))
]
: [
Text(name.toUpperCase(), style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(width: 15),
Column(children: [_scoreBox(score, color), const SizedBox(height: 4), Text("FALTAS: $fouls", style: TextStyle(color: fouls >= 5 ? Colors.red : Colors.yellowAccent, fontSize: 12, fontWeight: FontWeight.bold)), timeoutIndicators])
]
);
}
Widget _timeDisplay() => Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 6),
decoration: BoxDecoration(color: const Color(0xFF2C3E50), borderRadius: BorderRadius.circular(6)),
child: Text(_formatTime(_duration), style: const TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, fontFamily: 'monospace')),
);
Widget _scoreBox(int score, Color color) => Container(
width: 50, height: 40,
alignment: Alignment.center,
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(6)),
child: Text(score.toString(), style: const TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold)),
);
Widget _buildActionButtonsPanel() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// COLUNA 1: 1 Ponto
_columnBtn([
_actionBtn("T.O", const Color(0xFF1E5BB2), () => _useTimeout(false), labelSize: 20),
_dragAndTargetBtn("1", Colors.orange, "add_pts_1"),
_dragAndTargetBtn("1", Colors.orange, "sub_pts_1", isX: true),
_dragAndTargetBtn("AST", Colors.blueGrey, "add_ast"),
]),
const SizedBox(width: 15),
// COLUNA 2: 2 Pontos
_columnBtn([
_dragAndTargetBtn("M2", Colors.redAccent, "miss_2"),
_dragAndTargetBtn("2", Colors.orange, "add_pts_2"),
_dragAndTargetBtn("2", Colors.orange, "sub_pts_2", isX: true),
_dragAndTargetBtn("STL", Colors.green, "add_stl"),
]),
const SizedBox(width: 15),
// COLUNA 3: 3 Pontos
_columnBtn([
_dragAndTargetBtn("M3", Colors.redAccent, "miss_3"),
_dragAndTargetBtn("3", Colors.orange, "add_pts_3"),
_dragAndTargetBtn("3", Colors.orange, "sub_pts_3", isX: true),
_dragAndTargetBtn("TOV", Colors.redAccent, "add_tov"),
]),
const SizedBox(width: 15),
// COLUNA 4: Outras Stats
_columnBtn([
_actionBtn("T.O", const Color(0xFFD92C2C), () => _useTimeout(true), labelSize: 20),
_dragAndTargetBtn("ORB", const Color(0xFF1E2A38), "add_rbs", icon: Icons.sports_basketball),
_dragAndTargetBtn("DRB", const Color(0xFF1E2A38), "add_rbs", icon: Icons.sports_basketball),
// AQUI ESTÁ O BLK COM ÍCONE DE MÃO
_dragAndTargetBtn("BLK", Colors.deepPurple, "add_blk", icon: Icons.front_hand),
]),
],
);
}
Widget _columnBtn(List<Widget> children) => Column(mainAxisSize: MainAxisSize.min, children: children.map((c) => Padding(padding: const EdgeInsets.only(bottom: 8), child: c)).toList());
Widget _dragAndTargetBtn(String label, Color color, String actionData, {IconData? icon, bool isX = false}) {
return Draggable<String>(
data: actionData,
feedback: _circle(label, color, icon, true, isX: isX),
childWhenDragging: Opacity(opacity: 0.5, child: _circle(label, color, icon, false, isX: isX)),
child: DragTarget<String>(
onAcceptWithDetails: (details) {
final playerData = details.data;
if (playerData.startsWith("player_")) {
_handleActionDrag(actionData, playerData);
}
},
builder: (context, candidateData, rejectedData) {
bool isHovered = candidateData.any((data) => data != null && data.startsWith("player_"));
return Transform.scale(
scale: isHovered ? 1.15 : 1.0,
child: Container(
decoration: isHovered
? BoxDecoration(shape: BoxShape.circle, boxShadow: [BoxShadow(color: Colors.white, blurRadius: 10, spreadRadius: 3)])
: null,
child: _circle(label, color, icon, false, isX: isX)
),
);
}
),
);
}
Widget _actionBtn(String label, Color color, VoidCallback onTap, {IconData? icon, bool isX = false, double labelSize = 24}) {
return GestureDetector(onTap: onTap, child: _circle(label, color, icon, false, fontSize: labelSize, isX: isX));
}
Widget _circle(String label, Color color, IconData? icon, bool isFeed, {double fontSize = 20, bool isX = false}) {
Widget content;
bool isPointBtn = label == "1" || label == "2" || label == "3";
bool isMissBtn = label == "M2" || label == "M3";
bool isBlkBtn = label == "BLK";
// --- DESIGN SIMPLIFICADO: BOLA COM LINHAS PRETAS E NÚMERO ---
if (isPointBtn || isMissBtn) {
content = Stack(
alignment: Alignment.center,
children: [
// 1. CÍRCULO SÓLIDO PRETO: Isto preenche as partes "transparentes" do ícone com preto!
Container(
width: isFeed ? 55 : 45, // Tamanho exato para não vazar pelas bordas da bola
height: isFeed ? 55 : 45,
decoration: const BoxDecoration(
color: Colors.black, // O preto sólido das linhas
shape: BoxShape.circle,
),
),
// 2. Ícone da Bola de Basquete (Laranja para marcar, avermelhado para falhar)
Icon(
Icons.sports_basketball,
color: color, // Usa a cor laranja ou vermelha passada no botão
size: isFeed ? 65 : 55
),
// 3. Número no centro (Preto com contorno branco)
Stack(
children: [
// Contorno Branco
Text(
label,
style: TextStyle(
fontSize: isFeed ? 26 : 22,
fontWeight: FontWeight.w900,
foreground: Paint()
..style = PaintingStyle.stroke
..strokeWidth = 3
..color = Colors.white,
decoration: TextDecoration.none,
),
),
// Texto Preto
Text(
label,
style: TextStyle(
fontSize: isFeed ? 26 : 22,
fontWeight: FontWeight.w900,
color: Colors.black,
decoration: TextDecoration.none,
),
),
],
),
],
);
}
// --- DESIGN DE MÃO COM TEXTO PARA O BLK ---
else if (isBlkBtn) {
content = Stack(
alignment: Alignment.center,
children: [
Icon(
Icons.front_hand,
color: const Color.fromARGB(207, 56, 52, 52),
size: isFeed ? 55 : 45
),
Stack(
alignment: Alignment.center,
children: [
Text(
label,
style: TextStyle(
fontSize: isFeed ? 18 : 16,
fontWeight: FontWeight.w900,
foreground: Paint()
..style = PaintingStyle.stroke
..strokeWidth = 3
..color = Colors.black,
decoration: TextDecoration.none,
),
),
Text(
label,
style: TextStyle(
fontSize: isFeed ? 18 : 16,
fontWeight: FontWeight.w900,
color: Colors.white,
decoration: TextDecoration.none,
),
),
],
),
],
);
}
// --- RESTANTES BOTÕES DO SISTEMA ---
else if (icon != null) {
content = Icon(icon, color: Colors.white, size: 30);
} else {
content = Text(label, style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: fontSize, decoration: TextDecoration.none));
}
return Stack(
clipBehavior: Clip.none,
alignment: Alignment.bottomRight,
children: [
Container(
width: isFeed ? 70 : 60, height: isFeed ? 70 : 60,
decoration: (isPointBtn || isMissBtn || isBlkBtn)
? const BoxDecoration(color: Colors.transparent) // Retira o círculo de fundo base
: BoxDecoration(
gradient: RadialGradient(colors: [color.withOpacity(0.7), color], radius: 0.8),
shape: BoxShape.circle,
boxShadow: const [BoxShadow(color: Colors.black38, blurRadius: 6, offset: Offset(0, 3))]
),
alignment: Alignment.center,
child: content,
),
// Ícone de Anular
if (isX)
Positioned(
top: 0,
right: 0,
child: Container(
decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle),
child: Icon(Icons.cancel, color: Colors.red, size: isFeed ? 28 : 24)
),
),
],
);
}
}