590 lines
22 KiB
Dart
590 lines
22 KiB
Dart
import 'dart:async';
|
|
import 'dart:math' as math;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
|
|
class ShotRecord {
|
|
final double relativeX;
|
|
final double relativeY;
|
|
final bool isMake;
|
|
final String playerName;
|
|
final String? zone;
|
|
final int? points;
|
|
|
|
ShotRecord({
|
|
required this.relativeX,
|
|
required this.relativeY,
|
|
required this.isMake,
|
|
required this.playerName,
|
|
this.zone,
|
|
this.points,
|
|
});
|
|
}
|
|
|
|
class PlacarController {
|
|
final String gameId;
|
|
final String myTeam;
|
|
final String opponentTeam;
|
|
final VoidCallback onUpdate;
|
|
|
|
PlacarController({
|
|
required this.gameId,
|
|
required this.myTeam,
|
|
required this.opponentTeam,
|
|
required this.onUpdate
|
|
});
|
|
|
|
bool isLoading = true;
|
|
bool isSaving = false;
|
|
|
|
bool gameWasAlreadyFinished = false;
|
|
|
|
int myScore = 0;
|
|
int opponentScore = 0;
|
|
int myFouls = 0;
|
|
int opponentFouls = 0;
|
|
int currentQuarter = 1;
|
|
int myTimeoutsUsed = 0;
|
|
int opponentTimeoutsUsed = 0;
|
|
|
|
String? myTeamDbId;
|
|
String? oppTeamDbId;
|
|
|
|
List<String> myCourt = [];
|
|
List<String> myBench = [];
|
|
List<String> oppCourt = [];
|
|
List<String> oppBench = [];
|
|
|
|
Map<String, String> playerNumbers = {};
|
|
Map<String, Map<String, int>> playerStats = {};
|
|
Map<String, String> playerDbIds = {};
|
|
|
|
bool showMyBench = false;
|
|
bool showOppBench = false;
|
|
|
|
bool isSelectingShotLocation = false;
|
|
String? pendingAction;
|
|
String? pendingPlayer;
|
|
List<ShotRecord> matchShots = [];
|
|
|
|
Duration duration = const Duration(minutes: 10);
|
|
Timer? timer;
|
|
bool isRunning = false;
|
|
|
|
bool isCalibrating = false;
|
|
double hoopBaseX = 0.088;
|
|
double arcRadius = 0.459;
|
|
double cornerY = 0.440;
|
|
|
|
Future<void> loadPlayers() async {
|
|
final supabase = Supabase.instance.client;
|
|
try {
|
|
await Future.delayed(const Duration(milliseconds: 1500));
|
|
|
|
myCourt.clear();
|
|
myBench.clear();
|
|
oppCourt.clear();
|
|
oppBench.clear();
|
|
playerStats.clear();
|
|
playerNumbers.clear();
|
|
playerDbIds.clear();
|
|
matchShots.clear(); // Limpa as bolas do último jogo
|
|
myFouls = 0;
|
|
opponentFouls = 0;
|
|
|
|
final gameResponse = await supabase.from('games').select().eq('id', gameId).single();
|
|
|
|
myScore = int.tryParse(gameResponse['my_score']?.toString() ?? '0') ?? 0;
|
|
opponentScore = int.tryParse(gameResponse['opponent_score']?.toString() ?? '0') ?? 0;
|
|
|
|
int totalSeconds = int.tryParse(gameResponse['remaining_seconds']?.toString() ?? '600') ?? 600;
|
|
duration = Duration(seconds: totalSeconds);
|
|
|
|
myTimeoutsUsed = int.tryParse(gameResponse['my_timeouts']?.toString() ?? '0') ?? 0;
|
|
opponentTimeoutsUsed = int.tryParse(gameResponse['opp_timeouts']?.toString() ?? '0') ?? 0;
|
|
currentQuarter = int.tryParse(gameResponse['current_quarter']?.toString() ?? '1') ?? 1;
|
|
|
|
gameWasAlreadyFinished = gameResponse['status'] == 'Terminado';
|
|
|
|
final teamsResponse = await supabase.from('teams').select('id, name').inFilter('name', [myTeam, opponentTeam]);
|
|
for (var t in teamsResponse) {
|
|
if (t['name'] == myTeam) myTeamDbId = t['id'];
|
|
if (t['name'] == opponentTeam) oppTeamDbId = t['id'];
|
|
}
|
|
|
|
List<dynamic> myPlayers = myTeamDbId != null ? await supabase.from('members').select().eq('team_id', myTeamDbId!).eq('type', 'Jogador') : [];
|
|
List<dynamic> oppPlayers = oppTeamDbId != null ? await supabase.from('members').select().eq('team_id', oppTeamDbId!).eq('type', 'Jogador') : [];
|
|
|
|
final statsResponse = await supabase.from('player_stats').select().eq('game_id', gameId);
|
|
final Map<String, dynamic> savedStats = {
|
|
for (var item in statsResponse) item['member_id'].toString(): item
|
|
};
|
|
|
|
for (int i = 0; i < myPlayers.length; i++) {
|
|
String dbId = myPlayers[i]['id'].toString();
|
|
String name = myPlayers[i]['name'].toString();
|
|
|
|
_registerPlayer(name: name, number: myPlayers[i]['number']?.toString() ?? "0", dbId: dbId, isMyTeam: true, isCourt: i < 5);
|
|
|
|
if (savedStats.containsKey(dbId)) {
|
|
var s = savedStats[dbId];
|
|
playerStats[name] = {
|
|
"pts": s['pts'] ?? 0, "rbs": s['rbs'] ?? 0, "ast": s['ast'] ?? 0,
|
|
"stl": s['stl'] ?? 0, "tov": s['tov'] ?? 0, "blk": s['blk'] ?? 0,
|
|
"fls": s['fls'] ?? 0, "fgm": s['fgm'] ?? 0, "fga": s['fga'] ?? 0,
|
|
"ftm": s['ftm'] ?? 0, "fta": s['fta'] ?? 0, "orb": s['orb'] ?? 0, "drb": s['drb'] ?? 0,
|
|
};
|
|
myFouls += (s['fls'] as int? ?? 0);
|
|
}
|
|
}
|
|
_padTeam(myCourt, myBench, "Jogador", isMyTeam: true);
|
|
|
|
for (int i = 0; i < oppPlayers.length; i++) {
|
|
String dbId = oppPlayers[i]['id'].toString();
|
|
String name = oppPlayers[i]['name'].toString();
|
|
|
|
_registerPlayer(name: name, number: oppPlayers[i]['number']?.toString() ?? "0", dbId: dbId, isMyTeam: false, isCourt: i < 5);
|
|
|
|
if (savedStats.containsKey(dbId)) {
|
|
var s = savedStats[dbId];
|
|
playerStats[name] = {
|
|
"pts": s['pts'] ?? 0, "rbs": s['rbs'] ?? 0, "ast": s['ast'] ?? 0,
|
|
"stl": s['stl'] ?? 0, "tov": s['tov'] ?? 0, "blk": s['blk'] ?? 0,
|
|
"fls": s['fls'] ?? 0, "fgm": s['fgm'] ?? 0, "fga": s['fga'] ?? 0,
|
|
"ftm": s['ftm'] ?? 0, "fta": s['fta'] ?? 0, "orb": s['orb'] ?? 0, "drb": s['drb'] ?? 0,
|
|
};
|
|
opponentFouls += (s['fls'] as int? ?? 0);
|
|
}
|
|
}
|
|
_padTeam(oppCourt, oppBench, "Adversário", isMyTeam: false);
|
|
|
|
// 👇 CARREGA AS BOLINHAS ANTIGAS (MAPA DE CALOR DO JOGO ATUAL) 👇
|
|
final shotsResponse = await supabase.from('shot_locations').select().eq('game_id', gameId);
|
|
for (var shotData in shotsResponse) {
|
|
matchShots.add(ShotRecord(
|
|
relativeX: double.parse(shotData['relative_x'].toString()),
|
|
relativeY: double.parse(shotData['relative_y'].toString()),
|
|
isMake: shotData['is_make'] == true,
|
|
playerName: shotData['player_name'].toString(),
|
|
zone: shotData['zone']?.toString(),
|
|
points: shotData['points'] != null ? int.parse(shotData['points'].toString()) : null,
|
|
));
|
|
}
|
|
|
|
isLoading = false;
|
|
onUpdate();
|
|
} catch (e) {
|
|
debugPrint("Erro ao retomar jogo: $e");
|
|
_padTeam(myCourt, myBench, "Falha", isMyTeam: true);
|
|
_padTeam(oppCourt, oppBench, "Falha Opp", isMyTeam: false);
|
|
isLoading = false;
|
|
onUpdate();
|
|
}
|
|
}
|
|
|
|
void _registerPlayer({required String name, required String number, String? dbId, required bool isMyTeam, required bool isCourt}) {
|
|
if (playerNumbers.containsKey(name)) name = "$name (Opp)";
|
|
playerNumbers[name] = number;
|
|
if (dbId != null) playerDbIds[name] = dbId;
|
|
|
|
playerStats[name] = {
|
|
"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0,
|
|
"fls": 0, "fgm": 0, "fga": 0, "ftm": 0, "fta": 0, "orb": 0, "drb": 0
|
|
};
|
|
|
|
if (isMyTeam) {
|
|
if (isCourt) myCourt.add(name); else myBench.add(name);
|
|
} else {
|
|
if (isCourt) oppCourt.add(name); else oppBench.add(name);
|
|
}
|
|
}
|
|
|
|
void _padTeam(List<String> court, List<String> bench, String prefix, {required bool isMyTeam}) {
|
|
while (court.length < 5) {
|
|
_registerPlayer(name: "Sem $prefix ${court.length + 1}", number: "0", dbId: null, isMyTeam: isMyTeam, isCourt: true);
|
|
}
|
|
}
|
|
|
|
void toggleTimer(BuildContext context) {
|
|
if (isRunning) {
|
|
timer?.cancel();
|
|
} else {
|
|
timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
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;
|
|
myTimeoutsUsed = 0;
|
|
opponentTimeoutsUsed = 0;
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Período $currentQuarter iniciado. Faltas e Timeouts resetados!'), backgroundColor: Colors.blue));
|
|
} else {
|
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('FIM DO JOGO! Clica em Guardar para fechar a partida.'), backgroundColor: Colors.red));
|
|
}
|
|
}
|
|
onUpdate();
|
|
});
|
|
}
|
|
isRunning = !isRunning;
|
|
onUpdate();
|
|
}
|
|
|
|
void useTimeout(bool isOpponent) {
|
|
if (isOpponent) {
|
|
if (opponentTimeoutsUsed < 3) opponentTimeoutsUsed++;
|
|
} else {
|
|
if (myTimeoutsUsed < 3) myTimeoutsUsed++;
|
|
}
|
|
isRunning = false;
|
|
timer?.cancel();
|
|
onUpdate();
|
|
}
|
|
|
|
String formatTime() => "${duration.inMinutes.toString().padLeft(2, '0')}:${duration.inSeconds.remainder(60).toString().padLeft(2, '0')}";
|
|
|
|
void handleActionDrag(BuildContext context, String action, String playerData) {
|
|
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;
|
|
}
|
|
|
|
if (action == "add_pts_2" || action == "add_pts_3" || action == "miss_2" || action == "miss_3") {
|
|
pendingAction = action;
|
|
pendingPlayer = playerData;
|
|
isSelectingShotLocation = true;
|
|
} else {
|
|
commitStat(action, playerData);
|
|
}
|
|
onUpdate();
|
|
}
|
|
|
|
void handleSubbing(BuildContext context, String action, String courtPlayerName, bool isOpponent) {
|
|
if (action.startsWith("bench_my_") && !isOpponent) {
|
|
String benchPlayer = action.replaceAll("bench_my_", "");
|
|
if (playerStats[benchPlayer]!["fls"]! >= 5) return;
|
|
int courtIndex = myCourt.indexOf(courtPlayerName);
|
|
int benchIndex = myBench.indexOf(benchPlayer);
|
|
myCourt[courtIndex] = benchPlayer;
|
|
myBench[benchIndex] = courtPlayerName;
|
|
showMyBench = false;
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Sai $courtPlayerName, Entra $benchPlayer')));
|
|
}
|
|
if (action.startsWith("bench_opp_") && isOpponent) {
|
|
String benchPlayer = action.replaceAll("bench_opp_", "");
|
|
if (playerStats[benchPlayer]!["fls"]! >= 5) return;
|
|
int courtIndex = oppCourt.indexOf(courtPlayerName);
|
|
int benchIndex = oppBench.indexOf(benchPlayer);
|
|
oppCourt[courtIndex] = benchPlayer;
|
|
oppBench[benchIndex] = courtPlayerName;
|
|
showOppBench = false;
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Sai $courtPlayerName, Entra $benchPlayer')));
|
|
}
|
|
onUpdate();
|
|
}
|
|
|
|
// =========================================================================
|
|
// 👇 REGISTA PONTOS VINDO DO POP-UP AMARELO (E MARCA A BOLINHA)
|
|
// =========================================================================
|
|
void registerShotFromPopup(BuildContext context, String action, String targetPlayer, String zone, int points, double relativeX, double relativeY) {
|
|
// 💡 AVISO AMIGÁVEL REMOVIDO. Agora podes marcar pontos mesmo com o tempo parado!
|
|
|
|
String name = targetPlayer.replaceAll("player_my_", "").replaceAll("player_opp_", "");
|
|
bool isMyTeam = targetPlayer.startsWith("player_my_");
|
|
bool isMake = action.startsWith("add_");
|
|
|
|
// 1. ATUALIZA A ESTATÍSTICA DO JOGADOR
|
|
if (playerStats.containsKey(name)) {
|
|
playerStats[name]!['fga'] = playerStats[name]!['fga']! + 1;
|
|
|
|
if (isMake) {
|
|
playerStats[name]!['fgm'] = playerStats[name]!['fgm']! + 1;
|
|
playerStats[name]!['pts'] = playerStats[name]!['pts']! + points;
|
|
|
|
// 2. ATUALIZA O PLACAR DA EQUIPA
|
|
if (isMyTeam) {
|
|
myScore += points;
|
|
} else {
|
|
opponentScore += points;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. CRIA A BOLINHA PARA APARECER NO CAMPO
|
|
matchShots.add(ShotRecord(
|
|
relativeX: relativeX,
|
|
relativeY: relativeY,
|
|
isMake: isMake,
|
|
playerName: name,
|
|
zone: zone,
|
|
points: points,
|
|
));
|
|
|
|
// 4. MANDA UMA MENSAGEM NO ECRÃ
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(isMake ? '🔥 $name MARCOU de $zone!' : '❌ $name FALHOU de $zone!'),
|
|
backgroundColor: isMake ? Colors.green : Colors.red,
|
|
duration: const Duration(seconds: 2),
|
|
)
|
|
);
|
|
|
|
// 5. ATUALIZA O ECRÃ
|
|
onUpdate();
|
|
}
|
|
|
|
// MANTIDO PARA CASO USES A MARCAÇÃO CLÁSSICA DIRETAMENTE NO CAMPO ESCURO
|
|
void registerShotLocation(BuildContext context, Offset position, Size size) {
|
|
if (pendingAction == null || pendingPlayer == null) return;
|
|
|
|
bool is3Pt = pendingAction!.contains("_3");
|
|
bool is2Pt = pendingAction!.contains("_2");
|
|
|
|
if (is3Pt || is2Pt) {
|
|
bool isValid = _validateShotZone(position, size, is3Pt);
|
|
if (!isValid) return;
|
|
}
|
|
|
|
bool isMake = pendingAction!.startsWith("add_pts_");
|
|
double relX = position.dx / size.width;
|
|
double relY = position.dy / size.height;
|
|
String name = pendingPlayer!.replaceAll("player_my_", "").replaceAll("player_opp_", "");
|
|
|
|
matchShots.add(ShotRecord(
|
|
relativeX: relX,
|
|
relativeY: relY,
|
|
isMake: isMake,
|
|
playerName: name
|
|
));
|
|
|
|
commitStat(pendingAction!, pendingPlayer!);
|
|
|
|
isSelectingShotLocation = false;
|
|
pendingAction = null;
|
|
pendingPlayer = null;
|
|
onUpdate();
|
|
}
|
|
|
|
bool _validateShotZone(Offset position, Size size, bool is3Pt) {
|
|
double relX = position.dx / size.width;
|
|
double relY = position.dy / size.height;
|
|
|
|
bool isLeftHalf = relX < 0.5;
|
|
double hoopX = isLeftHalf ? hoopBaseX : (1.0 - hoopBaseX);
|
|
double hoopY = 0.50;
|
|
|
|
double aspectRatio = size.width / size.height;
|
|
double distFromCenterY = (relY - hoopY).abs();
|
|
|
|
bool isInside2Pts;
|
|
|
|
if (distFromCenterY > cornerY) {
|
|
double distToBaseline = isLeftHalf ? relX : (1.0 - relX);
|
|
isInside2Pts = distToBaseline <= hoopBaseX;
|
|
} else {
|
|
double dx = (relX - hoopX) * aspectRatio;
|
|
double dy = (relY - hoopY);
|
|
double distanceToHoop = math.sqrt((dx * dx) + (dy * dy));
|
|
isInside2Pts = distanceToHoop < arcRadius;
|
|
}
|
|
|
|
if (is3Pt) return !isInside2Pts;
|
|
return isInside2Pts;
|
|
}
|
|
|
|
void cancelShotLocation() {
|
|
isSelectingShotLocation = false; pendingAction = null; pendingPlayer = null; onUpdate();
|
|
}
|
|
|
|
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]!;
|
|
|
|
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; }
|
|
if (pts == 1) { stats["ftm"] = stats["ftm"]! + 1; stats["fta"] = stats["fta"]! + 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;
|
|
}
|
|
if (pts == 1) {
|
|
if (stats["ftm"]! > 0) stats["ftm"] = stats["ftm"]! - 1;
|
|
if (stats["fta"]! > 0) stats["fta"] = stats["fta"]! - 1;
|
|
}
|
|
}
|
|
else if (action == "miss_1") { stats["fta"] = stats["fta"]! + 1; }
|
|
else if (action == "miss_2" || action == "miss_3") { stats["fga"] = stats["fga"]! + 1; }
|
|
else if (action == "add_orb") { stats["orb"] = stats["orb"]! + 1; stats["rbs"] = stats["rbs"]! + 1; }
|
|
else if (action == "add_drb") { stats["drb"] = stats["drb"]! + 1; 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--; }
|
|
}
|
|
}
|
|
|
|
Future<void> saveGameStats(BuildContext context) async {
|
|
final supabase = Supabase.instance.client;
|
|
isSaving = true;
|
|
onUpdate();
|
|
|
|
try {
|
|
bool isGameFinishedNow = currentQuarter >= 4 && duration.inSeconds == 0;
|
|
String newStatus = isGameFinishedNow ? 'Terminado' : 'Pausado';
|
|
|
|
String topPtsName = '---'; int maxPts = -1;
|
|
String topAstName = '---'; int maxAst = -1;
|
|
String topRbsName = '---'; int maxRbs = -1;
|
|
String topDefName = '---'; int maxDef = -1;
|
|
String mvpName = '---'; int maxMvpScore = -1;
|
|
|
|
playerStats.forEach((playerName, stats) {
|
|
int pts = stats['pts'] ?? 0;
|
|
int ast = stats['ast'] ?? 0;
|
|
int rbs = stats['rbs'] ?? 0;
|
|
int stl = stats['stl'] ?? 0;
|
|
int blk = stats['blk'] ?? 0;
|
|
|
|
int defScore = stl + blk;
|
|
int mvpScore = pts + ast + rbs + defScore;
|
|
|
|
if (pts > maxPts && pts > 0) { maxPts = pts; topPtsName = '$playerName ($pts)'; }
|
|
if (ast > maxAst && ast > 0) { maxAst = ast; topAstName = '$playerName ($ast)'; }
|
|
if (rbs > maxRbs && rbs > 0) { maxRbs = rbs; topRbsName = '$playerName ($rbs)'; }
|
|
if (defScore > maxDef && defScore > 0) { maxDef = defScore; topDefName = '$playerName ($defScore)'; }
|
|
if (mvpScore > maxMvpScore && mvpScore > 0) { maxMvpScore = mvpScore; mvpName = playerName; }
|
|
});
|
|
|
|
await supabase.from('games').update({
|
|
'my_score': myScore,
|
|
'opponent_score': opponentScore,
|
|
'remaining_seconds': duration.inSeconds,
|
|
'my_timeouts': myTimeoutsUsed,
|
|
'opp_timeouts': opponentTimeoutsUsed,
|
|
'current_quarter': currentQuarter,
|
|
'status': newStatus,
|
|
'top_pts_name': topPtsName,
|
|
'top_ast_name': topAstName,
|
|
'top_rbs_name': topRbsName,
|
|
'top_def_name': topDefName,
|
|
'mvp_name': mvpName,
|
|
}).eq('id', gameId);
|
|
|
|
if (isGameFinishedNow && !gameWasAlreadyFinished && myTeamDbId != null && oppTeamDbId != null) {
|
|
|
|
final teamsData = await supabase.from('teams').select('id, wins, losses, draws').inFilter('id', [myTeamDbId, oppTeamDbId]);
|
|
|
|
Map<String, dynamic> myTeamUpdate = {};
|
|
Map<String, dynamic> oppTeamUpdate = {};
|
|
|
|
for(var t in teamsData) {
|
|
if(t['id'].toString() == myTeamDbId) myTeamUpdate = Map.from(t);
|
|
if(t['id'].toString() == oppTeamDbId) oppTeamUpdate = Map.from(t);
|
|
}
|
|
|
|
if (myScore > opponentScore) {
|
|
myTeamUpdate['wins'] = (myTeamUpdate['wins'] ?? 0) + 1;
|
|
oppTeamUpdate['losses'] = (oppTeamUpdate['losses'] ?? 0) + 1;
|
|
} else if (myScore < opponentScore) {
|
|
myTeamUpdate['losses'] = (myTeamUpdate['losses'] ?? 0) + 1;
|
|
oppTeamUpdate['wins'] = (oppTeamUpdate['wins'] ?? 0) + 1;
|
|
} else {
|
|
myTeamUpdate['draws'] = (myTeamUpdate['draws'] ?? 0) + 1;
|
|
oppTeamUpdate['draws'] = (oppTeamUpdate['draws'] ?? 0) + 1;
|
|
}
|
|
|
|
await supabase.from('teams').update({
|
|
'wins': myTeamUpdate['wins'], 'losses': myTeamUpdate['losses'], 'draws': myTeamUpdate['draws']
|
|
}).eq('id', myTeamDbId!);
|
|
|
|
await supabase.from('teams').update({
|
|
'wins': oppTeamUpdate['wins'], 'losses': oppTeamUpdate['losses'], 'draws': oppTeamUpdate['draws']
|
|
}).eq('id', oppTeamDbId!);
|
|
|
|
gameWasAlreadyFinished = true;
|
|
}
|
|
|
|
List<Map<String, dynamic>> batchStats = [];
|
|
playerStats.forEach((playerName, stats) {
|
|
String? memberDbId = playerDbIds[playerName];
|
|
if (memberDbId != null && stats.values.any((val) => val > 0)) {
|
|
bool isMyTeamPlayer = myCourt.contains(playerName) || myBench.contains(playerName);
|
|
batchStats.add({
|
|
'game_id': gameId, 'member_id': memberDbId, 'team_id': isMyTeamPlayer ? myTeamDbId! : oppTeamDbId!,
|
|
'pts': stats['pts'], 'rbs': stats['rbs'], 'ast': stats['ast'], 'stl': stats['stl'], 'blk': stats['blk'], 'tov': stats['tov'], 'fls': stats['fls'], 'fgm': stats['fgm'], 'fga': stats['fga'], 'ftm': stats['ftm'], 'fta': stats['fta'], 'orb': stats['orb'], 'drb': stats['drb'],
|
|
});
|
|
}
|
|
});
|
|
|
|
await supabase.from('player_stats').delete().eq('game_id', gameId);
|
|
if (batchStats.isNotEmpty) {
|
|
await supabase.from('player_stats').insert(batchStats);
|
|
}
|
|
|
|
// 👇 6. GUARDA AS BOLINHAS (MAPA DE CALOR) NO SUPABASE 👇
|
|
List<Map<String, dynamic>> batchShots = [];
|
|
for (var shot in matchShots) {
|
|
String? memberDbId = playerDbIds[shot.playerName];
|
|
if (memberDbId != null) {
|
|
batchShots.add({
|
|
'game_id': gameId,
|
|
'member_id': memberDbId,
|
|
'player_name': shot.playerName,
|
|
'relative_x': shot.relativeX,
|
|
'relative_y': shot.relativeY,
|
|
'is_make': shot.isMake,
|
|
'zone': shot.zone ?? 'Desconhecida',
|
|
'points': shot.points ?? (shot.isMake ? 2 : 0),
|
|
});
|
|
}
|
|
}
|
|
|
|
// Apaga os antigos (para não duplicar) e guarda os novos!
|
|
await supabase.from('shot_locations').delete().eq('game_id', gameId);
|
|
if (batchShots.isNotEmpty) {
|
|
await supabase.from('shot_locations').insert(batchShots);
|
|
}
|
|
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Estatísticas, Mapa de Calor e Resultados guardados com Sucesso!'), backgroundColor: Colors.green));
|
|
}
|
|
|
|
} catch (e) {
|
|
debugPrint("Erro ao gravar estatísticas: $e");
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Erro ao guardar: $e'), backgroundColor: Colors.red));
|
|
}
|
|
} finally {
|
|
isSaving = false;
|
|
onUpdate();
|
|
}
|
|
}
|
|
|
|
void dispose() {
|
|
timer?.cancel();
|
|
}
|
|
} |