From fc527084deab56db25d47a0b6870d3bb5be121de Mon Sep 17 00:00:00 2001 From: 230404 <230404@epvc.pt> Date: Tue, 13 Jan 2026 17:15:21 +0000 Subject: [PATCH] fazer o botao de criqar personagem guardar personagem --- lib/controllers/stats_controller.dart | 121 ++++++++++++++++++++++++++ lib/models/person_model.dart | 17 ++++ lib/screens/team_stats_page.dart | 64 ++++++++++++++ lib/widgets/stats_widgets.dart | 90 +++++++++++++++++++ lib/widgets/team_widgets.dart | 25 +++--- 5 files changed, 307 insertions(+), 10 deletions(-) create mode 100644 lib/controllers/stats_controller.dart create mode 100644 lib/models/person_model.dart create mode 100644 lib/screens/team_stats_page.dart create mode 100644 lib/widgets/stats_widgets.dart diff --git a/lib/controllers/stats_controller.dart b/lib/controllers/stats_controller.dart new file mode 100644 index 0000000..cb09e7d --- /dev/null +++ b/lib/controllers/stats_controller.dart @@ -0,0 +1,121 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter/material.dart'; +import '../models/person_model.dart'; + +class StatsController { + final FirebaseFirestore _db = FirebaseFirestore.instance; + + // --- LÓGICA DE FIREBASE --- + + // GRAVAR: Cria o personagem numa sub-coleção dentro da equipa + Future addPerson(String teamId, String name, String type, String number) async { + await _db.collection('teams').doc(teamId).collection('members').add({ + 'name': name, + 'type': type, + 'number': number, + 'createdAt': FieldValue.serverTimestamp(), + }); + } + + // LER: Vai buscar todos os membros da equipa em tempo real + Stream> getMembers(String teamId) { + return _db + .collection('teams') + .doc(teamId) + .collection('members') + .orderBy('createdAt', descending: false) // Organiza por ordem de criação + .snapshots() + .map((snapshot) => snapshot.docs + .map((doc) => Person.fromFirestore(doc.data(), doc.id)) + .toList()); + } + + // --- LÓGICA DE INTERFACE (POPUP) --- + + void showAddPersonDialog(BuildContext context, String teamId) { + String selectedType = 'Jogador'; + final TextEditingController nameController = TextEditingController(); + final TextEditingController numberController = TextEditingController(); + + showDialog( + context: context, + builder: (context) { + return StatefulBuilder( + builder: (context, setPopupState) { + return AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), + title: const Text('Novo Personagem'), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Seletor: Jogador ou Treinador + DropdownButtonFormField( + value: selectedType, + decoration: const InputDecoration(labelText: 'Tipo'), + items: ['Jogador', 'Treinador'] + .map((t) => DropdownMenuItem(value: t, child: Text(t))) + .toList(), + onChanged: (val) { + setPopupState(() { + selectedType = val!; + }); + }, + ), + const SizedBox(height: 15), + // Campo Nome + TextField( + controller: nameController, + textCapitalization: TextCapitalization.words, + decoration: const InputDecoration( + labelText: 'Nome Completo', + hintText: 'Ex: Stephen Curry', + ), + ), + // Campo Número (Aparece apenas se for Jogador) + if (selectedType == 'Jogador') ...[ + const SizedBox(height: 15), + TextField( + controller: numberController, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + labelText: 'Número da Camisola', + hintText: 'Ex: 30', + ), + ), + ], + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancelar'), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF00C853), + ), + onPressed: () async { + if (nameController.text.isNotEmpty) { + // CHAMA A FUNÇÃO DE GRAVAR DO FIREBASE + await addPerson( + teamId, + nameController.text, + selectedType, + selectedType == 'Jogador' ? numberController.text : '', + ); + + if (context.mounted) Navigator.pop(context); + } + }, + child: const Text('Guardar', style: TextStyle(color: Colors.white)), + ), + ], + ); + }, + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/models/person_model.dart b/lib/models/person_model.dart new file mode 100644 index 0000000..0cebed2 --- /dev/null +++ b/lib/models/person_model.dart @@ -0,0 +1,17 @@ +class Person { + final String id; + final String name; + final String type; // 'Jogador' ou 'Treinador' + final String number; // Ex: '30' + + Person({required this.id, required this.name, required this.type, required this.number}); + + factory Person.fromFirestore(Map data, String id) { + return Person( + id: id, + name: data['name'] ?? '', + type: data['type'] ?? 'Jogador', + number: data['number'] ?? '', + ); + } +} \ No newline at end of file diff --git a/lib/screens/team_stats_page.dart b/lib/screens/team_stats_page.dart new file mode 100644 index 0000000..812fc2d --- /dev/null +++ b/lib/screens/team_stats_page.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import '../models/team_model.dart'; +import '../controllers/stats_controller.dart'; +import '../widgets/stats_widgets.dart'; + +class TeamStatsPage extends StatelessWidget { + final Team team; + final StatsController _controller = StatsController(); // Instancia o controller + + TeamStatsPage({super.key, required this.team}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFFF5F7FA), + body: Column( + children: [ + StatsHeader(team: team), // Widget extraído + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SummaryCard(), // Widget extraído + const SizedBox(height: 30), + _buildSectionTitle("Treinadores"), + const SizedBox(height: 30), + _buildSectionTitle("Jogadores"), + const SizedBox(height: 50), + const Center( + child: Text( + "Clica no botão + para adicionar membros", + style: TextStyle(color: Colors.grey, fontStyle: FontStyle.italic), + ), + ), + ], + ), + ), + ), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: () => _controller.showAddPersonDialog(context, team.id), + backgroundColor: const Color(0xFF00C853), + child: const Icon(Icons.add, color: Colors.white), + ), + ); + } + + Widget _buildSectionTitle(String title) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF2C3E50)), + ), + const SizedBox(height: 10), + const Divider(), + ], + ); + } +} \ No newline at end of file diff --git a/lib/widgets/stats_widgets.dart b/lib/widgets/stats_widgets.dart new file mode 100644 index 0000000..cd9ed2c --- /dev/null +++ b/lib/widgets/stats_widgets.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import '../models/team_model.dart'; + +class StatsHeader extends StatelessWidget { + final Team team; + const StatsHeader({super.key, required this.team}); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.only(top: 50, left: 16, right: 16, bottom: 25), + decoration: const BoxDecoration(color: Color(0xFF2196F3)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.pop(context), + ), + Row( + children: [ + _buildLogo(), + const SizedBox(width: 16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(team.name, style: const TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold)), + Text("Temporada ${team.season}", style: const TextStyle(color: Colors.white70, fontSize: 14)), + ], + ), + ], + ), + ], + ), + ); + } + + Widget _buildLogo() { + return Container( + width: 60, height: 60, + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12)), + child: Center( + child: team.imageUrl.startsWith('http') + ? ClipRRect(borderRadius: BorderRadius.circular(12), child: Image.network(team.imageUrl)) + : Text(team.imageUrl.isEmpty ? "🏀" : team.imageUrl, style: const TextStyle(fontSize: 30)), + ), + ); + } +} + +class SummaryCard extends StatelessWidget { + const SummaryCard({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)], + ), + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _StatItem(label: "Jogos", value: "0", color: Colors.black), + _StatItem(label: "Vitórias", value: "0", color: Colors.green), + _StatItem(label: "Derrotas", value: "0", color: Colors.red), + ], + ), + ); + } +} + +class _StatItem extends StatelessWidget { + final String label, value; + final Color color; + const _StatItem({required this.label, required this.value, required this.color}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text(value, style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: color)), + Text(label, style: const TextStyle(color: Colors.grey, fontSize: 13)), + ], + ); + } +} \ No newline at end of file diff --git a/lib/widgets/team_widgets.dart b/lib/widgets/team_widgets.dart index be7d9e7..3976d7c 100644 --- a/lib/widgets/team_widgets.dart +++ b/lib/widgets/team_widgets.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:playmaker/screens/team_stats_page.dart'; import '../models/team_model.dart'; import '../controllers/team_controllers.dart'; @@ -83,19 +84,23 @@ subtitle: Padding( children: [ // Botão Status IconButton( - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - icon: const Icon(Icons.bar_chart, color: Colors.blue), - onPressed: () { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("A abrir status de ${team.name}...")), - ); - }, - ), + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + icon: const Icon(Icons.bar_chart, color: Colors.blue), + onPressed: () { + // NAVEGAÇÃO PARA A NOVA PÁGINA + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TeamStatsPage(team: team), + ), + ); + }, +), const SizedBox(width: 12), // Botão Eliminar IconButton( - padding: EdgeInsets.zero, + padding: EdgeInsets.zero, constraints: const BoxConstraints(), icon: const Icon(Icons.delete_outline, color: Color(0xFFE74C3C)), onPressed: () => _confirmDelete(context),