nao repetir o numero e mais algo tou com sono
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -424,8 +423,9 @@ class _PlacarPageState extends State<PlacarPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildBenchPlayers(List<String> bench, bool isOpponent) {
|
List<Widget> _buildBenchPlayers(List<String> bench, bool isOpponent) {
|
||||||
final teamColor = isOpponent ? const Color(0xFF8B1A1A) : const Color(0xFF1E5BB2);
|
final teamColor = isOpponent ? const Color(0xFFD92C2C) : const Color(0xFF1E5BB2);
|
||||||
final prefix = isOpponent ? "sub_opp_" : "sub_my_";
|
// CORREÇÃO: Utilização do prefixo 'bench_' em vez de 'sub_'
|
||||||
|
final prefix = isOpponent ? "bench_opp_" : "bench_my_";
|
||||||
|
|
||||||
return bench.map((playerName) {
|
return bench.map((playerName) {
|
||||||
final num = _playerNumbers[playerName]!;
|
final num = _playerNumbers[playerName]!;
|
||||||
@@ -489,12 +489,13 @@ class _PlacarPageState extends State<PlacarPage> {
|
|||||||
final action = details.data;
|
final action = details.data;
|
||||||
|
|
||||||
if (action.startsWith("add_") || action.startsWith("sub_") || action.startsWith("miss_")) {
|
if (action.startsWith("add_") || action.startsWith("sub_") || action.startsWith("miss_")) {
|
||||||
_handleActionDrag(action, "$prefix$name"); // CHAMA A NOVA LÓGICA DE INTERCEÇÃO
|
_handleActionDrag(action, "$prefix$name");
|
||||||
}
|
}
|
||||||
else {
|
// CORREÇÃO: Nova lógica que processa apenas ações que comecem por 'bench_' para substituições
|
||||||
|
else if (action.startsWith("bench_")) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (action.startsWith("sub_my_") && !isOpponent) {
|
if (action.startsWith("bench_my_") && !isOpponent) {
|
||||||
String benchPlayer = action.replaceAll("sub_my_", "");
|
String benchPlayer = action.replaceAll("bench_my_", "");
|
||||||
if (_playerStats[benchPlayer]!["fls"]! >= 5) return;
|
if (_playerStats[benchPlayer]!["fls"]! >= 5) return;
|
||||||
|
|
||||||
int courtIndex = _myCourt.indexOf(name);
|
int courtIndex = _myCourt.indexOf(name);
|
||||||
@@ -504,8 +505,8 @@ class _PlacarPageState extends State<PlacarPage> {
|
|||||||
_showMyBench = false;
|
_showMyBench = false;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Sai $name, Entra $benchPlayer')));
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Sai $name, Entra $benchPlayer')));
|
||||||
}
|
}
|
||||||
if (action.startsWith("sub_opp_") && isOpponent) {
|
if (action.startsWith("bench_opp_") && isOpponent) {
|
||||||
String benchPlayer = action.replaceAll("sub_opp_", "");
|
String benchPlayer = action.replaceAll("bench_opp_", "");
|
||||||
if (_playerStats[benchPlayer]!["fls"]! >= 5) return;
|
if (_playerStats[benchPlayer]!["fls"]! >= 5) return;
|
||||||
|
|
||||||
int courtIndex = _oppCourt.indexOf(name);
|
int courtIndex = _oppCourt.indexOf(name);
|
||||||
@@ -519,7 +520,8 @@ class _PlacarPageState extends State<PlacarPage> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
builder: (context, candidateData, rejectedData) {
|
builder: (context, candidateData, rejectedData) {
|
||||||
bool isSubbing = candidateData.any((data) => data != null && data.startsWith("sub_my_") || data != null && data.startsWith("sub_opp_"));
|
// CORREÇÃO: Atualização da verificação de hover com base no novo prefixo
|
||||||
|
bool isSubbing = candidateData.any((data) => data != null && (data.startsWith("bench_my_") || data.startsWith("bench_opp_")));
|
||||||
bool isActionHover = candidateData.any((data) => data != null && (data.startsWith("add_") || data.startsWith("sub_") || data.startsWith("miss_")));
|
bool isActionHover = candidateData.any((data) => data != null && (data.startsWith("add_") || data.startsWith("sub_") || data.startsWith("miss_")));
|
||||||
return _playerCardUI(number, name, stats, teamColor, isSubbing, isActionHover);
|
return _playerCardUI(number, name, stats, teamColor, isSubbing, isActionHover);
|
||||||
},
|
},
|
||||||
@@ -870,4 +872,4 @@ Widget _circle(String label, Color color, IconData? icon, bool isFeed, {double f
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,171 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
import '../models/team_model.dart';
|
import '../models/team_model.dart';
|
||||||
import '../models/person_model.dart';
|
import '../models/person_model.dart';
|
||||||
import '../widgets/team_widgets.dart';
|
|
||||||
import '../widgets/stats_widgets.dart';
|
// ==========================================
|
||||||
|
// 1. WIDGETS
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
// --- CABEÇALHO ---
|
||||||
|
class StatsHeader extends StatelessWidget {
|
||||||
|
final Team team;
|
||||||
|
|
||||||
|
const StatsHeader({super.key, required this.team});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFF2C3E50),
|
||||||
|
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(30), bottomRight: Radius.circular(30)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
|
||||||
|
// IMAGEM OU EMOJI DA EQUIPA AQUI!
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 24,
|
||||||
|
backgroundColor: Colors.white24,
|
||||||
|
backgroundImage: (team.imageUrl.isNotEmpty && team.imageUrl.startsWith('http'))
|
||||||
|
? NetworkImage(team.imageUrl)
|
||||||
|
: null,
|
||||||
|
child: (team.imageUrl.isEmpty || !team.imageUrl.startsWith('http'))
|
||||||
|
? Text(
|
||||||
|
team.imageUrl.isEmpty ? "🛡️" : team.imageUrl,
|
||||||
|
style: const TextStyle(fontSize: 20),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
Expanded( // Expanded evita overflow se o nome for muito longo
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(team.name, style: const TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold), overflow: TextOverflow.ellipsis),
|
||||||
|
Text(team.season, style: const TextStyle(color: Colors.white70, fontSize: 14)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CARD DE RESUMO ---
|
||||||
|
class StatsSummaryCard extends StatelessWidget {
|
||||||
|
final int total;
|
||||||
|
|
||||||
|
const StatsSummaryCard({super.key, required this.total});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
elevation: 4,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
gradient: LinearGradient(colors: [Colors.blue.shade700, Colors.blue.shade400]),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text("Total de Membros", style: TextStyle(color: Colors.white, fontSize: 16)),
|
||||||
|
Text("$total", style: const TextStyle(color: Colors.white, fontSize: 28, fontWeight: FontWeight.bold)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- TÍTULO DE SECÇÃO ---
|
||||||
|
class StatsSectionTitle extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
const StatsSectionTitle({super.key, required this.title});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF2C3E50))),
|
||||||
|
const Divider(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CARD DA PESSOA (JOGADOR/TREINADOR) ---
|
||||||
|
class PersonCard extends StatelessWidget {
|
||||||
|
final Person person;
|
||||||
|
final bool isCoach;
|
||||||
|
final VoidCallback onEdit;
|
||||||
|
final VoidCallback onDelete;
|
||||||
|
|
||||||
|
const PersonCard({
|
||||||
|
super.key,
|
||||||
|
required this.person,
|
||||||
|
required this.isCoach,
|
||||||
|
required this.onEdit,
|
||||||
|
required this.onDelete,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(top: 12),
|
||||||
|
elevation: 2,
|
||||||
|
color: isCoach ? const Color(0xFFFFF9C4) : Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
||||||
|
child: ListTile(
|
||||||
|
leading: isCoach
|
||||||
|
? const CircleAvatar(backgroundColor: Colors.orange, child: Icon(Icons.person, color: Colors.white))
|
||||||
|
: Container(
|
||||||
|
width: 45,
|
||||||
|
height: 45,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(color: Colors.blue.withOpacity(0.1), borderRadius: BorderRadius.circular(10)),
|
||||||
|
child: Text(person.number ?? "J", style: const TextStyle(color: Colors.blue, fontWeight: FontWeight.bold, fontSize: 16)),
|
||||||
|
),
|
||||||
|
title: Text(person.name, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
|
||||||
|
// --- CANTO DIREITO (Trailing) ---
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// IMAGEM DA EQUIPA NO CARD DO JOGADOR
|
||||||
|
|
||||||
|
const SizedBox(width: 5), // Espaço
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.edit_outlined, color: Colors.blue),
|
||||||
|
onPressed: onEdit,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete_outline, color: Colors.red),
|
||||||
|
onPressed: onDelete,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// 2. PÁGINA PRINCIPAL
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
class TeamStatsPage extends StatefulWidget {
|
class TeamStatsPage extends StatefulWidget {
|
||||||
final Team team;
|
final Team team;
|
||||||
@@ -24,12 +187,11 @@ class _TeamStatsPageState extends State<TeamStatsPage> {
|
|||||||
backgroundColor: const Color(0xFFF5F7FA),
|
backgroundColor: const Color(0xFFF5F7FA),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
// Cabeçalho com informações da equipa
|
// Cabeçalho
|
||||||
StatsHeader(team: widget.team),
|
StatsHeader(team: widget.team),
|
||||||
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: StreamBuilder<List<Person>>(
|
child: StreamBuilder<List<Person>>(
|
||||||
// O StreamBuilder reconstrói a UI automaticamente sempre que o Supabase envia novos dados
|
|
||||||
stream: _controller.getMembers(widget.team.id),
|
stream: _controller.getMembers(widget.team.id),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
@@ -42,12 +204,11 @@ class _TeamStatsPageState extends State<TeamStatsPage> {
|
|||||||
|
|
||||||
final members = snapshot.data ?? [];
|
final members = snapshot.data ?? [];
|
||||||
|
|
||||||
// Filtros para organizar a lista
|
|
||||||
final coaches = members.where((m) => m.type == 'Treinador').toList();
|
final coaches = members.where((m) => m.type == 'Treinador').toList();
|
||||||
final players = members.where((m) => m.type == 'Jogador').toList();
|
final players = members.where((m) => m.type == 'Jogador').toList();
|
||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async => setState(() {}), // Pull to refresh como backup
|
onRefresh: () async => setState(() {}),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
@@ -57,25 +218,25 @@ class _TeamStatsPageState extends State<TeamStatsPage> {
|
|||||||
StatsSummaryCard(total: members.length),
|
StatsSummaryCard(total: members.length),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
// SECÇÃO TREINADORES
|
// TREINADORES
|
||||||
if (coaches.isNotEmpty) ...[
|
if (coaches.isNotEmpty) ...[
|
||||||
const StatsSectionTitle(title: "Treinadores"),
|
const StatsSectionTitle(title: "Treinadores"),
|
||||||
...coaches.map((c) => PersonCard(
|
...coaches.map((c) => PersonCard(
|
||||||
person: c,
|
person: c,
|
||||||
isCoach: true,
|
isCoach: true,
|
||||||
|
|
||||||
onEdit: () => _controller.showEditPersonDialog(context, widget.team.id, c),
|
onEdit: () => _controller.showEditPersonDialog(context, widget.team.id, c),
|
||||||
onDelete: () => _confirmDelete(context, c),
|
onDelete: () => _confirmDelete(context, c),
|
||||||
)),
|
)),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
],
|
],
|
||||||
|
|
||||||
// SECÇÃO JOGADORES
|
// JOGADORES
|
||||||
const StatsSectionTitle(title: "Jogadores"),
|
const StatsSectionTitle(title: "Jogadores"),
|
||||||
if (players.isEmpty)
|
if (players.isEmpty)
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.only(top: 20),
|
padding: EdgeInsets.only(top: 20),
|
||||||
child: Text("Nenhum jogador nesta equipa.",
|
child: Text("Nenhum jogador nesta equipa.", style: TextStyle(color: Colors.grey, fontSize: 16)),
|
||||||
style: TextStyle(color: Colors.grey, fontSize: 16)),
|
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
...players.map((p) => PersonCard(
|
...players.map((p) => PersonCard(
|
||||||
@@ -84,7 +245,7 @@ class _TeamStatsPageState extends State<TeamStatsPage> {
|
|||||||
onEdit: () => _controller.showEditPersonDialog(context, widget.team.id, p),
|
onEdit: () => _controller.showEditPersonDialog(context, widget.team.id, p),
|
||||||
onDelete: () => _confirmDelete(context, p),
|
onDelete: () => _confirmDelete(context, p),
|
||||||
)),
|
)),
|
||||||
const SizedBox(height: 80), // Espaço para o FAB não tapar o último card
|
const SizedBox(height: 80),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -95,7 +256,6 @@ class _TeamStatsPageState extends State<TeamStatsPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
// Hero tag única para evitar o erro de tags duplicadas
|
|
||||||
heroTag: 'fab_team_${widget.team.id}',
|
heroTag: 'fab_team_${widget.team.id}',
|
||||||
onPressed: () => _controller.showAddPersonDialog(context, widget.team.id),
|
onPressed: () => _controller.showAddPersonDialog(context, widget.team.id),
|
||||||
backgroundColor: const Color(0xFF00C853),
|
backgroundColor: const Color(0xFF00C853),
|
||||||
@@ -125,22 +285,22 @@ class _TeamStatsPageState extends State<TeamStatsPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- CONTROLLER SUPABASE ---
|
// ==========================================
|
||||||
|
// 3. CONTROLLER
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
class StatsController {
|
class StatsController {
|
||||||
final _supabase = Supabase.instance.client;
|
final _supabase = Supabase.instance.client;
|
||||||
|
|
||||||
// 1. LER (A escuta em tempo real)
|
|
||||||
Stream<List<Person>> getMembers(String teamId) {
|
Stream<List<Person>> getMembers(String teamId) {
|
||||||
return _supabase
|
return _supabase
|
||||||
.from('members')
|
.from('members')
|
||||||
.stream(primaryKey: ['id']) // Garante que a PK na tabela é 'id'
|
.stream(primaryKey: ['id'])
|
||||||
.eq('team_id', teamId)
|
.eq('team_id', teamId)
|
||||||
.order('name', ascending: true)
|
.order('name', ascending: true)
|
||||||
.map((data) => data.map((json) => Person.fromMap(json)).toList());
|
.map((data) => data.map((json) => Person.fromMap(json)).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. APAGAR
|
|
||||||
Future<void> deletePerson(String personId) async {
|
Future<void> deletePerson(String personId) async {
|
||||||
try {
|
try {
|
||||||
await _supabase.from('members').delete().eq('id', personId);
|
await _supabase.from('members').delete().eq('id', personId);
|
||||||
@@ -149,7 +309,6 @@ class StatsController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. DIÁLOGOS
|
|
||||||
void showAddPersonDialog(BuildContext context, String teamId) {
|
void showAddPersonDialog(BuildContext context, String teamId) {
|
||||||
_showForm(context, teamId: teamId);
|
_showForm(context, teamId: teamId);
|
||||||
}
|
}
|
||||||
@@ -237,8 +396,12 @@ class StatsController {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Erro Supabase: $e");
|
debugPrint("Erro Supabase: $e");
|
||||||
if (ctx.mounted) {
|
if (ctx.mounted) {
|
||||||
|
String errorMsg = "Erro ao guardar: $e";
|
||||||
|
if (e.toString().contains('unique')) {
|
||||||
|
errorMsg = "Já existe um membro com este nome na equipa.";
|
||||||
|
}
|
||||||
ScaffoldMessenger.of(ctx).showSnackBar(
|
ScaffoldMessenger.of(ctx).showSnackBar(
|
||||||
SnackBar(content: Text("Erro ao guardar: $e"), backgroundColor: Colors.red)
|
SnackBar(content: Text(errorMsg), backgroundColor: Colors.red)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,6 +413,4 @@ class StatsController {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {}
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:playmaker/pages/PlacarPage.dart'; // Garante que o import está correto
|
import 'package:playmaker/pages/PlacarPage.dart'; // Garante que o import está correto
|
||||||
import '../controllers/team_controller.dart';
|
import '../controllers/team_controller.dart';
|
||||||
import '../controllers/game_controller.dart'; // Import necessário
|
import '../controllers/game_controller.dart';
|
||||||
|
|
||||||
// --- CARD DE EXIBIÇÃO DO JOGO (Mantém-se quase igual) ---
|
// --- CARD DE EXIBIÇÃO DO JOGO (Mantém-se quase igual) ---
|
||||||
class GameResultCard extends StatelessWidget {
|
class GameResultCard extends StatelessWidget {
|
||||||
|
|||||||
Reference in New Issue
Block a user