mapa de calor ?
This commit is contained in:
@@ -3,9 +3,17 @@ import 'package:flutter/material.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class ShotRecord {
|
||||
final Offset position;
|
||||
final double relativeX;
|
||||
final double relativeY;
|
||||
final bool isMake;
|
||||
ShotRecord(this.position, this.isMake);
|
||||
final String playerName; // Bónus: Agora guardamos quem foi o jogador!
|
||||
|
||||
ShotRecord({
|
||||
required this.relativeX,
|
||||
required this.relativeY,
|
||||
required this.isMake,
|
||||
required this.playerName
|
||||
});
|
||||
}
|
||||
|
||||
class PlacarController {
|
||||
@@ -261,7 +269,7 @@ class PlacarController {
|
||||
onUpdate();
|
||||
}
|
||||
|
||||
void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
if (pendingAction == null || pendingPlayer == null) return;
|
||||
bool is3Pt = pendingAction!.contains("_3");
|
||||
bool is2Pt = pendingAction!.contains("_2");
|
||||
@@ -269,13 +277,28 @@ class PlacarController {
|
||||
if (is3Pt || is2Pt) {
|
||||
bool isValid = _validateShotZone(position, size, is3Pt);
|
||||
if (!isValid) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('🛑 Local de lançamento incompatível com a pontuação.'), backgroundColor: Colors.red, duration: Duration(seconds: 2)));
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('🛑 Local incompatível com a pontuação.'), backgroundColor: Colors.red, duration: Duration(seconds: 2)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool isMake = pendingAction!.startsWith("add_pts_");
|
||||
matchShots.add(ShotRecord(position, isMake));
|
||||
|
||||
// 👇 A MÁGICA DAS COORDENADAS RELATIVAS (0.0 a 1.0) 👇
|
||||
double relX = position.dx / size.width;
|
||||
double relY = position.dy / size.height;
|
||||
|
||||
// Extrai só o nome do jogador
|
||||
String name = pendingPlayer!.replaceAll("player_my_", "").replaceAll("player_opp_", "");
|
||||
|
||||
// Guarda na lista!
|
||||
matchShots.add(ShotRecord(
|
||||
relativeX: relX,
|
||||
relativeY: relY,
|
||||
isMake: isMake,
|
||||
playerName: name
|
||||
));
|
||||
|
||||
commitStat(pendingAction!, pendingPlayer!);
|
||||
|
||||
isSelectingShotLocation = false;
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class TeamController {
|
||||
// Instância do cliente Supabase
|
||||
final _supabase = Supabase.instance.client;
|
||||
|
||||
// 1. STREAM (Realtime)
|
||||
Stream<List<Map<String, dynamic>>> get teamsStream {
|
||||
return _supabase
|
||||
// 1. Variável fixa para guardar o Stream principal
|
||||
late final Stream<List<Map<String, dynamic>>> teamsStream;
|
||||
|
||||
// 2. Dicionário (Cache) para não recriar Streams de contagem repetidos
|
||||
final Map<String, Stream<int>> _playerCountStreams = {};
|
||||
|
||||
TeamController() {
|
||||
// INICIALIZAÇÃO: O stream é criado APENAS UMA VEZ quando abres a página!
|
||||
teamsStream = _supabase
|
||||
.from('teams')
|
||||
.stream(primaryKey: ['id'])
|
||||
.order('name', ascending: true)
|
||||
.map((data) => List<Map<String, dynamic>>.from(data));
|
||||
}
|
||||
|
||||
// 2. CRIAR
|
||||
// CRIAR
|
||||
Future<void> createTeam(String name, String season, String? imageUrl) async {
|
||||
try {
|
||||
await _supabase.from('teams').insert({
|
||||
@@ -28,35 +33,50 @@ class TeamController {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. ELIMINAR
|
||||
// ELIMINAR
|
||||
Future<void> deleteTeam(String id) async {
|
||||
try {
|
||||
await _supabase.from('teams').delete().eq('id', id);
|
||||
// Limpa o cache deste teamId se a equipa for apagada
|
||||
_playerCountStreams.remove(id);
|
||||
} catch (e) {
|
||||
print("❌ Erro ao eliminar: $e");
|
||||
}
|
||||
}
|
||||
|
||||
// 4. FAVORITAR
|
||||
// FAVORITAR
|
||||
Future<void> toggleFavorite(String teamId, bool currentStatus) async {
|
||||
try {
|
||||
await _supabase
|
||||
.from('teams')
|
||||
.update({'is_favorite': !currentStatus}) // Inverte o valor
|
||||
.update({'is_favorite': !currentStatus})
|
||||
.eq('id', teamId);
|
||||
} catch (e) {
|
||||
print("❌ Erro ao favoritar: $e");
|
||||
}
|
||||
}
|
||||
|
||||
// 5. CONTAR JOGADORES (AGORA EM TEMPO REAL COM STREAM!)
|
||||
// CONTAR JOGADORES (AGORA COM CACHE DE MEMÓRIA!)
|
||||
Stream<int> getPlayerCountStream(String teamId) {
|
||||
return _supabase
|
||||
// Se já criámos um "Tubo de ligação" para esta equipa, REUTILIZA-O!
|
||||
if (_playerCountStreams.containsKey(teamId)) {
|
||||
return _playerCountStreams[teamId]!;
|
||||
}
|
||||
|
||||
// Se é a primeira vez que pede esta equipa, cria a ligação e guarda na memória
|
||||
final newStream = _supabase
|
||||
.from('members')
|
||||
.stream(primaryKey: ['id'])
|
||||
.eq('team_id', teamId)
|
||||
.map((data) => data.length); // O tamanho da lista é o número de jogadores
|
||||
.map((data) => data.length);
|
||||
|
||||
_playerCountStreams[teamId] = newStream; // Guarda no dicionário
|
||||
return newStream;
|
||||
}
|
||||
|
||||
void dispose() {}
|
||||
// LIMPEZA FINAL QUANDO SAÍMOS DA PÁGINA
|
||||
void dispose() {
|
||||
// Limpamos o dicionário de streams para libertar memória RAM
|
||||
_playerCountStreams.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user