fazer a tela de jogo ta tudo no PlacarPage
This commit is contained in:
@@ -1,25 +1,40 @@
|
||||
import 'dart:async';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import '../models/game_model.dart';
|
||||
|
||||
class GameController {
|
||||
final List<Game> _games = [];
|
||||
final _gameStreamController = StreamController<List<Game>>.broadcast();
|
||||
final _supabase = Supabase.instance.client;
|
||||
|
||||
Stream<List<Game>> get gamesStream => _gameStreamController.stream;
|
||||
// 1. LER JOGOS (Stream em Tempo Real)
|
||||
Stream<List<Game>> get gamesStream {
|
||||
return _supabase
|
||||
.from('games')
|
||||
.stream(primaryKey: ['id'])
|
||||
.order('game_date', ascending: false) // Mais recentes primeiro
|
||||
.map((data) => data.map((json) => Game.fromMap(json)).toList());
|
||||
}
|
||||
|
||||
void addGame(String myTeam, String opponent, String season) {
|
||||
final newGame = Game(
|
||||
id: DateTime.now().toString(),
|
||||
myTeam: myTeam,
|
||||
opponentTeam: opponent,
|
||||
season: season,
|
||||
date: DateTime.now(),
|
||||
);
|
||||
_games.insert(0, newGame); // Adiciona ao topo da lista
|
||||
_gameStreamController.add(List.unmodifiable(_games));
|
||||
// 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({
|
||||
'my_team': myTeam,
|
||||
'opponent_team': opponent,
|
||||
'season': season,
|
||||
'my_score': 0,
|
||||
'opponent_score': 0,
|
||||
'status': 'Decorrer', // Começa como "Decorrer"
|
||||
'game_date': DateTime.now().toIso8601String(),
|
||||
}).select().single(); // .select().single() retorna o objeto criado
|
||||
|
||||
return response['id']; // Retorna o UUID gerado pelo Supabase
|
||||
} catch (e) {
|
||||
print("Erro ao criar jogo: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_gameStreamController.close();
|
||||
// Não é necessário fechar streams do Supabase manualmente aqui
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,27 @@ class Game {
|
||||
required this.id,
|
||||
required this.myTeam,
|
||||
required this.opponentTeam,
|
||||
this.myScore = "0",
|
||||
this.opponentScore = "0",
|
||||
required this.myScore,
|
||||
required this.opponentScore,
|
||||
required this.season,
|
||||
this.status = "Brevemente",
|
||||
required this.status,
|
||||
required this.date,
|
||||
});
|
||||
|
||||
// Converte dados do Supabase para o Objeto Dart
|
||||
factory Game.fromMap(Map<String, dynamic> map) {
|
||||
return Game(
|
||||
id: map['id'] ?? '',
|
||||
myTeam: map['my_team'] ?? 'Desconhecido',
|
||||
opponentTeam: map['opponent_team'] ?? 'Desconhecido',
|
||||
// Convertemos para String porque no DB é Integer, mas na UI usas String
|
||||
myScore: (map['my_score'] ?? 0).toString(),
|
||||
opponentScore: (map['opponent_score'] ?? 0).toString(),
|
||||
season: map['season'] ?? '',
|
||||
status: map['status'] ?? 'Brevemente',
|
||||
date: map['game_date'] != null
|
||||
? DateTime.parse(map['game_date'])
|
||||
: DateTime.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class PlacarPage extends StatefulWidget {
|
||||
final String gameId;
|
||||
@@ -21,11 +22,20 @@ class _PlacarPageState extends State<PlacarPage> {
|
||||
int _myScore = 0;
|
||||
int _opponentScore = 0;
|
||||
|
||||
// Lógica do Tempo (Exemplo: 10 minutos)
|
||||
Duration _duration = const Duration(minutes: 10);
|
||||
Timer? _timer;
|
||||
bool _isRunning = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Força a tela na horizontal
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.landscapeRight,
|
||||
DeviceOrientation.landscapeLeft,
|
||||
]);
|
||||
}
|
||||
|
||||
void _toggleTimer() {
|
||||
if (_isRunning) {
|
||||
_timer?.cancel();
|
||||
@@ -49,91 +59,205 @@ class _PlacarPageState extends State<PlacarPage> {
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
// Restaura a vertical ao sair
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
]);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFF121212),
|
||||
appBar: AppBar(
|
||||
title: const Text("Placar Live"),
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
),
|
||||
body: Column(
|
||||
body: Stack(
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
// Cronómetro
|
||||
GestureDetector(
|
||||
onTap: _toggleTimer,
|
||||
child: Text(
|
||||
_formatTime(_duration),
|
||||
style: const TextStyle(color: Colors.white, fontSize: 75, fontWeight: FontWeight.bold, fontFamily: 'monospace'),
|
||||
),
|
||||
),
|
||||
const Text("CLIQUE NO TEMPO PARA INICIAR/PAUSAR", style: TextStyle(color: Colors.grey, fontSize: 10)),
|
||||
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
// Minha Equipa
|
||||
_buildTeamSide(widget.myTeam, _myScore, (p) => setState(() => _myScore += p), const Color(0xFFE74C3C)),
|
||||
// Divisor
|
||||
Container(width: 1, color: Colors.white24, margin: const EdgeInsets.symmetric(vertical: 40)),
|
||||
// Adversário
|
||||
_buildTeamSide(widget.opponentTeam, _opponentScore, (p) => setState(() => _opponentScore += p), Colors.blueGrey),
|
||||
],
|
||||
// 1. FUNDO (O CAMPO DE BASQUETEBOL)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFD2B48C), // Cor de madeira temporária
|
||||
// TODO: Descomenta a linha abaixo e usa a tua imagem do campo
|
||||
// image: DecorationImage(image: AssetImage('assets/court.png'), fit: BoxFit.cover),
|
||||
),
|
||||
// Linhas do campo desenhadas por cima (Opcional se usares imagem real)
|
||||
child: const CustomPaint(painter: CourtPainterPlaceholder()),
|
||||
),
|
||||
|
||||
// Botão Finalizar
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
||||
),
|
||||
onPressed: () {
|
||||
// Aqui podes adicionar a lógica para salvar o resultado final no Controller
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text("FINALIZAR PARTIDA", style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
// 2. BARRA SUPERIOR (PLACARD)
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: _buildTopScoreboard(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTeamSide(String name, int score, Function(int) onAdd, Color color) {
|
||||
return Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(name, textAlign: TextAlign.center, style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 10),
|
||||
Text("$score", style: TextStyle(color: color, fontSize: 80, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 30),
|
||||
// Botões de Pontuação
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [1, 2, 3].map((p) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: InkWell(
|
||||
onTap: () => onAdd(p),
|
||||
child: CircleAvatar(
|
||||
backgroundColor: color.withOpacity(0.2),
|
||||
child: Text("+$p", style: TextStyle(color: color, fontWeight: FontWeight.bold)),
|
||||
// 3. JOGADORES NO CAMPO (Posições manuais usando Positioned)
|
||||
// Exemplo de como posicionar os teus jogadores
|
||||
Positioned(top: 120, left: 100, child: _buildPlayerCard("6", "LeBron James")),
|
||||
Positioned(top: 250, left: 160, child: _buildPlayerCard("3", "Anthony Davis")),
|
||||
Positioned(top: 320, left: 280, child: _buildPlayerCard("28", "Rui Hachimura")),
|
||||
Positioned(top: 380, left: 160, child: _buildPlayerCard("1", "D'Angelo Russell")),
|
||||
Positioned(top: 500, left: 100, child: _buildPlayerCard("15", "Austin Reaves")),
|
||||
|
||||
// 4. BOTÃO CENTRAL (PLAY/PAUSE)
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTap: _toggleTimer,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: const Color(0xFF2662D9).withOpacity(0.8),
|
||||
border: Border.all(color: Colors.blueAccent.withOpacity(0.5), width: 8),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Icon(
|
||||
_isRunning ? Icons.pause : Icons.play_arrow,
|
||||
color: Colors.white,
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
)).toList(),
|
||||
),
|
||||
),
|
||||
|
||||
// 5. PAINEL DE ACÇÕES EM BAIXO (Estatísticas: 1pt, 2pt, 3pt)
|
||||
Positioned(
|
||||
bottom: 20,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: _buildActionButtonsPanel(),
|
||||
),
|
||||
|
||||
// 6. BOTÃO DE FECHAR / GUARDAR NO CANTO
|
||||
Positioned(
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
child: FloatingActionButton(
|
||||
backgroundColor: const Color(0xFF1E2A38),
|
||||
mini: true,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Icon(Icons.close, color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// --- WIDGETS AUXILIARES PARA LIMPAR O CÓDIGO ---
|
||||
|
||||
Widget _buildTopScoreboard() {
|
||||
return Container(
|
||||
color: const Color(0xFF1E2A38), // Azul Escuro da barra
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Equipa Casa
|
||||
Text(widget.myTeam.toUpperCase(), style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(width: 15),
|
||||
_scoreBox(_myScore, const Color(0xFF2662D9)), // Azul
|
||||
|
||||
const SizedBox(width: 20),
|
||||
|
||||
// Relógio
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||
decoration: BoxDecoration(color: Colors.white.withOpacity(0.1), borderRadius: BorderRadius.circular(8)),
|
||||
child: Text(
|
||||
_formatTime(_duration),
|
||||
style: const TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, fontFamily: 'monospace'),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 20),
|
||||
|
||||
// Equipa Visitante
|
||||
_scoreBox(_opponentScore, const Color(0xFFE74C3C)), // Vermelho
|
||||
const SizedBox(width: 15),
|
||||
Text(widget.opponentTeam.toUpperCase(), style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _scoreBox(int score, Color color) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(8)),
|
||||
child: Text(score.toString(), style: const TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPlayerCard(String number, String name) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF1E2A38).withOpacity(0.9),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: const Color(0xFF2662D9),
|
||||
radius: 12,
|
||||
child: Text(number, style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(name, style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
|
||||
const Text("0 Pts | 0 Rbs | 0 Ast", style: TextStyle(color: Colors.grey, fontSize: 8)),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButtonsPanel() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Podes adicionar onTap nestes botões para chamarem setState(() => _myScore += X)
|
||||
_actionBtn("1", Colors.orange),
|
||||
_actionBtn("2", Colors.orange),
|
||||
_actionBtn("3", Colors.orange),
|
||||
const SizedBox(width: 20),
|
||||
_actionBtn("M", Colors.blueGrey, icon: Icons.sports_basketball),
|
||||
_actionBtn("F", Colors.redAccent, icon: Icons.pan_tool),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _actionBtn(String label, Color color, {IconData? icon}) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: icon != null
|
||||
? Icon(icon, color: Colors.white, size: 20)
|
||||
: Text(label, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Classe temporária para desenhar as marcações do campo caso não tenhas imagem
|
||||
class CourtPainterPlaceholder extends CustomPainter {
|
||||
const CourtPainterPlaceholder();
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..color = Colors.white.withOpacity(0.5)..style = PaintingStyle.stroke..strokeWidth = 2;
|
||||
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
|
||||
canvas.drawLine(Offset(size.width / 2, 0), Offset(size.width / 2, size.height), paint);
|
||||
canvas.drawCircle(Offset(size.width / 2, size.height / 2), 60, paint);
|
||||
}
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
@@ -12,17 +12,9 @@ class GamePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _GamePageState extends State<GamePage> {
|
||||
// Criamos as instâncias dos controllers
|
||||
final GameController gameController = GameController();
|
||||
final TeamController teamController = TeamController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// É importante fechar os streams quando a página sai da memória
|
||||
gameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -33,12 +25,17 @@ class _GamePageState extends State<GamePage> {
|
||||
elevation: 0,
|
||||
),
|
||||
body: StreamBuilder<List<Game>>(
|
||||
// LÊ DIRETAMENTE DO SUPABASE
|
||||
stream: gameController.gamesStream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return Center(child: Text("Erro: ${snapshot.error}"));
|
||||
}
|
||||
|
||||
if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return const Center(child: Text("Nenhum jogo registado."));
|
||||
}
|
||||
@@ -49,7 +46,6 @@ class _GamePageState extends State<GamePage> {
|
||||
itemBuilder: (context, index) {
|
||||
final game = snapshot.data![index];
|
||||
|
||||
// ATUALIZADO: Passamos o gameId para o card
|
||||
return GameResultCard(
|
||||
gameId: game.id,
|
||||
myTeam: game.myTeam,
|
||||
@@ -75,10 +71,8 @@ class _GamePageState extends State<GamePage> {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => CreateGameDialogManual(
|
||||
controller: teamController,
|
||||
onConfirm: (my, opp, sea) {
|
||||
gameController.addGame(my, opp, sea);
|
||||
},
|
||||
teamController: teamController,
|
||||
gameController: gameController, // Passamos o controller para fazer o insert
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:playmaker/pages/PlacarPage.dart';
|
||||
import 'package:playmaker/pages/PlacarPage.dart'; // Garante que o import está correto
|
||||
import '../controllers/team_controller.dart';
|
||||
import '../controllers/game_controller.dart'; // Import necessário
|
||||
|
||||
// --- CARD DE EXIBIÇÃO DO JOGO ---
|
||||
// --- CARD DE EXIBIÇÃO DO JOGO (Mantém-se quase igual) ---
|
||||
class GameResultCard extends StatelessWidget {
|
||||
final String gameId; // Adicionado para identificar o jogo no retorno
|
||||
final String gameId;
|
||||
final String myTeam, opponentTeam, myScore, opponentScore, status, season;
|
||||
|
||||
const GameResultCard({
|
||||
@@ -26,15 +27,14 @@ class GameResultCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 10)],
|
||||
boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 10)],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_buildTeamInfo(myTeam, const Color(0xFFE74C3C)),
|
||||
// Agora passamos o context e o gameId para o centro
|
||||
_buildScoreCenter(context, gameId),
|
||||
_buildTeamInfo(opponentTeam, Colors.black87),
|
||||
Expanded(child: _buildTeamInfo(myTeam, const Color(0xFFE74C3C))),
|
||||
_buildScoreCenter(context, gameId),
|
||||
Expanded(child: _buildTeamInfo(opponentTeam, Colors.black87)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -43,12 +43,13 @@ class GameResultCard extends StatelessWidget {
|
||||
Widget _buildTeamInfo(String name, Color color) {
|
||||
return Column(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: color,
|
||||
child: const Icon(Icons.shield, color: Colors.white)
|
||||
),
|
||||
CircleAvatar(backgroundColor: color, child: const Icon(Icons.shield, color: Colors.white)),
|
||||
const SizedBox(height: 4),
|
||||
Text(name, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12)),
|
||||
Text(name,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -65,22 +66,22 @@ class GameResultCard extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// BOTÃO PARA RETORNAR AO JOGO
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
print("Navegando para o marcador do jogo: $id");
|
||||
// Aqui faremos a navegação para a página do marcador em breve
|
||||
// NAVEGAÇÃO PARA O PLACAR (Usando o ID real)
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PlacarPage(
|
||||
gameId: id,
|
||||
myTeam: myTeam,
|
||||
opponentTeam: opponentTeam,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.play_circle_fill, size: 16, color: Color(0xFFE74C3C)),
|
||||
label: const Text(
|
||||
"RETORNAR",
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Color(0xFFE74C3C),
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
),
|
||||
label: const Text("RETORNAR", style: TextStyle(fontSize: 10, color: Color(0xFFE74C3C), fontWeight: FontWeight.bold)),
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFE74C3C).withOpacity(0.1),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
@@ -88,7 +89,6 @@ class GameResultCard extends StatelessWidget {
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 4),
|
||||
Text(status, style: const TextStyle(fontSize: 10, color: Colors.blue, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
@@ -102,12 +102,16 @@ class GameResultCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// --- POPUP DE CRIAÇÃO ---
|
||||
// --- POPUP DE CRIAÇÃO (MODIFICADO PARA SUPABASE) ---
|
||||
class CreateGameDialogManual extends StatefulWidget {
|
||||
final TeamController controller;
|
||||
final Function(String, String, String) onConfirm;
|
||||
final TeamController teamController;
|
||||
final GameController gameController; // Recebemos o controller do jogo
|
||||
|
||||
const CreateGameDialogManual({super.key, required this.controller, required this.onConfirm});
|
||||
const CreateGameDialogManual({
|
||||
super.key,
|
||||
required this.teamController,
|
||||
required this.gameController
|
||||
});
|
||||
|
||||
@override
|
||||
State<CreateGameDialogManual> createState() => _CreateGameDialogManualState();
|
||||
@@ -115,8 +119,10 @@ class CreateGameDialogManual extends StatefulWidget {
|
||||
|
||||
class _CreateGameDialogManualState extends State<CreateGameDialogManual> {
|
||||
late TextEditingController _seasonController;
|
||||
String _myTeamName = "";
|
||||
String _opponentName = "";
|
||||
final TextEditingController _myTeamController = TextEditingController();
|
||||
final TextEditingController _opponentController = TextEditingController();
|
||||
|
||||
bool _isLoading = false; // Para mostrar loading no botão
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -124,12 +130,6 @@ class _CreateGameDialogManualState extends State<CreateGameDialogManual> {
|
||||
_seasonController = TextEditingController(text: _calculateSeason());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_seasonController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String _calculateSeason() {
|
||||
final now = DateTime.now();
|
||||
return now.month >= 7 ? "${now.year}/${(now.year + 1).toString().substring(2)}" : "${now.year - 1}/${now.year.toString().substring(2)}";
|
||||
@@ -146,73 +146,102 @@ class _CreateGameDialogManualState extends State<CreateGameDialogManual> {
|
||||
children: [
|
||||
TextField(
|
||||
controller: _seasonController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Temporada',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.calendar_today, size: 20),
|
||||
),
|
||||
decoration: const InputDecoration(labelText: 'Temporada', border: OutlineInputBorder(), prefixIcon: Icon(Icons.calendar_today)),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
_buildSearch(label: "Minha Equipa", onSelect: (v) => _myTeamName = v),
|
||||
|
||||
// Usamos Autocomplete para equipas (Assumindo que TeamController já é Supabase)
|
||||
_buildSearch(label: "Minha Equipa", controller: _myTeamController),
|
||||
|
||||
const Padding(padding: EdgeInsets.symmetric(vertical: 8), child: Text("VS", style: TextStyle(fontWeight: FontWeight.bold, color: Colors.grey))),
|
||||
_buildSearch(label: "Adversário", onSelect: (v) => _opponentName = v),
|
||||
|
||||
_buildSearch(label: "Adversário", controller: _opponentController),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context), child: const Text('CANCELAR')),
|
||||
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFE74C3C),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: () {
|
||||
if (_myTeamName.isNotEmpty && _opponentName.isNotEmpty) {
|
||||
// 1. Criar um ID único para este jogo
|
||||
final String newGameId = DateTime.now().toString();
|
||||
|
||||
// 2. Notificar a GamePage para criar o card (via onConfirm)
|
||||
widget.onConfirm(_myTeamName, _opponentName, _seasonController.text);
|
||||
|
||||
// 3. Fechar o popup de criação
|
||||
Navigator.pop(context);
|
||||
|
||||
// 4. Ir direto para a tela do marcador de pontos
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PlacarPage(
|
||||
gameId: newGameId,
|
||||
myTeam: _myTeamName,
|
||||
opponentTeam: _opponentName,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFE74C3C),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: _isLoading ? null : () async {
|
||||
if (_myTeamController.text.isNotEmpty && _opponentController.text.isNotEmpty) {
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
// 1. CRIAR NO SUPABASE E OBTER O ID REAL
|
||||
String? newGameId = await widget.gameController.createGame(
|
||||
_myTeamController.text,
|
||||
_opponentController.text,
|
||||
_seasonController.text,
|
||||
);
|
||||
|
||||
setState(() => _isLoading = false);
|
||||
|
||||
if (newGameId != null && context.mounted) {
|
||||
// 2. Fechar Popup
|
||||
Navigator.pop(context);
|
||||
|
||||
// 3. Ir para o Placar com o ID real do banco
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PlacarPage(
|
||||
gameId: newGameId,
|
||||
myTeam: _myTeamController.text,
|
||||
opponentTeam: _opponentController.text,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: _isLoading
|
||||
? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2))
|
||||
: const Text('CRIAR', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text('CRIAR', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSearch({required String label, required Function(String) onSelect}) {
|
||||
Widget _buildSearch({required String label, required TextEditingController controller}) {
|
||||
return StreamBuilder<List<Map<String, dynamic>>>(
|
||||
stream: widget.controller.teamsStream,
|
||||
stream: widget.teamController.teamsStream,
|
||||
builder: (context, snapshot) {
|
||||
List<String> teamList = snapshot.hasData ? snapshot.data!.map((t) => t['name'].toString()).toList() : [];
|
||||
List<String> teamList = snapshot.hasData
|
||||
? snapshot.data!.map((t) => t['name'].toString()).toList()
|
||||
: [];
|
||||
|
||||
return Autocomplete<String>(
|
||||
optionsBuilder: (val) => val.text.isEmpty ? [] : teamList.where((t) => t.toLowerCase().contains(val.text.toLowerCase())),
|
||||
fieldViewBuilder: (ctx, ctrl, node, submit) => TextField(
|
||||
controller: ctrl,
|
||||
focusNode: node,
|
||||
onChanged: onSelect,
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
border: const OutlineInputBorder()
|
||||
),
|
||||
),
|
||||
optionsBuilder: (val) {
|
||||
if (val.text.isEmpty) return const Iterable<String>.empty();
|
||||
return teamList.where((t) => t.toLowerCase().contains(val.text.toLowerCase()));
|
||||
},
|
||||
onSelected: (String selection) {
|
||||
controller.text = selection;
|
||||
},
|
||||
fieldViewBuilder: (ctx, txtCtrl, node, submit) {
|
||||
// Sincronizar o controller interno do Autocomplete com o nosso controller externo
|
||||
if (txtCtrl.text.isEmpty && controller.text.isNotEmpty) {
|
||||
txtCtrl.text = controller.text;
|
||||
}
|
||||
// Importante: Guardar o valor escrito mesmo que não selecionado da lista
|
||||
txtCtrl.addListener(() {
|
||||
controller.text = txtCtrl.text;
|
||||
});
|
||||
|
||||
return TextField(
|
||||
controller: txtCtrl,
|
||||
focusNode: node,
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
border: const OutlineInputBorder()
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user