import 'package:flutter/material.dart'; import 'dart:math' as math; import '../classe/theme.dart'; import '../controllers/placar_controller.dart'; import 'package:playmaker/zone_map_dialog.dart'; // ============================================================================== // WIDGETS COMPONENTIZADOS E POP-UPS // ============================================================================== class ActionSubtypeDialog extends StatelessWidget { final String title; final Map options; final Function(String) onSelected; final double sf; const ActionSubtypeDialog({super.key, required this.title, required this.options, required this.onSelected, required this.sf}); @override Widget build(BuildContext context) { return Dialog( backgroundColor: Colors.transparent, elevation: 0, child: Container( width: MediaQuery.of(context).size.width * 0.55, decoration: BoxDecoration( color: AppTheme.placarDarkSurface, borderRadius: BorderRadius.circular(12 * sf), border: Border.all(color: AppTheme.warningAmber, width: 1.5 * sf), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: EdgeInsets.symmetric(horizontal: 12 * sf, vertical: 12 * sf), decoration: BoxDecoration( color: AppTheme.placarListCard, borderRadius: BorderRadius.vertical(top: Radius.circular(10 * sf)), ), child: Stack( alignment: Alignment.center, children: [ Align( alignment: Alignment.center, child: Text( title, style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16 * sf), ), ), Align( alignment: Alignment.centerRight, child: InkWell( onTap: () => Navigator.pop(context), child: Container( padding: EdgeInsets.all(4 * sf), decoration: const BoxDecoration(color: Colors.white24, shape: BoxShape.circle), child: Icon(Icons.close, color: Colors.white, size: 16 * sf), ), ), ), ], ), ), Padding( padding: EdgeInsets.symmetric(vertical: 20 * sf, horizontal: 15 * sf), child: Wrap( spacing: 12 * sf, runSpacing: 15 * sf, alignment: WrapAlignment.center, children: options.entries.map((e) => SizedBox( width: 110 * sf, height: 60 * sf, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: AppTheme.placarTimerBg, foregroundColor: Colors.white, padding: EdgeInsets.all(6 * sf), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8 * sf), side: BorderSide(color: Colors.white12, width: 1 * sf), ), ), onPressed: () => onSelected(e.key), // Retorna a chave correta (ex: "tov_3s") child: Text( e.value, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12 * sf), textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, ), ), )).toList(), ), ), ], ), ), ); } } void showFoulVictimDialog(BuildContext context, PlacarController controller, bool isCommitterOpponent, String committerId, String foulType, double sf) { final victimCourt = isCommitterOpponent ? controller.myCourt : controller.oppCourt; final prefixCommitter = isCommitterOpponent ? "player_opp_" : "player_my_"; final prefixVictim = isCommitterOpponent ? "player_my_" : "player_opp_"; final victimsColor = isCommitterOpponent ? AppTheme.myTeamBlue : AppTheme.oppTeamRed; final possibleVictims = victimCourt.where((id) => !id.startsWith("fake_")).toList(); // Função interna para verificar se o jogador tem de sair void checkFouledOut() { final fouls = controller.playerStats[committerId]?["fls"] ?? 0; final isCourt = isCommitterOpponent ? controller.oppCourt.contains(committerId) : controller.myCourt.contains(committerId); if (fouls >= 5 && isCourt) { Future.delayed(const Duration(milliseconds: 300), () { if (!context.mounted) return; showDialog( context: context, barrierDismissible: false, // Obriga a fazer a substituição builder: (ctx) => SubstitutionDialog( controller: controller, isOpponent: isCommitterOpponent, sf: sf, forcedStarterId: committerId, // Passamos o jogador que foi expulso ), ); }); } } showDialog( context: context, barrierDismissible: false, builder: (ctx) => Dialog( backgroundColor: Colors.transparent, elevation: 0, child: Container( width: MediaQuery.of(context).size.width * 0.60, decoration: BoxDecoration( color: AppTheme.placarDarkSurface, borderRadius: BorderRadius.circular(12 * sf), border: Border.all(color: AppTheme.warningAmber, width: 1.5 * sf), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: EdgeInsets.symmetric(horizontal: 12 * sf, vertical: 12 * sf), decoration: BoxDecoration( color: AppTheme.placarListCard, borderRadius: BorderRadius.vertical(top: Radius.circular(10 * sf)), ), child: Stack( alignment: Alignment.center, children: [ Align( alignment: Alignment.center, child: Text( "Quem sofreu a falta?", style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16 * sf), ), ), Align( alignment: Alignment.centerRight, child: InkWell( onTap: () => Navigator.pop(ctx), child: Container( padding: EdgeInsets.all(4 * sf), decoration: const BoxDecoration(color: Colors.white24, shape: BoxShape.circle), child: Icon(Icons.close, color: Colors.white, size: 16 * sf), ), ), ), ], ), ), Padding( padding: EdgeInsets.symmetric(vertical: 20 * sf, horizontal: 10 * sf), child: Column( children: [ Wrap( spacing: 10 * sf, runSpacing: 10 * sf, alignment: WrapAlignment.center, children: possibleVictims.map((id) { final name = controller.playerNames[id] ?? "Desconhecido"; final number = controller.playerNumbers[id] ?? "0"; return InkWell( onTap: () { Navigator.pop(ctx); controller.registerFoul("$prefixCommitter$committerId", foulType, "$prefixVictim$id"); checkFouledOut(); // Verifica 5 faltas! }, child: Container( width: 80 * sf, padding: EdgeInsets.all(6 * sf), decoration: BoxDecoration( color: victimsColor.withOpacity(0.2), border: Border.all(color: victimsColor, width: 1.5 * sf), borderRadius: BorderRadius.circular(12 * sf), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ CircleAvatar( backgroundColor: victimsColor, radius: 16 * sf, child: Text(number, style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14 * sf)), ), SizedBox(height: 6 * sf), Text(name, style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 10 * sf), textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis), ], ), ), ); }).toList(), ), SizedBox(height: 15 * sf), const Divider(color: Colors.white24), SizedBox(height: 8 * sf), ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: AppTheme.placarTimerBg, foregroundColor: Colors.white, padding: EdgeInsets.symmetric(horizontal: 16 * sf, vertical: 10 * sf), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8 * sf), side: BorderSide(color: Colors.white12, width: 1 * sf), ), ), icon: Icon(Icons.group, color: Colors.white, size: 16 * sf), label: Text("Equipa / Sem Vítima Específica", style: TextStyle(fontSize: 12 * sf)), onPressed: () { Navigator.pop(ctx); controller.registerFoul("$prefixCommitter$committerId", foulType, ""); checkFouledOut(); // Verifica 5 faltas! }, ) ], ), ), ], ), ), ), ); } void showAssistDialog(BuildContext context, PlacarController controller, bool isOpponent, String scorerId, double sf) { final teamCourt = isOpponent ? controller.oppCourt : controller.myCourt; final prefix = isOpponent ? "player_opp_" : "player_my_"; final teamColor = isOpponent ? AppTheme.oppTeamRed : AppTheme.myTeamBlue; final possibleAssistants = teamCourt.where((id) => id != scorerId && !id.startsWith("fake_")).toList(); if (possibleAssistants.isEmpty) return; showDialog( context: context, barrierDismissible: false, builder: (ctx) => Dialog( backgroundColor: Colors.transparent, elevation: 0, child: Container( width: MediaQuery.of(context).size.width * 0.55, decoration: BoxDecoration( color: AppTheme.placarDarkSurface, borderRadius: BorderRadius.circular(12 * sf), border: Border.all(color: AppTheme.warningAmber, width: 1.5 * sf), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: EdgeInsets.symmetric(horizontal: 12 * sf, vertical: 12 * sf), decoration: BoxDecoration( color: AppTheme.placarListCard, borderRadius: BorderRadius.vertical(top: Radius.circular(10 * sf)), ), child: Stack( alignment: Alignment.center, children: [ Align( alignment: Alignment.center, child: Text( "Houve Assistência?", style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16 * sf), ), ), Align( alignment: Alignment.centerRight, child: InkWell( onTap: () => Navigator.pop(ctx), child: Container( padding: EdgeInsets.all(4 * sf), decoration: const BoxDecoration(color: Colors.white24, shape: BoxShape.circle), child: Icon(Icons.close, color: Colors.white, size: 16 * sf), ), ), ), ], ), ), Padding( padding: EdgeInsets.symmetric(vertical: 20 * sf, horizontal: 10 * sf), child: Column( children: [ Wrap( spacing: 10 * sf, runSpacing: 10 * sf, alignment: WrapAlignment.center, children: possibleAssistants.map((id) { final name = controller.playerNames[id] ?? "Desconhecido"; final number = controller.playerNumbers[id] ?? "0"; return InkWell( onTap: () { Navigator.pop(ctx); controller.commitStat("add_ast", "$prefix$id"); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Assistência: $name'), duration: const Duration(seconds: 1), backgroundColor: AppTheme.successGreen)); }, child: Container( width: 75 * sf, padding: EdgeInsets.all(6 * sf), decoration: BoxDecoration( color: teamColor.withOpacity(0.2), border: Border.all(color: teamColor, width: 1.5 * sf), borderRadius: BorderRadius.circular(12 * sf), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ CircleAvatar( backgroundColor: teamColor, radius: 16 * sf, child: Text(number, style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14 * sf)), ), SizedBox(height: 6 * sf), Text(name, style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 10 * sf), textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis), ], ), ), ); }).toList(), ), SizedBox(height: 15 * sf), const Divider(color: Colors.white24), SizedBox(height: 8 * sf), ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: AppTheme.placarTimerBg, foregroundColor: Colors.white, padding: EdgeInsets.symmetric(horizontal: 16 * sf, vertical: 10 * sf), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8 * sf), side: BorderSide(color: Colors.white12, width: 1 * sf), ), ), icon: Icon(Icons.person_off, color: Colors.white, size: 16 * sf), label: Text("Isolado (Sem assistência)", style: TextStyle(fontSize: 12 * sf)), onPressed: () => Navigator.pop(ctx), ) ], ), ), ], ), ), ), ); } class TopScoreboard extends StatelessWidget { final PlacarController controller; final double sf; const TopScoreboard({super.key, required this.controller, required this.sf}); @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.symmetric(vertical: 6 * sf, horizontal: 20 * sf), decoration: BoxDecoration( color: AppTheme.placarDarkSurface, borderRadius: BorderRadius.only( bottomLeft: Radius.circular(22 * sf), bottomRight: Radius.circular(22 * 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), SizedBox(width: 20 * sf), Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: EdgeInsets.symmetric( horizontal: 14 * sf, vertical: 4 * sf), decoration: BoxDecoration( color: AppTheme.placarTimerBg, borderRadius: BorderRadius.circular(9 * sf)), child: ValueListenableBuilder( valueListenable: controller.durationNotifier, builder: (context, duration, child) { String formatTime = "${duration.inMinutes.toString().padLeft(2, '0')}:${duration.inSeconds.remainder(60).toString().padLeft(2, '0')}"; return Text(formatTime, style: TextStyle( color: Colors.white, fontSize: 24 * sf, fontWeight: FontWeight.w900, fontFamily: 'monospace', letterSpacing: 1.5 * sf)); }, ), ), SizedBox(height: 4 * sf), Text("PERÍODO ${controller.currentQuarter}", style: TextStyle( color: AppTheme.warningAmber, fontSize: 12 * sf, fontWeight: FontWeight.w900)), ], ), SizedBox(width: 20 * sf), _buildTeamSection( controller.opponentTeam, controller.opponentScore, controller.opponentFouls, controller.opponentTimeoutsUsed, AppTheme.oppTeamRed, true, sf), ], ), ); } Widget _buildTeamSection(String name, int score, int fouls, int timeouts, Color color, bool isOpp, double sf) { int displayFouls = fouls > 5 ? 5 : fouls; final timeoutIndicators = Row( mainAxisSize: MainAxisSize.min, children: List.generate( 3, (index) => Container( margin: EdgeInsets.symmetric(horizontal: 2.5 * sf), width: 10 * sf, height: 10 * sf, decoration: BoxDecoration( shape: BoxShape.circle, color: index < timeouts ? AppTheme.warningAmber : Colors.grey.shade600, border: Border.all(color: Colors.white54, width: 1.0 * sf)), )), ); List content = [ Column(children: [ _scoreBox(score, color, sf), SizedBox(height: 5 * sf), timeoutIndicators ]), SizedBox(width: 12 * sf), Column( crossAxisAlignment: isOpp ? CrossAxisAlignment.start : CrossAxisAlignment.end, children: [ Text(name.toUpperCase(), style: TextStyle( color: Colors.white, fontSize: 16 * sf, fontWeight: FontWeight.w900, letterSpacing: 1.0 * sf)), SizedBox(height: 3 * sf), Text("FALTAS: $displayFouls", style: TextStyle( color: displayFouls >= 5 ? AppTheme.actionMiss : AppTheme.warningAmber, fontSize: 11 * sf, fontWeight: FontWeight.bold)), ], ) ]; return Row( crossAxisAlignment: CrossAxisAlignment.center, children: isOpp ? content : content.reversed.toList()); } Widget _scoreBox(int score, Color color, double sf) => Container( width: 45 * sf, height: 35 * sf, alignment: Alignment.center, decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(6 * sf)), child: Text(score.toString(), style: TextStyle( color: Colors.white, fontSize: 20 * sf, fontWeight: FontWeight.w900)), ); } class ShirtPainter extends CustomPainter { final Color color; final bool isFouledOut; const ShirtPainter({required this.color, this.isFouledOut = false}); @override void paint(Canvas canvas, Size size) { final double w = size.width; final double h = size.height; final Color shirtColor = isFouledOut ? Colors.grey.shade700 : color; final paint = Paint() ..color = shirtColor ..style = PaintingStyle.fill; final trimPaint = Paint() ..color = Colors.white ..style = PaintingStyle.stroke ..strokeWidth = w * 0.04 ..strokeJoin = StrokeJoin.round; final path = Path(); path.moveTo(w * 0.32, h * 0.10); path.lineTo(w * 0.18, h * 0.10); path.quadraticBezierTo(w * 0.28, h * 0.35, w * 0.05, h * 0.55); path.lineTo(w * 0.15, h * 1.1); path.lineTo(w * 0.85, h * 1.1); path.lineTo(w * 0.95, h * 0.55); path.quadraticBezierTo(w * 0.72, h * 0.35, w * 0.82, h * 0.10); path.lineTo(w * 0.68, h * 0.10); path.quadraticBezierTo(w * 0.50, h * 0.45, w * 0.32, h * 0.10); path.close(); canvas.drawPath(path, paint); canvas.drawPath(path, trimPaint); } @override bool shouldRepaint(ShirtPainter old) => old.color != color || old.isFouledOut != isFouledOut; } class PlayerCourtCard extends StatelessWidget { final PlacarController controller; final String playerId; final bool isOpponent; final double sf; const PlayerCourtCard({super.key, required this.controller, required this.playerId, required this.isOpponent, required this.sf}); @override Widget build(BuildContext context) { final teamColor = isOpponent ? AppTheme.oppTeamRed : AppTheme.myTeamBlue; final realName = controller.playerNames[playerId] ?? "Erro"; final stats = controller.playerStats[playerId]!; final number = controller.playerNumbers[playerId]!; final prefix = isOpponent ? "player_opp_" : "player_my_"; return Draggable( data: "$prefix$playerId", feedback: Material( color: Colors.transparent, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration(color: teamColor.withOpacity(0.9), borderRadius: BorderRadius.circular(6)), child: Text(realName, style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)), ), ), childWhenDragging: Opacity(opacity: 0.5, child: _playerCardUI(number, realName, stats, teamColor, false, false, sf)), child: DragTarget( onAcceptWithDetails: (details) { final action = details.data; if (action == "add_pts_2" || action == "add_pts_3" || action == "miss_2" || action == "miss_3") { bool isMake = action.startsWith("add_"); bool is3Pt = action.endsWith("_3"); showDialog( context: context, builder: (ctx) => ZoneMapDialog( playerName: realName, isMake: isMake, is3PointAction: is3Pt, onZoneSelected: (zone, points, relX, relY) { Navigator.pop(ctx); controller.registerShotFromPopup(context, action, "$prefix$playerId", zone, points, relX, relY); if (isMake) { showAssistDialog(context, controller, isOpponent, playerId, sf); } }, ), ); } // ─── NOVO POP-UP PARA AS FALTAS ─── else if (action == "add_foul") { showDialog( context: context, builder: (ctx) => ActionSubtypeDialog( title: "TIPO DE FALTA", options: const { "Defensiva": "Falta Defensiva", "Ofensiva": "Falta Ofensiva", "Técnica": "Falta Técnica", "Antidesportiva": "Antidesportiva", "Desqualificante": "Desqualificante", }, sf: sf, onSelected: (foulType) { Navigator.pop(ctx); // Depois de escolher o tipo de falta, abre o pop-up a perguntar quem sofreu showFoulVictimDialog(context, controller, isOpponent, playerId, foulType, sf); }, ), ); } // ─── POP-UPS PARA TOV, STL E BLK ─── else if (action == "add_tov") { showDialog( context: context, builder: (ctx) => ActionSubtypeDialog( title: "TIPO DE TURNOVER", options: const { "tov_3s": "3 Segundos", "tov_clock": "Relógio Lanç.", "tov_travel": "Passos", "tov_double": "Dribles Duplos", "tov_badpass": "Passe Ruim", }, sf: sf, onSelected: (subAction) { Navigator.pop(ctx); controller.handleActionDrag(context, subAction, "$prefix$playerId"); }, ), ); } else if (action == "add_stl") { showDialog( context: context, builder: (ctx) => ActionSubtypeDialog( title: "TIPO DE ROUBO", options: const { "stl_steal": "Roubo de Bola", "stl_intercept": "Interceção Lanç.", }, sf: sf, onSelected: (subAction) { Navigator.pop(ctx); controller.handleActionDrag(context, subAction, "$prefix$playerId"); }, ), ); } else if (action == "add_blk") { showDialog( context: context, builder: (ctx) => ActionSubtypeDialog( title: "DESARME", options: const { "blk_made": "Fez o Desarme", "blk_suffered": "Sofreu Desarme", }, sf: sf, onSelected: (subAction) { Navigator.pop(ctx); controller.handleActionDrag(context, subAction, "$prefix$playerId"); }, ), ); } // ─── FIM DOS POP-UPS ESPECIAIS ─── else if (action.startsWith("add_") || action.startsWith("sub_") || action.startsWith("miss_")) { controller.handleActionDrag(context, action, "$prefix$playerId"); } else if (action.startsWith("bench_")) { controller.handleSubbing(context, action, playerId, isOpponent); } }, builder: (context, candidateData, rejectedData) { 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, realName, stats, teamColor, isSubbing, isActionHover, sf); }, ), ); } Widget _playerCardUI(String number, String displayNameStr, Map stats, Color teamColor, bool isSubbing, bool isActionHover, double sf) { bool isFouledOut = stats["fls"]! >= 5; Color bgColor = isFouledOut ? Colors.red.shade100 : Colors.white; Color borderColor = isFouledOut ? AppTheme.actionMiss : Colors.transparent; if (isSubbing) { bgColor = Colors.blue.shade50; borderColor = AppTheme.myTeamBlue; } else if (isActionHover && !isFouledOut) { bgColor = Colors.orange.shade50; borderColor = AppTheme.actionPoints; } int fgm = stats["fgm"]!; int fga = stats["fga"]!; String fgPercent = fga > 0 ? ((fgm / fga) * 100).toStringAsFixed(0) : "0"; String displayName = displayNameStr.length > 12 ? "${displayNameStr.substring(0, 10)}..." : displayNameStr; final double shirtSize = 40 * sf; return Container( padding: EdgeInsets.symmetric(horizontal: 8 * sf, vertical: 6 * sf), decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(8 * sf), border: Border.all(color: borderColor, width: 1.5 * sf), boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 4 * sf, offset: Offset(0, 2 * sf))], ), child: IntrinsicHeight( child: Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox( width: shirtSize, height: shirtSize, child: Stack( alignment: Alignment.center, children: [ CustomPaint( size: Size(shirtSize, shirtSize), painter: ShirtPainter( color: teamColor, isFouledOut: isFouledOut, ), ), Padding( padding: EdgeInsets.only(top: shirtSize * 0.15), child: Text( number, style: TextStyle( color: Colors.white, fontSize: shirtSize * 0.40, fontWeight: FontWeight.w900, decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none, shadows: const [Shadow(color: Colors.black45, blurRadius: 2, offset: Offset(1, 1))], ), ), ), ], ), ), SizedBox(width: 8 * sf), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Text(displayName, style: TextStyle(fontSize: 14 * sf, fontWeight: FontWeight.bold, color: isFouledOut ? AppTheme.actionMiss : Colors.black87, decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none)), SizedBox(height: 1.5 * sf), Text("${stats["pts"]} Pts | FG: $fgm/$fga ($fgPercent%)", style: TextStyle(fontSize: 10 * sf, color: isFouledOut ? AppTheme.actionMiss : Colors.grey[700], fontWeight: FontWeight.w600)), Text("${stats["ast"]} Ast | ${stats["orb"]! + stats["drb"]!} Rbs | ${stats["fls"]} Fls", style: TextStyle(fontSize: 10 * sf, color: isFouledOut ? AppTheme.actionMiss : Colors.grey[500], fontWeight: FontWeight.w600)), ], ), ], ), ), ); } } class SubstitutionDialog extends StatefulWidget { final PlacarController controller; final bool isOpponent; final double sf; final String? forcedStarterId; // <--- ADICIONADO PARA EXPULSÕES const SubstitutionDialog({ super.key, required this.controller, required this.isOpponent, required this.sf, this.forcedStarterId, }); @override State createState() => _SubstitutionDialogState(); } class _SubstitutionDialogState extends State { String? _selectedStarterId; String? _selectedBenchId; PlacarController get ctrl => widget.controller; bool get isOpp => widget.isOpponent; double get sf => widget.sf; List get court => isOpp ? ctrl.oppCourt : ctrl.myCourt; List get bench => isOpp ? ctrl.oppBench : ctrl.myBench; Color get teamColor => isOpp ? AppTheme.oppTeamRed : AppTheme.myTeamBlue; String get teamName => isOpp ? ctrl.opponentTeam : ctrl.myTeam; bool get canConfirm => _selectedStarterId != null && _selectedBenchId != null; bool get isForced => widget.forcedStarterId != null; // NOVO @override void initState() { super.initState(); // Se for obrigado a sair, já aparece selecionado! if (isForced) { _selectedStarterId = widget.forcedStarterId; } } void _confirmSwap() { if (!canConfirm) return; final benchPrefix = isOpp ? "bench_opp_" : "bench_my_"; ctrl.handleSubbing( context, "$benchPrefix$_selectedBenchId", _selectedStarterId!, isOpp, ); Navigator.pop(context); } @override Widget build(BuildContext context) { const activeColor = Color(0xFF22C55E); return Dialog( backgroundColor: Colors.transparent, insetPadding: EdgeInsets.all(16 * sf), child: Container( width: 420 * sf, decoration: BoxDecoration( color: const Color(0xFF1A1F2E), borderRadius: BorderRadius.circular(14 * sf), border: Border.all(color: isForced ? AppTheme.actionMiss : const Color(0xFF2D3450), width: 2), // Borda vermelha se for expulsão ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: EdgeInsets.symmetric(horizontal: 14 * sf, vertical: 10 * sf), decoration: BoxDecoration( color: isForced ? AppTheme.actionMiss.withOpacity(0.8) : const Color(0xFF1E2540), // Fundo vermelho no título borderRadius: BorderRadius.vertical(top: Radius.circular(12 * sf)), border: const Border(bottom: BorderSide(color: Color(0xFF2D3450))), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( isForced ? "SUBSTITUIÇÃO OBRIGATÓRIA (5 Faltas)" : "Substituição — ${teamName.toUpperCase()}", style: TextStyle(color: Colors.white, fontSize: 13 * sf, fontWeight: FontWeight.w600), ), if (!isForced) // Esconde o "X" de fechar se for forçado InkWell( onTap: () => Navigator.pop(context), child: Container( padding: EdgeInsets.all(4 * sf), decoration: const BoxDecoration(color: Colors.white24, shape: BoxShape.circle), child: Icon(Icons.close, color: Colors.white, size: 14 * sf), ), ), ], ), ), _sectionLabel("Em Campo"), _playerGrid( players: court.where((id) => !id.startsWith("fake_")).toList(), selected: _selectedStarterId, isStarter: true, activeColor: activeColor, onTap: (id) { if (isForced) return; // Se for forçado, não deixa clicar/desmarcar o titular setState(() { _selectedStarterId = _selectedStarterId == id ? null : id; }); }, ), Divider(color: Colors.white12, height: 1, indent: 10 * sf, endIndent: 10 * sf), _sectionLabel("Banco de Suplentes"), _playerGrid( players: bench.where((id) => !id.startsWith("fake_")).toList(), selected: _selectedBenchId, isStarter: false, activeColor: activeColor, onTap: (id) { final fouls = ctrl.playerStats[id]?["fls"] ?? 0; if (fouls >= 5) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text('🛑 ${ctrl.playerNames[id]} expulso!'), backgroundColor: AppTheme.actionMiss)); return; } setState(() { _selectedBenchId = _selectedBenchId == id ? null : id; }); }, ), Padding( padding: EdgeInsets.symmetric(horizontal: 12 * sf, vertical: 6 * sf), child: Text( _hintText(), style: TextStyle(color: isForced ? AppTheme.actionMiss : Colors.white38, fontSize: 11 * sf, fontWeight: isForced ? FontWeight.bold : FontWeight.normal), textAlign: TextAlign.center, ), ), Padding( padding: EdgeInsets.fromLTRB(12 * sf, 0, 12 * sf, 12 * sf), child: Row( children: [ if (!isForced) // Esconde o botão de cancelar ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.white12, foregroundColor: Colors.white70, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8 * sf)), padding: EdgeInsets.symmetric(horizontal: 14 * sf, vertical: 10 * sf), elevation: 0, ), onPressed: () => Navigator.pop(context), child: Text("Cancelar", style: TextStyle(fontSize: 13 * sf)), ), if (!isForced) SizedBox(width: 8 * sf), Expanded( child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: canConfirm ? activeColor : Colors.white12, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8 * sf)), padding: EdgeInsets.symmetric(vertical: 10 * sf), elevation: 0, ), onPressed: canConfirm ? _confirmSwap : null, child: Text(isForced ? "Substituir Jogador" : "Confirmar Troca", style: TextStyle(fontSize: 13 * sf, fontWeight: FontWeight.w600)), ), ), ], ), ), ], ), ), ); } String _hintText() { if (isForced && _selectedBenchId == null) { return "Um jogador atingiu as 5 faltas. Seleciona um suplente obrigatoriamente."; } else if (_selectedStarterId == null && _selectedBenchId == null) { return "Seleciona um titular e um suplente para fazer a troca"; } else if (_selectedStarterId != null && _selectedBenchId == null) { return "Agora seleciona o suplente que vai entrar"; } else if (_selectedStarterId == null && _selectedBenchId != null) { return "Agora seleciona o titular que vai sair"; } else { final s = ctrl.playerNames[_selectedStarterId] ?? ""; final sNum = ctrl.playerNumbers[_selectedStarterId] ?? ""; final b = ctrl.playerNames[_selectedBenchId] ?? ""; final bNum = ctrl.playerNumbers[_selectedBenchId] ?? ""; return "#$sNum $s ↔ #$bNum $b"; } } Widget _sectionLabel(String label) => Padding( padding: EdgeInsets.fromLTRB(12 * sf, 8 * sf, 12 * sf, 4 * sf), child: Align( alignment: Alignment.centerLeft, child: Text( label.toUpperCase(), style: TextStyle(color: Colors.white38, fontSize: 10 * sf, letterSpacing: 0.8, fontWeight: FontWeight.w500), ), ), ); Widget _playerGrid({ required List players, required String? selected, required bool isStarter, required Color activeColor, required void Function(String) onTap, }) { return Padding( padding: EdgeInsets.fromLTRB(10 * sf, 0, 10 * sf, 6 * sf), child: Wrap( spacing: 6 * sf, runSpacing: 6 * sf, children: players.map((id) { final isSelected = selected == id; final name = ctrl.playerNames[id] ?? "?"; final num = ctrl.playerNumbers[id] ?? "0"; final fouls = ctrl.playerStats[id]?["fls"] ?? 0; final isFouledOut = fouls >= 5; final shortName = name.length > 8 ? "${name.substring(0, 7)}." : name; final bgColor = isSelected ? const Color(0xFF14331F) : isStarter ? const Color(0xFF1E2540) : const Color(0xFF141824); final borderColor = isSelected ? activeColor : Colors.transparent; final shirtColor = isSelected ? const Color(0xFF15803D) : isFouledOut ? Colors.grey.shade700 : teamColor; return GestureDetector( onTap: () => onTap(id), child: AnimatedContainer( duration: const Duration(milliseconds: 150), width: 60 * sf, padding: EdgeInsets.symmetric(vertical: 6 * sf, horizontal: 2 * sf), decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(10 * sf), border: Border.all(color: borderColor, width: 2), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 36 * sf, height: 36 * sf, child: Stack( alignment: Alignment.center, children: [ CustomPaint( size: Size(36 * sf, 36 * sf), painter: ShirtPainter(color: shirtColor, isFouledOut: isFouledOut), ), Padding( padding: EdgeInsets.only(top: 36 * sf * 0.15), child: Text( num, style: TextStyle( color: Colors.white, fontSize: 36 * sf * 0.38, fontWeight: FontWeight.w900, decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none, shadows: const [Shadow(color: Colors.black45, blurRadius: 2)], ), ), ), ], ), ), SizedBox(height: 3 * sf), Text( shortName, style: TextStyle(color: Colors.white70, fontSize: 9 * sf, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, ), if (isFouledOut) Container( margin: EdgeInsets.only(top: 2 * sf), padding: EdgeInsets.symmetric(horizontal: 4 * sf, vertical: 1 * sf), decoration: BoxDecoration( color: Colors.red.shade900.withOpacity(0.4), border: Border.all(color: Colors.red.shade400), borderRadius: BorderRadius.circular(4 * sf), ), child: Text("5 Fls", style: TextStyle(color: Colors.red.shade300, fontSize: 8 * sf)), ), ], ), ), ); }).toList(), ), ); } } class HeatmapDialog extends StatefulWidget { final List shots; final String myTeamName; final String oppTeamName; final List myPlayersIds; final List oppPlayersIds; final Map> playerStats; final Map playerNames; const HeatmapDialog({ super.key, required this.shots, required this.myTeamName, required this.oppTeamName, required this.myPlayersIds, required this.oppPlayersIds, required this.playerStats, required this.playerNames, }); @override State createState() => _HeatmapDialogState(); } class _HeatmapDialogState extends State { bool _isMapVisible = false; String _selectedTeam = ''; String _selectedPlayerId = ''; @override Widget build(BuildContext context) { const Color headerColor = Color(0xFFE88F15); const Color yellowBackground = Color(0xFFDFAB00); final double screenHeight = MediaQuery.of(context).size.height; final double dialogHeight = screenHeight * 0.95; final double dialogWidth = dialogHeight * 1.0; return Dialog( backgroundColor: yellowBackground, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), clipBehavior: Clip.antiAlias, insetPadding: const EdgeInsets.all(10), child: SizedBox( height: dialogHeight, width: dialogWidth, child: _isMapVisible ? _buildMapScreen(headerColor) : _buildSelectionScreen(headerColor), ), ); } Widget _buildSelectionScreen(Color headerColor) { return Column( children: [ Container( height: 40, color: headerColor, width: double.infinity, child: Stack( alignment: Alignment.center, children: [ const Text("ESCOLHE A EQUIPA OU UM JOGADOR", style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold)), Positioned( right: 8, child: InkWell( onTap: () => Navigator.pop(context), child: Container( padding: const EdgeInsets.all(4), decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle), child: Icon(Icons.close, color: headerColor, size: 16), ), ), ) ], ), ), Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ Expanded(child: _buildTeamColumn(teamName: widget.myTeamName, playerIds: widget.myPlayersIds, teamColor: AppTheme.myTeamBlue)), const SizedBox(width: 8), Expanded(child: _buildTeamColumn(teamName: widget.oppTeamName, playerIds: widget.oppPlayersIds, teamColor: AppTheme.oppTeamRed)), ], ), ), ), ], ); } Widget _buildTeamColumn({required String teamName, required List playerIds, required Color teamColor}) { final realPlayerIds = playerIds.where((id) => !id.startsWith("fake_")).toList(); return Container( decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)), child: Column( children: [ InkWell( onTap: () => setState(() { _selectedTeam = teamName; _selectedPlayerId = 'Todos'; _isMapVisible = true; }), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 10), decoration: BoxDecoration( color: teamColor, borderRadius: const BorderRadius.only(topLeft: Radius.circular(8), topRight: Radius.circular(8)), ), child: Column( children: [ Text(teamName.toUpperCase(), style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16)), const SizedBox(height: 4), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), decoration: BoxDecoration(color: Colors.white24, borderRadius: BorderRadius.circular(12)), child: const Text("MAPA GERAL DA EQUIPA", style: TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)), ), ], ), ), ), Expanded( child: ListView.separated( itemCount: realPlayerIds.length, separatorBuilder: (context, index) => const Divider(height: 1, color: Colors.black12), itemBuilder: (context, index) { final pId = realPlayerIds[index]; final pName = widget.playerNames[pId] ?? 'Desconhecido'; final pts = widget.playerStats[pId]?['pts'] ?? 0; return ListTile( dense: true, visualDensity: VisualDensity.compact, leading: Icon(Icons.person, color: teamColor), title: Text(pName, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: Colors.black87)), trailing: Text("$pts Pts", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: teamColor)), onTap: () => setState(() { _selectedTeam = teamName; _selectedPlayerId = pId; _isMapVisible = true; }), ); }, ), ), ], ), ); } Widget _buildMapScreen(Color headerColor) { final filteredShots = widget.shots.where((s) { if (_selectedPlayerId != 'Todos') return s.playerId == _selectedPlayerId; if (_selectedTeam == widget.myTeamName) return widget.myPlayersIds.contains(s.playerId); if (_selectedTeam == widget.oppTeamName) return widget.oppPlayersIds.contains(s.playerId); return true; }).toList(); final titleText = _selectedPlayerId == 'Todos' ? "MAPA GERAL: ${_selectedTeam.toUpperCase()}" : "MAPA: ${widget.playerNames[_selectedPlayerId]?.toUpperCase() ?? ''}"; return Column( children: [ Container( height: 40, color: headerColor, width: double.infinity, child: Stack( alignment: Alignment.center, children: [ Positioned( left: 8, child: InkWell( onTap: () => setState(() => _isMapVisible = false), child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12)), child: Row(children: [ Icon(Icons.arrow_back, color: headerColor, size: 14), const SizedBox(width: 4), Text("VOLTAR", style: TextStyle(color: headerColor, fontWeight: FontWeight.bold, fontSize: 12)), ]), ), ), ), Text(titleText, style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold)), Positioned( right: 8, child: InkWell( onTap: () => Navigator.pop(context), child: Container( padding: const EdgeInsets.all(4), decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle), child: Icon(Icons.close, color: headerColor, size: 16), ), ), ), ], ), ), Expanded( child: LayoutBuilder(builder: (context, constraints) { return Stack( children: [ CustomPaint( size: Size(constraints.maxWidth, constraints.maxHeight), painter: HeatmapCourtPainter(), ), ...filteredShots.map((shot) => Positioned( left: (shot.relativeX * constraints.maxWidth) - 8, top: (shot.relativeY * constraints.maxHeight) - 8, child: CircleAvatar( radius: 8, backgroundColor: shot.isMake ? AppTheme.successGreen : AppTheme.actionMiss, child: Icon(shot.isMake ? Icons.check : Icons.close, size: 10, color: Colors.white), ), )), ], ); }), ), ], ); } } class HeatmapCourtPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final double w = size.width; final double h = size.height; final double basketX = w / 2; final Paint whiteStroke = Paint()..color = Colors.white..style = PaintingStyle.stroke..strokeWidth = 2.0; final Paint blackStroke = Paint()..color = Colors.black87..style = PaintingStyle.stroke..strokeWidth = 2.0; final double margin = w * 0.10; final double length = h * 0.35; final double larguraDoArco = (w / 2) - margin; final double alturaDoArco = larguraDoArco * 0.30; final double totalArcoHeight = alturaDoArco * 4; canvas.drawLine(Offset(margin, 0), Offset(margin, length), whiteStroke); canvas.drawLine(Offset(w - margin, 0), Offset(w - margin, length), whiteStroke); canvas.drawLine(Offset(0, length), Offset(margin, length), whiteStroke); canvas.drawLine(Offset(w - margin, length), Offset(w, length), whiteStroke); canvas.drawArc(Rect.fromCenter(center: Offset(basketX, length), width: larguraDoArco * 2, height: totalArcoHeight), 0, math.pi, false, whiteStroke); double sXL = basketX + (larguraDoArco * math.cos(math.pi * 0.75)); double sYL = length + ((totalArcoHeight / 2) * math.sin(math.pi * 0.75)); double sXR = basketX + (larguraDoArco * math.cos(math.pi * 0.25)); double sYR = length + ((totalArcoHeight / 2) * math.sin(math.pi * 0.25)); canvas.drawLine(Offset(sXL, sYL), Offset(0, h * 0.85), whiteStroke); canvas.drawLine(Offset(sXR, sYR), Offset(w, h * 0.85), whiteStroke); final double pW = w * 0.28; final double pH = h * 0.38; canvas.drawRect(Rect.fromLTWH(basketX - pW / 2, 0, pW, pH), blackStroke); final double ftR = pW / 2; canvas.drawArc(Rect.fromCircle(center: Offset(basketX, pH), radius: ftR), 0, math.pi, false, blackStroke); for (int i = 0; i < 10; i++) { canvas.drawArc(Rect.fromCircle(center: Offset(basketX, pH), radius: ftR), math.pi + (i * 2 * (math.pi / 20)), math.pi / 20, false, blackStroke); } canvas.drawLine(Offset(basketX - pW / 2, pH), Offset(sXL, sYL), blackStroke); canvas.drawLine(Offset(basketX + pW / 2, pH), Offset(sXR, sYR), blackStroke); canvas.drawArc(Rect.fromCircle(center: Offset(basketX, h), radius: w * 0.12), math.pi, math.pi, false, blackStroke); canvas.drawCircle(Offset(basketX, h * 0.12), w * 0.02, blackStroke); canvas.drawLine(Offset(basketX - w * 0.08, h * 0.12 - 5), Offset(basketX + w * 0.08, h * 0.12 - 5), blackStroke); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } class PlayByPlayDialog extends StatelessWidget { final PlacarController controller; const PlayByPlayDialog({super.key, required this.controller}); @override Widget build(BuildContext context) { return Dialog( backgroundColor: AppTheme.placarDarkSurface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: Container( width: 400, height: MediaQuery.of(context).size.height * 0.8, padding: const EdgeInsets.all(16), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text("HISTÓRICO DE JOGADAS", style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)), IconButton(icon: const Icon(Icons.close, color: Colors.white), onPressed: () => Navigator.pop(context)) ], ), const Divider(color: Colors.white24), Expanded( child: controller.playByPlay.isEmpty ? const Center(child: Text("Ainda não há jogadas.", style: TextStyle(color: Colors.white54))) : ListView.separated( itemCount: controller.playByPlay.length, separatorBuilder: (_, __) => const Divider(color: Colors.white10), itemBuilder: (context, index) => Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Text(controller.playByPlay[index], style: const TextStyle(color: Colors.white, fontSize: 14)), ), ), ), ], ), ), ); } } class BoxScoreDialog extends StatelessWidget { final PlacarController controller; final double sf; const BoxScoreDialog({super.key, required this.controller, required this.sf}); @override Widget build(BuildContext context) { return AnimatedBuilder( animation: controller, builder: (context, child) { return Dialog( backgroundColor: AppTheme.placarDarkSurface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12 * sf), side: BorderSide(color: Colors.white24, width: 1 * sf), ), insetPadding: EdgeInsets.all(8 * sf), clipBehavior: Clip.antiAlias, child: SizedBox( width: MediaQuery.of(context).size.width * 0.98, height: MediaQuery.of(context).size.height * 0.98, child: DefaultTabController( length: 2, child: Column( children: [ Padding( padding: EdgeInsets.fromLTRB(16 * sf, 16 * sf, 8 * sf, 0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("BOX SCORE", style: TextStyle(color: Colors.white, fontSize: 20 * sf, fontWeight: FontWeight.bold)), IconButton( icon: Icon(Icons.close, color: Colors.white, size: 24 * sf), padding: EdgeInsets.zero, constraints: const BoxConstraints(), onPressed: () => Navigator.pop(context), ) ], ), ), SizedBox(height: 8 * sf), TabBar( indicatorColor: AppTheme.warningAmber, labelColor: Colors.white, unselectedLabelColor: Colors.white54, labelStyle: TextStyle(fontSize: 14 * sf, fontWeight: FontWeight.bold), indicatorWeight: 3 * sf, dividerColor: Colors.white10, tabs: [ Tab(text: controller.myTeam.toUpperCase(), height: 40 * sf), Tab(text: controller.opponentTeam.toUpperCase(), height: 40 * sf), ], ), Expanded( child: Container( width: double.infinity, color: Colors.black12, child: ValueListenableBuilder( valueListenable: controller.durationNotifier, builder: (context, duration, _) { return TabBarView( physics: const NeverScrollableScrollPhysics(), children: [ _buildStatsTable(controller.myCourt + controller.myBench, controller, sf), _buildStatsTable(controller.oppCourt + controller.oppBench, controller, sf), ], ); }, ), ), ), ], ), ), ), ); }, ); } Widget _buildStatsTable(List teamPlayers, PlacarController ctrl, double sf) { return LayoutBuilder(builder: (context, constraints) { return SingleChildScrollView( scrollDirection: Axis.vertical, physics: const BouncingScrollPhysics(), child: SingleChildScrollView( scrollDirection: Axis.horizontal, physics: const ClampingScrollPhysics(), child: ConstrainedBox( constraints: BoxConstraints(minWidth: constraints.maxWidth), child: DataTable( headingRowColor: WidgetStateProperty.all(AppTheme.placarListCard), columnSpacing: 18 * sf, horizontalMargin: 16 * sf, headingRowHeight: 45 * sf, dataRowMinHeight: 40 * sf, dataRowMaxHeight: 45 * sf, headingTextStyle: TextStyle(color: Colors.white70, fontWeight: FontWeight.bold, fontSize: 13 * sf), dataTextStyle: TextStyle(color: Colors.white, fontSize: 13 * sf), columns: const [ DataColumn(label: Text('JOGADOR')), DataColumn(label: Text('MIN')), DataColumn(label: Text('PTS')), DataColumn(label: Text('REB')), DataColumn(label: Text('AST')), DataColumn(label: Text('STL')), DataColumn(label: Text('BLK')), DataColumn(label: Text('TOV')), DataColumn(label: Text('FLS')), DataColumn(label: Text('SO')), DataColumn(label: Text('IL')), DataColumn(label: Text('LI')), DataColumn(label: Text('PA')), DataColumn(label: Text('3S')), DataColumn(label: Text('DR')), DataColumn(label: Text('FG')), ], rows: teamPlayers.where((id) => !id.startsWith("fake_")).map((id) { final name = ctrl.playerNames[id] ?? "---"; final s = ctrl.playerStats[id]!; final totalSecs = s['sec'] ?? 0; final minutes = totalSecs ~/ 60; final seconds = totalSecs % 60; final timeStr = '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; final rebs = s['orb']! + s['drb']!; final fgText = "${s['fgm']}/${s['fga']}"; return DataRow(cells: [ DataCell(Text(name, style: const TextStyle(fontWeight: FontWeight.bold))), DataCell(Text(timeStr, style: const TextStyle(color: Colors.white70))), DataCell(Text(s['pts'].toString(), style: TextStyle(color: AppTheme.warningAmber, fontWeight: FontWeight.bold, fontSize: 14 * sf))), DataCell(Text(rebs.toString())), DataCell(Text(s['ast'].toString())), DataCell(Text(s['stl'].toString())), DataCell(Text(s['blk'].toString())), DataCell(Text(s['tov'].toString(), style: const TextStyle(color: Colors.redAccent))), DataCell(Text(s['fls'].toString())), DataCell(Text((s['so'] ?? 0).toString(), style: const TextStyle(color: Colors.greenAccent))), DataCell(Text((s['il'] ?? 0).toString(), style: const TextStyle(color: Colors.lightBlue))), DataCell(Text((s['li'] ?? 0).toString(), style: const TextStyle(color: Colors.orangeAccent))), DataCell(Text((s['pa'] ?? 0).toString(), style: const TextStyle(color: Colors.redAccent))), DataCell(Text((s['tres_seg'] ?? 0).toString(), style: const TextStyle(color: Colors.redAccent))), // CORRIGIDO PARA MOSTRAR OS 3 SEG NO BOX SCORE DataCell(Text((s['dr'] ?? 0).toString(), style: const TextStyle(color: Colors.redAccent))), DataCell(Text(fgText, style: const TextStyle(color: Colors.white54))), ]); }).toList(), ), ), ), ); }); } }