nao repetir o numero e mais algo tou com sono
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -424,8 +423,9 @@ class _PlacarPageState extends State<PlacarPage> {
|
||||
}
|
||||
|
||||
List<Widget> _buildBenchPlayers(List<String> bench, bool isOpponent) {
|
||||
final teamColor = isOpponent ? const Color(0xFF8B1A1A) : const Color(0xFF1E5BB2);
|
||||
final prefix = isOpponent ? "sub_opp_" : "sub_my_";
|
||||
final teamColor = isOpponent ? const Color(0xFFD92C2C) : const Color(0xFF1E5BB2);
|
||||
// CORREÇÃO: Utilização do prefixo 'bench_' em vez de 'sub_'
|
||||
final prefix = isOpponent ? "bench_opp_" : "bench_my_";
|
||||
|
||||
return bench.map((playerName) {
|
||||
final num = _playerNumbers[playerName]!;
|
||||
@@ -489,12 +489,13 @@ class _PlacarPageState extends State<PlacarPage> {
|
||||
final action = details.data;
|
||||
|
||||
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(() {
|
||||
if (action.startsWith("sub_my_") && !isOpponent) {
|
||||
String benchPlayer = action.replaceAll("sub_my_", "");
|
||||
if (action.startsWith("bench_my_") && !isOpponent) {
|
||||
String benchPlayer = action.replaceAll("bench_my_", "");
|
||||
if (_playerStats[benchPlayer]!["fls"]! >= 5) return;
|
||||
|
||||
int courtIndex = _myCourt.indexOf(name);
|
||||
@@ -504,8 +505,8 @@ class _PlacarPageState extends State<PlacarPage> {
|
||||
_showMyBench = false;
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Sai $name, Entra $benchPlayer')));
|
||||
}
|
||||
if (action.startsWith("sub_opp_") && isOpponent) {
|
||||
String benchPlayer = action.replaceAll("sub_opp_", "");
|
||||
if (action.startsWith("bench_opp_") && isOpponent) {
|
||||
String benchPlayer = action.replaceAll("bench_opp_", "");
|
||||
if (_playerStats[benchPlayer]!["fls"]! >= 5) return;
|
||||
|
||||
int courtIndex = _oppCourt.indexOf(name);
|
||||
@@ -519,7 +520,8 @@ class _PlacarPageState extends State<PlacarPage> {
|
||||
}
|
||||
},
|
||||
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_")));
|
||||
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 '../models/team_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 {
|
||||
final Team team;
|
||||
@@ -24,12 +187,11 @@ class _TeamStatsPageState extends State<TeamStatsPage> {
|
||||
backgroundColor: const Color(0xFFF5F7FA),
|
||||
body: Column(
|
||||
children: [
|
||||
// Cabeçalho com informações da equipa
|
||||
// Cabeçalho
|
||||
StatsHeader(team: widget.team),
|
||||
|
||||
Expanded(
|
||||
child: StreamBuilder<List<Person>>(
|
||||
// O StreamBuilder reconstrói a UI automaticamente sempre que o Supabase envia novos dados
|
||||
stream: _controller.getMembers(widget.team.id),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
@@ -42,12 +204,11 @@ class _TeamStatsPageState extends State<TeamStatsPage> {
|
||||
|
||||
final members = snapshot.data ?? [];
|
||||
|
||||
// Filtros para organizar a lista
|
||||
final coaches = members.where((m) => m.type == 'Treinador').toList();
|
||||
final players = members.where((m) => m.type == 'Jogador').toList();
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async => setState(() {}), // Pull to refresh como backup
|
||||
onRefresh: () async => setState(() {}),
|
||||
child: SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
@@ -57,25 +218,25 @@ class _TeamStatsPageState extends State<TeamStatsPage> {
|
||||
StatsSummaryCard(total: members.length),
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// SECÇÃO TREINADORES
|
||||
// TREINADORES
|
||||
if (coaches.isNotEmpty) ...[
|
||||
const StatsSectionTitle(title: "Treinadores"),
|
||||
...coaches.map((c) => PersonCard(
|
||||
person: c,
|
||||
isCoach: true,
|
||||
|
||||
onEdit: () => _controller.showEditPersonDialog(context, widget.team.id, c),
|
||||
onDelete: () => _confirmDelete(context, c),
|
||||
)),
|
||||
const SizedBox(height: 30),
|
||||
],
|
||||
|
||||
// SECÇÃO JOGADORES
|
||||
// JOGADORES
|
||||
const StatsSectionTitle(title: "Jogadores"),
|
||||
if (players.isEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Text("Nenhum jogador nesta equipa.",
|
||||
style: TextStyle(color: Colors.grey, fontSize: 16)),
|
||||
child: Text("Nenhum jogador nesta equipa.", style: TextStyle(color: Colors.grey, fontSize: 16)),
|
||||
)
|
||||
else
|
||||
...players.map((p) => PersonCard(
|
||||
@@ -84,7 +245,7 @@ class _TeamStatsPageState extends State<TeamStatsPage> {
|
||||
onEdit: () => _controller.showEditPersonDialog(context, widget.team.id, 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(
|
||||
// Hero tag única para evitar o erro de tags duplicadas
|
||||
heroTag: 'fab_team_${widget.team.id}',
|
||||
onPressed: () => _controller.showAddPersonDialog(context, widget.team.id),
|
||||
backgroundColor: const Color(0xFF00C853),
|
||||
@@ -125,22 +285,22 @@ class _TeamStatsPageState extends State<TeamStatsPage> {
|
||||
}
|
||||
}
|
||||
|
||||
// --- CONTROLLER SUPABASE ---
|
||||
// ==========================================
|
||||
// 3. CONTROLLER
|
||||
// ==========================================
|
||||
|
||||
class StatsController {
|
||||
final _supabase = Supabase.instance.client;
|
||||
|
||||
// 1. LER (A escuta em tempo real)
|
||||
Stream<List<Person>> getMembers(String teamId) {
|
||||
return _supabase
|
||||
.from('members')
|
||||
.stream(primaryKey: ['id']) // Garante que a PK na tabela é 'id'
|
||||
.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);
|
||||
@@ -149,7 +309,6 @@ class StatsController {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. DIÁLOGOS
|
||||
void showAddPersonDialog(BuildContext context, String teamId) {
|
||||
_showForm(context, teamId: teamId);
|
||||
}
|
||||
@@ -237,8 +396,12 @@ class StatsController {
|
||||
} catch (e) {
|
||||
debugPrint("Erro Supabase: $e");
|
||||
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(
|
||||
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:playmaker/pages/PlacarPage.dart'; // Garante que o import está correto
|
||||
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) ---
|
||||
class GameResultCard extends StatelessWidget {
|
||||
|
||||
Reference in New Issue
Block a user