Conflitos resolvidos: mantido o código local
This commit is contained in:
@@ -4,34 +4,25 @@ import '../models/game_model.dart';
|
||||
class GameController {
|
||||
final _supabase = Supabase.instance.client;
|
||||
|
||||
// 1. LER JOGOS (Com Filtros Opcionais)
|
||||
Stream<List<Game>> getFilteredGames({String? teamFilter, String? seasonFilter}) {
|
||||
// 1. LER JOGOS (Stream em Tempo Real)
|
||||
Stream<List<Game>> get gamesStream {
|
||||
return _supabase
|
||||
.from('games')
|
||||
.from('games') // 1. Fica à escuta da tabela original (Garante o Tempo Real!)
|
||||
.stream(primaryKey: ['id'])
|
||||
.asyncMap((event) async {
|
||||
|
||||
// 👇 A CORREÇÃO ESTÁ AQUI: Lê diretamente da tabela 'games'
|
||||
var query = _supabase.from('games').select();
|
||||
|
||||
// Aplica o filtro de Temporada
|
||||
if (seasonFilter != null && seasonFilter.isNotEmpty && seasonFilter != 'Todas') {
|
||||
query = query.eq('season', seasonFilter);
|
||||
}
|
||||
|
||||
// Aplica o filtro de Equipa (Procura em casa ou fora)
|
||||
if (teamFilter != null && teamFilter.isNotEmpty && teamFilter != 'Todas') {
|
||||
query = query.or('my_team.eq.$teamFilter,opponent_team.eq.$teamFilter');
|
||||
}
|
||||
|
||||
// Executa a query com a ordenação por data
|
||||
final viewData = await query.order('game_date', ascending: false);
|
||||
// 2. Sempre que a tabela 'games' mudar (novo jogo, alteração de resultado),
|
||||
// vamos buscar os dados já misturados com as imagens à nossa View.
|
||||
final viewData = await _supabase
|
||||
.from('games_with_logos')
|
||||
.select()
|
||||
.order('game_date', ascending: false);
|
||||
|
||||
// 3. Convertemos para a nossa lista de objetos Game
|
||||
return viewData.map((json) => Game.fromMap(json)).toList();
|
||||
});
|
||||
}
|
||||
|
||||
// 2. CRIAR JOGO
|
||||
// Retorna o ID do jogo criado para podermos navegar para o placar
|
||||
Future<String?> createGame(String myTeam, String opponent, String season) async {
|
||||
try {
|
||||
final response = await _supabase.from('games').insert({
|
||||
@@ -40,16 +31,18 @@ class GameController {
|
||||
'season': season,
|
||||
'my_score': 0,
|
||||
'opponent_score': 0,
|
||||
'status': 'Decorrer',
|
||||
'status': 'Decorrer', // Começa como "Decorrer"
|
||||
'game_date': DateTime.now().toIso8601String(),
|
||||
}).select().single();
|
||||
}).select().single(); // .select().single() retorna o objeto criado
|
||||
|
||||
return response['id'];
|
||||
return response['id']; // Retorna o UUID gerado pelo Supabase
|
||||
} catch (e) {
|
||||
print("Erro ao criar jogo: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {}
|
||||
void dispose() {
|
||||
// Não é necessário fechar streams do Supabase manualmente aqui
|
||||
}
|
||||
}
|
||||
158
lib/controllers/stats_controller.dart
Normal file
158
lib/controllers/stats_controller.dart
Normal file
@@ -0,0 +1,158 @@
|
||||
/*import 'package:flutter/material.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import '../models/person_model.dart';
|
||||
|
||||
class StatsController {
|
||||
final SupabaseClient _supabase = Supabase.instance.client;
|
||||
|
||||
// 1. LER
|
||||
Stream<List<Person>> getMembers(String teamId) {
|
||||
return _supabase
|
||||
.from('members')
|
||||
.stream(primaryKey: ['id'])
|
||||
.eq('team_id', teamId)
|
||||
.order('name', ascending: true)
|
||||
.map((data) => data.map((json) => Person.fromMap(json)).toList());
|
||||
}
|
||||
|
||||
// 2. APAGAR
|
||||
Future<void> deletePerson(String personId) async {
|
||||
try {
|
||||
await _supabase.from('members').delete().eq('id', personId);
|
||||
} catch (e) {
|
||||
debugPrint("Erro ao eliminar: $e");
|
||||
}
|
||||
}
|
||||
|
||||
// 3. DIÁLOGOS
|
||||
void showAddPersonDialog(BuildContext context, String teamId) {
|
||||
_showForm(context, teamId: teamId);
|
||||
}
|
||||
|
||||
void showEditPersonDialog(BuildContext context, String teamId, Person person) {
|
||||
_showForm(context, teamId: teamId, person: person);
|
||||
}
|
||||
|
||||
// --- O POPUP ESTÁ AQUI ---
|
||||
void _showForm(BuildContext context, {required String teamId, Person? person}) {
|
||||
final isEdit = person != null;
|
||||
final nameCtrl = TextEditingController(text: person?.name ?? '');
|
||||
final numCtrl = TextEditingController(text: person?.number ?? '');
|
||||
|
||||
// Define o valor inicial
|
||||
String selectedType = person?.type ?? 'Jogador';
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => StatefulBuilder(
|
||||
builder: (ctx, setState) => AlertDialog(
|
||||
title: Text(isEdit ? "Editar" : "Adicionar"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// NOME
|
||||
TextField(
|
||||
controller: nameCtrl,
|
||||
decoration: const InputDecoration(labelText: "Nome"),
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// FUNÇÃO
|
||||
DropdownButtonFormField<String>(
|
||||
value: selectedType,
|
||||
decoration: const InputDecoration(labelText: "Função"),
|
||||
items: ["Jogador", "Treinador"]
|
||||
.map((e) => DropdownMenuItem(value: e, child: Text(e)))
|
||||
.toList(),
|
||||
onChanged: (v) {
|
||||
if (v != null) setState(() => selectedType = v);
|
||||
},
|
||||
),
|
||||
|
||||
// NÚMERO (Só aparece se for Jogador)
|
||||
if (selectedType == "Jogador") ...[
|
||||
const SizedBox(height: 10),
|
||||
TextField(
|
||||
controller: numCtrl,
|
||||
decoration: const InputDecoration(labelText: "Número da Camisola"),
|
||||
keyboardType: TextInputType.text, // Aceita texto para evitar erros
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: const Text("Cancelar")
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFF00C853)),
|
||||
onPressed: () async {
|
||||
print("--- 1. CLICOU EM GUARDAR ---");
|
||||
|
||||
// Validação Simples
|
||||
if (nameCtrl.text.trim().isEmpty) {
|
||||
print("ERRO: Nome vazio");
|
||||
return;
|
||||
}
|
||||
|
||||
// Lógica do Número:
|
||||
// Se for Treinador -> envia NULL
|
||||
// Se for Jogador e estiver vazio -> envia NULL
|
||||
// Se tiver texto -> envia o Texto
|
||||
String? numeroFinal;
|
||||
if (selectedType == "Treinador") {
|
||||
numeroFinal = null;
|
||||
} else {
|
||||
numeroFinal = numCtrl.text.trim().isEmpty ? null : numCtrl.text.trim();
|
||||
}
|
||||
|
||||
print("--- 2. DADOS A ENVIAR ---");
|
||||
print("Nome: ${nameCtrl.text}");
|
||||
print("Tipo: $selectedType");
|
||||
print("Número: $numeroFinal");
|
||||
|
||||
try {
|
||||
if (isEdit) {
|
||||
await _supabase.from('members').update({
|
||||
'name': nameCtrl.text.trim(),
|
||||
'type': selectedType,
|
||||
'number': numeroFinal,
|
||||
}).eq('id', person!.id);
|
||||
} else {
|
||||
await _supabase.from('members').insert({
|
||||
'team_id': teamId, // Verifica se este teamId é válido!
|
||||
'name': nameCtrl.text.trim(),
|
||||
'type': selectedType,
|
||||
'number': numeroFinal,
|
||||
});
|
||||
}
|
||||
|
||||
print("--- 3. SUCESSO! FECHANDO DIÁLOGO ---");
|
||||
if (ctx.mounted) Navigator.pop(ctx);
|
||||
|
||||
} catch (e) {
|
||||
print("--- X. ERRO AO GUARDAR ---");
|
||||
print(e.toString());
|
||||
|
||||
// MOSTRA O ERRO NO TELEMÓVEL
|
||||
if (ctx.mounted) {
|
||||
ScaffoldMessenger.of(ctx).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text("Erro: $e"),
|
||||
backgroundColor: Colors.red,
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: const Text("Guardar", style: TextStyle(color: Colors.white)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}*/
|
||||
@@ -1,24 +1,21 @@
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class TeamController {
|
||||
// Instância do cliente Supabase
|
||||
final _supabase = Supabase.instance.client;
|
||||
|
||||
// 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
|
||||
// 1. STREAM (Realtime)
|
||||
// Adicionei o .map() no final para garantir que o Dart entende que é uma List<Map>
|
||||
Stream<List<Map<String, dynamic>>> get teamsStream {
|
||||
return _supabase
|
||||
.from('teams')
|
||||
.stream(primaryKey: ['id'])
|
||||
.order('name', ascending: true)
|
||||
.map((data) => List<Map<String, dynamic>>.from(data));
|
||||
}
|
||||
|
||||
// CRIAR
|
||||
// 2. CRIAR
|
||||
// Alterei imageUrl para String? (pode ser nulo) para evitar erros se não houver imagem
|
||||
Future<void> createTeam(String name, String season, String? imageUrl) async {
|
||||
try {
|
||||
await _supabase.from('teams').insert({
|
||||
@@ -33,50 +30,42 @@ class TeamController {
|
||||
}
|
||||
}
|
||||
|
||||
// ELIMINAR
|
||||
// 3. 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");
|
||||
}
|
||||
}
|
||||
|
||||
// FAVORITAR
|
||||
// 4. FAVORITAR
|
||||
Future<void> toggleFavorite(String teamId, bool currentStatus) async {
|
||||
try {
|
||||
await _supabase
|
||||
.from('teams')
|
||||
.update({'is_favorite': !currentStatus})
|
||||
.update({'is_favorite': !currentStatus}) // Inverte o valor
|
||||
.eq('id', teamId);
|
||||
} catch (e) {
|
||||
print("❌ Erro ao favoritar: $e");
|
||||
}
|
||||
}
|
||||
|
||||
// CONTAR JOGADORES (AGORA COM CACHE DE MEMÓRIA!)
|
||||
Stream<int> getPlayerCountStream(String teamId) {
|
||||
// Se já criámos um "Tubo de ligação" para esta equipa, REUTILIZA-O!
|
||||
if (_playerCountStreams.containsKey(teamId)) {
|
||||
return _playerCountStreams[teamId]!;
|
||||
// 5. CONTAR JOGADORES
|
||||
// CORRIGIDO: A sintaxe antiga dava erro. O método .count() é o correto agora.
|
||||
Future<int> getPlayerCount(String teamId) async {
|
||||
try {
|
||||
final count = await _supabase
|
||||
.from('members')
|
||||
.count() // Retorna diretamente o número inteiro
|
||||
.eq('team_id', teamId);
|
||||
return count;
|
||||
} catch (e) {
|
||||
print("Erro ao contar jogadores: $e");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
_playerCountStreams[teamId] = newStream; // Guarda no dicionário
|
||||
return newStream;
|
||||
}
|
||||
|
||||
// LIMPEZA FINAL QUANDO SAÍMOS DA PÁGINA
|
||||
void dispose() {
|
||||
// Limpamos o dicionário de streams para libertar memória RAM
|
||||
_playerCountStreams.clear();
|
||||
}
|
||||
// Mantemos o dispose vazio para não quebrar a chamada na TeamsPage
|
||||
void dispose() {}
|
||||
}
|
||||
Reference in New Issue
Block a user