melhorar a camisola
This commit is contained in:
@@ -645,6 +645,76 @@ void showAssistDialog(BuildContext context, PlacarController controller, bool is
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SHIRT PAINTER — Desenho 100% código (Formato Jersey Realista)
|
||||||
|
// ============================================================================
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Tinta para preencher a cor da camisola
|
||||||
|
final paint = Paint()
|
||||||
|
..color = shirtColor
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
// Tinta para fazer a borda branca (tipo o acabamento do tecido)
|
||||||
|
final trimPaint = Paint()
|
||||||
|
..color = Colors.white
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = w * 0.04
|
||||||
|
..strokeJoin = StrokeJoin.round;
|
||||||
|
|
||||||
|
final path = Path();
|
||||||
|
|
||||||
|
// 1. Ombro esquerdo (lado do pescoço)
|
||||||
|
path.moveTo(w * 0.32, h * 0.10);
|
||||||
|
|
||||||
|
// 2. Ombro esquerdo (lado do braço)
|
||||||
|
path.lineTo(w * 0.18, h * 0.10);
|
||||||
|
|
||||||
|
// 3. Cava do braço esquerdo (curva funda)
|
||||||
|
path.quadraticBezierTo(w * 0.28, h * 0.35, w * 0.05, h * 0.55);
|
||||||
|
|
||||||
|
// 4. Lado esquerdo (desce até baixo)
|
||||||
|
path.lineTo(w * 0.15, h * 1.1);
|
||||||
|
|
||||||
|
// 5. Fundo da camisola (linha reta em baixo)
|
||||||
|
path.lineTo(w * 0.85, h * 1.1);
|
||||||
|
|
||||||
|
// 6. Lado direito (sobe até à axila)
|
||||||
|
path.lineTo(w * 0.95, h * 0.55);
|
||||||
|
|
||||||
|
// 7. Cava do braço direito (curva funda)
|
||||||
|
path.quadraticBezierTo(w * 0.72, h * 0.35, w * 0.82, h * 0.10);
|
||||||
|
|
||||||
|
// 8. Ombro direito (lado do braço até ao pescoço)
|
||||||
|
path.lineTo(w * 0.68, h * 0.10);
|
||||||
|
|
||||||
|
// 9. Gola (decote redondo profundo)
|
||||||
|
path.quadraticBezierTo(w * 0.50, h * 0.45, w * 0.32, h * 0.10);
|
||||||
|
|
||||||
|
path.close();
|
||||||
|
|
||||||
|
// Desenha o fundo da cor da equipa
|
||||||
|
canvas.drawPath(path, paint);
|
||||||
|
|
||||||
|
// Desenha a borda branca por cima para dar estilo
|
||||||
|
canvas.drawPath(path, trimPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(ShirtPainter old) => old.color != color || old.isFouledOut != isFouledOut;
|
||||||
|
}
|
||||||
|
// ============================================================================
|
||||||
|
// CARD DO JOGADOR NO CAMPO
|
||||||
|
// ============================================================================
|
||||||
class PlayerCourtCard extends StatelessWidget {
|
class PlayerCourtCard extends StatelessWidget {
|
||||||
final PlacarController controller;
|
final PlacarController controller;
|
||||||
final String playerId;
|
final String playerId;
|
||||||
@@ -680,6 +750,7 @@ class PlayerCourtCard extends StatelessWidget {
|
|||||||
if (action == "add_pts_2" || action == "add_pts_3" || action == "miss_2" || action == "miss_3") {
|
if (action == "add_pts_2" || action == "add_pts_3" || action == "miss_2" || action == "miss_3") {
|
||||||
bool isMake = action.startsWith("add_");
|
bool isMake = action.startsWith("add_");
|
||||||
bool is3Pt = action.endsWith("_3");
|
bool is3Pt = action.endsWith("_3");
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => ZoneMapDialog(
|
builder: (ctx) => ZoneMapDialog(
|
||||||
@@ -689,58 +760,14 @@ class PlayerCourtCard extends StatelessWidget {
|
|||||||
onZoneSelected: (zone, points, relX, relY) {
|
onZoneSelected: (zone, points, relX, relY) {
|
||||||
Navigator.pop(ctx);
|
Navigator.pop(ctx);
|
||||||
controller.registerShotFromPopup(context, action, "$prefix$playerId", zone, points, relX, relY);
|
controller.registerShotFromPopup(context, action, "$prefix$playerId", zone, points, relX, relY);
|
||||||
if (isMake) showAssistDialog(context, controller, isOpponent, playerId, sf);
|
|
||||||
|
if (isMake) {
|
||||||
|
showAssistDialog(context, controller, isOpponent, playerId, sf);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (action == "add_tov") {
|
|
||||||
showDialog(context: context, builder: (ctx) => ActionSubtypeDialog(
|
|
||||||
title: "Escolha o tipo de turnover",
|
|
||||||
options: {
|
|
||||||
"add_3s": "3\nsegundos",
|
|
||||||
"add_24s": "Relógio de\nlançamento\n(24s)",
|
|
||||||
"add_pa": "Passos",
|
|
||||||
"add_dr": "Drible duplo",
|
|
||||||
"add_tov": "Passe ruim",
|
|
||||||
},
|
|
||||||
onSelected: (val) { Navigator.pop(ctx); controller.commitStat(val, "$prefix$playerId"); },
|
|
||||||
sf: sf,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
else if (action == "add_stl") {
|
|
||||||
showDialog(context: context, builder: (ctx) => ActionSubtypeDialog(
|
|
||||||
title: "Ação Defensiva",
|
|
||||||
options: {"add_stl": "Roubo de Bola\n(BR)", "add_il": "Interceção\nLançamento (IL)"},
|
|
||||||
onSelected: (val) { Navigator.pop(ctx); controller.commitStat(val, "$prefix$playerId"); },
|
|
||||||
sf: sf,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
else if (action == "add_blk") {
|
|
||||||
showDialog(context: context, builder: (ctx) => ActionSubtypeDialog(
|
|
||||||
title: "Ação de Desarme / Bloco",
|
|
||||||
options: {"add_blk": "Fez o Desarme\n(BLK)", "add_li": "Sofreu o Desarme\n(LI)"},
|
|
||||||
onSelected: (val) { Navigator.pop(ctx); controller.commitStat(val, "$prefix$playerId"); },
|
|
||||||
sf: sf,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
else if (action == "add_foul") {
|
|
||||||
showDialog(context: context, builder: (ctx) => ActionSubtypeDialog(
|
|
||||||
title: "Escolha o tipo de falta pessoal",
|
|
||||||
options: {
|
|
||||||
"Defensiva": "Falta\ndefensiva",
|
|
||||||
"Ofensiva": "Falta\nofensiva",
|
|
||||||
"Técnica": "Falta\ntécnica",
|
|
||||||
"Antidesportiva": "Falta\nantidesportiva",
|
|
||||||
"Desqualificante": "Falta\ndesqualificante"
|
|
||||||
},
|
|
||||||
onSelected: (foulType) {
|
|
||||||
Navigator.pop(ctx);
|
|
||||||
showFoulVictimDialog(context, controller, isOpponent, playerId, foulType, sf);
|
|
||||||
},
|
|
||||||
sf: sf,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
else if (action.startsWith("add_") || action.startsWith("sub_") || action.startsWith("miss_")) {
|
else if (action.startsWith("add_") || action.startsWith("sub_") || action.startsWith("miss_")) {
|
||||||
controller.handleActionDrag(context, action, "$prefix$playerId");
|
controller.handleActionDrag(context, action, "$prefix$playerId");
|
||||||
}
|
}
|
||||||
@@ -770,208 +797,68 @@ class PlayerCourtCard extends StatelessWidget {
|
|||||||
String fgPercent = fga > 0 ? ((fgm / fga) * 100).toStringAsFixed(0) : "0";
|
String fgPercent = fga > 0 ? ((fgm / fga) * 100).toStringAsFixed(0) : "0";
|
||||||
String displayName = displayNameStr.length > 12 ? "${displayNameStr.substring(0, 10)}..." : displayNameStr;
|
String displayName = displayNameStr.length > 12 ? "${displayNameStr.substring(0, 10)}..." : displayNameStr;
|
||||||
|
|
||||||
|
// Tamanho da camisola ajustado para ficar perfeito no cartão
|
||||||
|
final double shirtSize = 40 * sf;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
padding: EdgeInsets.symmetric(horizontal: 8 * sf, vertical: 6 * sf),
|
||||||
decoration: BoxDecoration(color: bgColor, borderRadius: BorderRadius.circular(8), border: Border.all(color: borderColor, width: 1.5), boxShadow: const [BoxShadow(color: Colors.black26, blurRadius: 4, offset: Offset(0, 2))]),
|
decoration: BoxDecoration(
|
||||||
child: ClipRRect(
|
color: bgColor,
|
||||||
borderRadius: BorderRadius.circular(6 * sf),
|
borderRadius: BorderRadius.circular(8 * sf),
|
||||||
child: IntrinsicHeight(
|
border: Border.all(color: borderColor, width: 1.5 * sf),
|
||||||
child: Row(
|
boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 4 * sf, offset: Offset(0, 2 * sf))],
|
||||||
mainAxisSize: MainAxisSize.min,
|
),
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
child: IntrinsicHeight(
|
||||||
children: [
|
child: Row(
|
||||||
Container(
|
mainAxisSize: MainAxisSize.min,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10 * sf),
|
crossAxisAlignment: CrossAxisAlignment.center, // Centra verticalmente a camisola com o texto
|
||||||
color: isFouledOut ? Colors.grey[700] : teamColor,
|
children: [
|
||||||
|
// ── APENAS A CAMISOLA (Sem quadrado de fundo) ──
|
||||||
|
SizedBox(
|
||||||
|
width: shirtSize,
|
||||||
|
height: shirtSize,
|
||||||
|
child: Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Text(number, style: TextStyle(color: Colors.white, fontSize: 18 * sf, fontWeight: FontWeight.bold)),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8 * sf, vertical: 4 * sf),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
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 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<Duration>(
|
|
||||||
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<Widget> content = [
|
|
||||||
Column(children: [_scoreBox(score, color, sf), SizedBox(height: 5 * sf), timeoutIndicators]),
|
|
||||||
SizedBox(width: 12 * sf),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: isOpp ? CrossAxisAlignment.start : CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 130 * sf,
|
|
||||||
child: Text(
|
|
||||||
name.toUpperCase(),
|
|
||||||
style: TextStyle(color: Colors.white, fontSize: 16 * sf, fontWeight: FontWeight.w900, letterSpacing: 1.0 * sf),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
textAlign: isOpp ? TextAlign.left : TextAlign.right,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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 BenchPopup extends StatelessWidget {
|
|
||||||
final PlacarController controller;
|
|
||||||
final bool isOpponent;
|
|
||||||
final double sf;
|
|
||||||
|
|
||||||
const BenchPopup({super.key, required this.controller, required this.isOpponent, required this.sf});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final bench = isOpponent ? controller.oppBench : controller.myBench;
|
|
||||||
final teamColor = isOpponent ? AppTheme.oppTeamRed : AppTheme.myTeamBlue;
|
|
||||||
final prefix = isOpponent ? "bench_opp_" : "bench_my_";
|
|
||||||
final teamName = isOpponent ? controller.opponentTeam : controller.myTeam;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
width: 280 * sf,
|
|
||||||
padding: EdgeInsets.all(12 * sf),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppTheme.placarDarkSurface.withOpacity(0.95),
|
|
||||||
borderRadius: BorderRadius.circular(16 * sf),
|
|
||||||
border: Border.all(color: teamColor, width: 2 * sf),
|
|
||||||
boxShadow: [BoxShadow(color: Colors.black54, blurRadius: 10 * sf, spreadRadius: 2 * sf)],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text("SUPLENTES: ${teamName.toUpperCase()}", style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12 * sf)),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
if (isOpponent) { controller.showOppBench = false; }
|
|
||||||
else { controller.showMyBench = false; }
|
|
||||||
controller.notifyListeners();
|
|
||||||
},
|
|
||||||
child: Icon(Icons.close, color: Colors.white70, size: 20 * sf),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Divider(color: Colors.white24, height: 16 * sf),
|
|
||||||
|
|
||||||
Wrap(
|
|
||||||
spacing: 12 * sf,
|
|
||||||
runSpacing: 12 * sf,
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
children: bench.map((playerId) {
|
|
||||||
final playerName = controller.playerNames[playerId] ?? "Erro";
|
|
||||||
final num = controller.playerNumbers[playerId] ?? "0";
|
|
||||||
final int fouls = controller.playerStats[playerId]?["fls"] ?? 0;
|
|
||||||
final bool isFouledOut = fouls >= 5;
|
|
||||||
|
|
||||||
String shortName = playerName.length > 8 ? "${playerName.substring(0, 7)}." : playerName;
|
|
||||||
|
|
||||||
Widget avatarUI = Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CustomPaint(
|
||||||
radius: 20 * sf,
|
size: Size(shirtSize, shirtSize),
|
||||||
backgroundColor: isFouledOut ? Colors.grey.shade800 : teamColor,
|
painter: ShirtPainter(
|
||||||
child: Text(num, style: TextStyle(color: isFouledOut ? Colors.red.shade300 : Colors.white, fontSize: 16 * sf, fontWeight: FontWeight.bold, decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none)),
|
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(height: 4 * sf),
|
|
||||||
Text(shortName, style: TextStyle(color: Colors.white, fontSize: 10 * sf, fontWeight: FontWeight.bold), overflow: TextOverflow.ellipsis),
|
|
||||||
],
|
],
|
||||||
);
|
),
|
||||||
|
),
|
||||||
if (isFouledOut) {
|
SizedBox(width: 8 * sf), // Espaço entre a camisola e as estatísticas
|
||||||
return GestureDetector(onTap: () => ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('🛑 $playerName expulso!'), backgroundColor: AppTheme.actionMiss)), child: avatarUI);
|
|
||||||
}
|
// ── Estatísticas ─────────────────────────────────────
|
||||||
|
Column(
|
||||||
return Draggable<String>(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
data: "$prefix$playerId",
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
feedback: Material(color: Colors.transparent, child: CircleAvatar(radius: 26 * sf, backgroundColor: teamColor, child: Text(num, style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 18 * sf)))),
|
mainAxisSize: MainAxisSize.min,
|
||||||
childWhenDragging: Opacity(opacity: 0.3, child: avatarUI),
|
children: [
|
||||||
child: avatarUI,
|
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),
|
||||||
}).toList(),
|
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)),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1286,6 +1173,9 @@ class HeatmapCourtPainter extends CustomPainter {
|
|||||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 5. CAIXA DE HISTÓRICO (PLAY-BY-PLAY)
|
||||||
|
// ============================================================================
|
||||||
class PlayByPlayDialog extends StatelessWidget {
|
class PlayByPlayDialog extends StatelessWidget {
|
||||||
final PlacarController controller;
|
final PlacarController controller;
|
||||||
const PlayByPlayDialog({super.key, required this.controller});
|
const PlayByPlayDialog({super.key, required this.controller});
|
||||||
@@ -1333,6 +1223,9 @@ class PlayByPlayDialog extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 6. ECRÃ DE BOX SCORE (ESTATÍSTICAS GERAIS)
|
||||||
|
// ============================================================================
|
||||||
class BoxScoreDialog extends StatelessWidget {
|
class BoxScoreDialog extends StatelessWidget {
|
||||||
final PlacarController controller;
|
final PlacarController controller;
|
||||||
final double sf;
|
final double sf;
|
||||||
@@ -1380,7 +1273,6 @@ class BoxScoreDialog extends StatelessWidget {
|
|||||||
indicatorColor: AppTheme.warningAmber,
|
indicatorColor: AppTheme.warningAmber,
|
||||||
labelColor: Colors.white,
|
labelColor: Colors.white,
|
||||||
unselectedLabelColor: Colors.white54,
|
unselectedLabelColor: Colors.white54,
|
||||||
// 👇 LETRAS DAS ABAS MAIORES
|
|
||||||
labelStyle: TextStyle(fontSize: 14 * sf, fontWeight: FontWeight.bold),
|
labelStyle: TextStyle(fontSize: 14 * sf, fontWeight: FontWeight.bold),
|
||||||
indicatorWeight: 3 * sf,
|
indicatorWeight: 3 * sf,
|
||||||
dividerColor: Colors.white10,
|
dividerColor: Colors.white10,
|
||||||
@@ -1394,7 +1286,6 @@ class BoxScoreDialog extends StatelessWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
color: Colors.black12,
|
color: Colors.black12,
|
||||||
// 👇 MÁGICA DE PERFORMANCE: Só a zona da tabela é que redesenha por segundo!
|
|
||||||
child: ValueListenableBuilder<Duration>(
|
child: ValueListenableBuilder<Duration>(
|
||||||
valueListenable: controller.durationNotifier,
|
valueListenable: controller.durationNotifier,
|
||||||
builder: (context, duration, _) {
|
builder: (context, duration, _) {
|
||||||
|
|||||||
@@ -257,6 +257,9 @@ class ShirtPainter extends CustomPainter {
|
|||||||
bool shouldRepaint(ShirtPainter old) => old.color != color || old.isFouledOut != isFouledOut;
|
bool shouldRepaint(ShirtPainter old) => old.color != color || old.isFouledOut != isFouledOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// CARD DO JOGADOR NO CAMPO
|
||||||
|
// ============================================================================
|
||||||
class PlayerCourtCard extends StatelessWidget {
|
class PlayerCourtCard extends StatelessWidget {
|
||||||
final PlacarController controller;
|
final PlacarController controller;
|
||||||
final String playerId;
|
final String playerId;
|
||||||
@@ -327,7 +330,7 @@ class PlayerCourtCard extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _playerCardUI(String number, String displayNameStr, Map<String, int> stats, Color teamColor, bool isSubbing, bool isActionHover, double sf) {
|
Widget _playerCardUI(String number, String displayNameStr, Map<String, int> stats, Color teamColor, bool isSubbing, bool isActionHover, double sf) {
|
||||||
bool isFouledOut = stats["fls"]! >= 5;
|
bool isFouledOut = stats["fls"]! >= 5;
|
||||||
Color bgColor = isFouledOut ? Colors.red.shade100 : Colors.white;
|
Color bgColor = isFouledOut ? Colors.red.shade100 : Colors.white;
|
||||||
Color borderColor = isFouledOut ? AppTheme.actionMiss : Colors.transparent;
|
Color borderColor = isFouledOut ? AppTheme.actionMiss : Colors.transparent;
|
||||||
@@ -339,77 +342,67 @@ class PlayerCourtCard extends StatelessWidget {
|
|||||||
String fgPercent = fga > 0 ? ((fgm / fga) * 100).toStringAsFixed(0) : "0";
|
String fgPercent = fga > 0 ? ((fgm / fga) * 100).toStringAsFixed(0) : "0";
|
||||||
String displayName = displayNameStr.length > 12 ? "${displayNameStr.substring(0, 10)}..." : displayNameStr;
|
String displayName = displayNameStr.length > 12 ? "${displayNameStr.substring(0, 10)}..." : displayNameStr;
|
||||||
|
|
||||||
// Tamanho da camisola
|
// Tamanho da camisola ajustado para ficar perfeito no cartão
|
||||||
final double shirtSize = 38 * sf;
|
final double shirtSize = 42 * sf;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
padding: EdgeInsets.symmetric(horizontal: 8 * sf, vertical: 6 * sf),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: bgColor,
|
color: bgColor,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8 * sf),
|
||||||
border: Border.all(color: borderColor, width: 1.5),
|
border: Border.all(color: borderColor, width: 1.5 * sf),
|
||||||
boxShadow: const [BoxShadow(color: Colors.black26, blurRadius: 4, offset: Offset(0, 2))],
|
boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 4 * sf, offset: Offset(0, 2 * sf))],
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: IntrinsicHeight(
|
||||||
borderRadius: BorderRadius.circular(6 * sf),
|
child: Row(
|
||||||
child: IntrinsicHeight(
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Row(
|
crossAxisAlignment: CrossAxisAlignment.center, // Centra verticalmente a camisola com o texto
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
// ── APENAS A CAMISOLA (Sem quadrado de fundo) ──
|
||||||
children: [
|
SizedBox(
|
||||||
// ── Camisola com número ──────────────────────────────
|
width: shirtSize,
|
||||||
Container(
|
height: shirtSize,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 6 * sf, vertical: 4 * sf),
|
child: Stack(
|
||||||
color: isFouledOut ? Colors.grey.shade200 : teamColor.withOpacity(0.12),
|
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: SizedBox(
|
children: [
|
||||||
width: shirtSize,
|
CustomPaint(
|
||||||
height: shirtSize * 1.1,
|
size: Size(shirtSize, shirtSize),
|
||||||
child: Stack(
|
painter: ShirtPainter(
|
||||||
alignment: Alignment.center,
|
color: teamColor,
|
||||||
children: [
|
isFouledOut: isFouledOut,
|
||||||
// Camisola desenhada
|
),
|
||||||
CustomPaint(
|
|
||||||
size: Size(shirtSize, shirtSize * 1.1),
|
|
||||||
painter: ShirtPainter(
|
|
||||||
color: isFouledOut ? Colors.grey.shade600 : teamColor,
|
|
||||||
isFouledOut: isFouledOut,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Número por cima
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(top: shirtSize * 0.15),
|
|
||||||
child: Text(
|
|
||||||
number,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: shirtSize * 0.42,
|
|
||||||
fontWeight: FontWeight.w900,
|
|
||||||
decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none,
|
|
||||||
shadows: const [Shadow(color: Colors.black45, blurRadius: 3)],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
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))],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
// ── Estatísticas ─────────────────────────────────────
|
),
|
||||||
Padding(
|
SizedBox(width: 8 * sf), // Espaço entre a camisola e as estatísticas
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8 * sf, vertical: 4 * sf),
|
|
||||||
child: Column(
|
// ── Estatísticas ─────────────────────────────────────
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
Text(displayName, style: TextStyle(fontSize: 14 * sf, fontWeight: FontWeight.bold, color: isFouledOut ? AppTheme.actionMiss : Colors.black87, decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none)),
|
mainAxisSize: MainAxisSize.min,
|
||||||
SizedBox(height: 1.5 * sf),
|
children: [
|
||||||
Text("${stats["pts"]} Pts | FG: $fgm/$fga ($fgPercent%)", style: TextStyle(fontSize: 10 * sf, color: isFouledOut ? AppTheme.actionMiss : Colors.grey[700], fontWeight: FontWeight.w600)),
|
Text(displayName, style: TextStyle(fontSize: 14 * sf, fontWeight: FontWeight.bold, color: isFouledOut ? AppTheme.actionMiss : Colors.black87, decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none)),
|
||||||
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)),
|
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)),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user