login e register

This commit is contained in:
2026-03-12 10:42:21 +00:00
parent f5d7e88149
commit b95d6dc8d4
13 changed files with 926 additions and 1122 deletions

View File

@@ -6,8 +6,7 @@ import 'package:playmaker/pages/teamPage.dart';
import 'package:playmaker/controllers/team_controller.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:playmaker/pages/status_page.dart';
import 'dart:math' as math;
import '../utils/size_extension.dart';
import 'package:playmaker/grafico%20de%20pizza/controllers/contollers_grafico.dart';
class HomeScreen extends StatefulWidget {
@@ -31,12 +30,10 @@ class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
final double wScreen = MediaQuery.of(context).size.width;
final double hScreen = MediaQuery.of(context).size.height;
final double sf = math.min(wScreen, hScreen) / 400;
// Já não precisamos calcular o sf aqui!
final List<Widget> pages = [
_buildHomeContent(sf, wScreen),
_buildHomeContent(context), // Passamos só o context
const GamePage(),
const TeamsPage(),
const StatusPage(),
@@ -45,11 +42,11 @@ class _HomeScreenState extends State<HomeScreen> {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('PlayMaker', style: TextStyle(fontSize: 20 * sf)),
title: Text('PlayMaker', style: TextStyle(fontSize: 20 * context.sf)),
backgroundColor: HomeConfig.primaryColor,
foregroundColor: Colors.white,
leading: IconButton(
icon: Icon(Icons.person, size: 24 * sf),
icon: Icon(Icons.person, size: 24 * context.sf),
onPressed: () {},
),
),
@@ -65,7 +62,8 @@ class _HomeScreenState extends State<HomeScreen> {
backgroundColor: Theme.of(context).colorScheme.surface,
surfaceTintColor: Theme.of(context).colorScheme.surfaceTint,
elevation: 1,
height: 70 * math.min(sf, 1.2),
// O math.min não é necessário se já tens o sf. Mas podes usar context.sf
height: 70 * (context.sf < 1.2 ? context.sf : 1.2),
destinations: const [
NavigationDestination(icon: Icon(Icons.home_outlined), selectedIcon: Icon(Icons.home_filled), label: 'Home'),
NavigationDestination(icon: Icon(Icons.sports_soccer_outlined), selectedIcon: Icon(Icons.sports_soccer), label: 'Jogo'),
@@ -76,16 +74,16 @@ class _HomeScreenState extends State<HomeScreen> {
);
}
void _showTeamSelector(BuildContext context, double sf) {
void _showTeamSelector(BuildContext context) {
showModalBottomSheet(
context: context,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20 * sf))),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20 * context.sf))),
builder: (context) {
return StreamBuilder<List<Map<String, dynamic>>>(
stream: _teamController.teamsStream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator()));
if (!snapshot.hasData || snapshot.data!.isEmpty) return SizedBox(height: 200 * sf, child: const Center(child: Text("Nenhuma equipa criada.")));
if (!snapshot.hasData || snapshot.data!.isEmpty) return SizedBox(height: 200 * context.sf, child: const Center(child: Text("Nenhuma equipa criada.")));
final teams = snapshot.data!;
return ListView.builder(
@@ -114,8 +112,9 @@ class _HomeScreenState extends State<HomeScreen> {
);
}
Widget _buildHomeContent(double sf, double wScreen) {
final double cardHeight = (wScreen / 2) * 1.0;
Widget _buildHomeContent(BuildContext context) {
final double wScreen = MediaQuery.of(context).size.width;
final double cardHeight = wScreen * 0.5;
return StreamBuilder<List<Map<String, dynamic>>>(
stream: _selectedTeamId != null
@@ -126,44 +125,44 @@ class _HomeScreenState extends State<HomeScreen> {
return SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 22.0 * sf, vertical: 16.0 * sf),
padding: EdgeInsets.symmetric(horizontal: 22.0 * context.sf, vertical: 16.0 * context.sf),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InkWell(
onTap: () => _showTeamSelector(context, sf),
onTap: () => _showTeamSelector(context),
child: Container(
padding: EdgeInsets.all(12 * sf),
decoration: BoxDecoration(color: Colors.grey.shade100, borderRadius: BorderRadius.circular(15 * sf), border: Border.all(color: Colors.grey.shade300)),
padding: EdgeInsets.all(12 * context.sf),
decoration: BoxDecoration(color: Colors.grey.shade100, borderRadius: BorderRadius.circular(15 * context.sf), border: Border.all(color: Colors.grey.shade300)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(children: [Icon(Icons.shield, color: HomeConfig.primaryColor, size: 24 * sf), SizedBox(width: 10 * sf), Text(_selectedTeamName, style: TextStyle(fontSize: 16 * sf, fontWeight: FontWeight.bold))]),
Row(children: [Icon(Icons.shield, color: HomeConfig.primaryColor, size: 24 * context.sf), SizedBox(width: 10 * context.sf), Text(_selectedTeamName, style: TextStyle(fontSize: 16 * context.sf, fontWeight: FontWeight.bold))]),
const Icon(Icons.arrow_drop_down),
],
),
),
),
SizedBox(height: 20 * sf),
SizedBox(height: 20 * context.sf),
SizedBox(
height: cardHeight,
child: Row(
children: [
Expanded(child: _buildStatCard(title: 'Mais Pontos', playerName: leaders['pts_name'], statValue: leaders['pts_val'].toString(), statLabel: 'TOTAL', color: const Color(0xFF1565C0), isHighlighted: true)),
SizedBox(width: 12 * sf),
Expanded(child: _buildStatCard(title: 'Assistências', playerName: leaders['ast_name'], statValue: leaders['ast_val'].toString(), statLabel: 'TOTAL', color: const Color(0xFF2E7D32))),
Expanded(child: _buildStatCard(context: context, title: 'Mais Pontos', playerName: leaders['pts_name'], statValue: leaders['pts_val'].toString(), statLabel: 'TOTAL', color: const Color(0xFF1565C0), isHighlighted: true)),
SizedBox(width: 12 * context.sf),
Expanded(child: _buildStatCard(context: context, title: 'Assistências', playerName: leaders['ast_name'], statValue: leaders['ast_val'].toString(), statLabel: 'TOTAL', color: const Color(0xFF2E7D32))),
],
),
),
SizedBox(height: 12 * sf),
SizedBox(height: 12 * context.sf),
SizedBox(
height: cardHeight,
child: Row(
children: [
Expanded(child: _buildStatCard(title: 'Rebotes', playerName: leaders['rbs_name'], statValue: leaders['rbs_val'].toString(), statLabel: 'TOTAL', color: const Color(0xFF6A1B9A))),
SizedBox(width: 12 * sf),
Expanded(child: _buildStatCard(context: context, title: 'Rebotes', playerName: leaders['rbs_name'], statValue: leaders['rbs_val'].toString(), statLabel: 'TOTAL', color: const Color(0xFF6A1B9A))),
SizedBox(width: 12 * context.sf),
Expanded(
child: PieChartCard(
victories: _teamWins,
@@ -172,58 +171,42 @@ class _HomeScreenState extends State<HomeScreen> {
title: 'DESEMPENHO',
subtitle: 'Temporada',
backgroundColor: const Color(0xFFC62828),
sf: sf
sf: context.sf // Aqui o PieChartCard ainda usa sf, então passamos
),
),
],
),
),
SizedBox(height: 40 * sf),
SizedBox(height: 40 * context.sf),
Text('Histórico de Jogos', style: TextStyle(fontSize: 20 * sf, fontWeight: FontWeight.bold, color: Colors.grey[800])),
SizedBox(height: 16 * sf),
Text('Histórico de Jogos', style: TextStyle(fontSize: 20 * context.sf, fontWeight: FontWeight.bold, color: Colors.grey[800])),
SizedBox(height: 16 * context.sf),
// 👇 HISTÓRICO LIGADO À BASE DE DADOS (COM AS COLUNAS CORRIGIDAS) 👇
// 👇 LIGAÇÃO CORRIGIDA: Agora usa a coluna 'nome' como pediste 👇
// 👇 HISTÓRICO DINÂMICO (Qualquer equipa) 👇
_selectedTeamName == "Selecionar Equipa"
? Container(
padding: EdgeInsets.all(20 * sf),
padding: EdgeInsets.all(20 * context.sf),
alignment: Alignment.center,
child: Text("Seleciona uma equipa no topo.", style: TextStyle(color: Colors.grey, fontSize: 14 * sf)),
child: Text("Seleciona uma equipa no topo.", style: TextStyle(color: Colors.grey, fontSize: 14 * context.sf)),
)
: StreamBuilder<List<Map<String, dynamic>>>(
// Pede os jogos ordenados pela data (sem filtros rígidos aqui)
stream: _supabase.from('games').stream(primaryKey: ['id'])
.order('game_date', ascending: false),
builder: (context, gameSnapshot) {
if (gameSnapshot.hasError) {
return Text("Erro ao carregar jogos: ${gameSnapshot.error}", style: const TextStyle(color: Colors.red));
}
if (gameSnapshot.hasError) return Text("Erro: ${gameSnapshot.error}", style: const TextStyle(color: Colors.red));
if (gameSnapshot.connectionState == ConnectionState.waiting) return const Center(child: CircularProgressIndicator());
if (gameSnapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
// 👇 O CÉREBRO DA APP: Filtro inteligente no Flutter 👇
final todosOsJogos = gameSnapshot.data ?? [];
final gamesList = todosOsJogos.where((game) {
String myT = game['my_team']?.toString() ?? '';
String oppT = game['opponent_team']?.toString() ?? '';
String status = game['status']?.toString() ?? '';
// O jogo tem de envolver a equipa selecionada E estar Terminado
bool isPlaying = (myT == _selectedTeamName || oppT == _selectedTeamName);
bool isFinished = status == 'Terminado';
return isPlaying && isFinished;
}).take(3).toList(); // Pega apenas nos 3 mais recentes
return (myT == _selectedTeamName || oppT == _selectedTeamName) && status == 'Terminado';
}).take(3).toList();
if (gamesList.isEmpty) {
return Container(
padding: EdgeInsets.all(20 * sf),
padding: EdgeInsets.all(20 * context.sf),
decoration: BoxDecoration(color: Colors.grey.shade50, borderRadius: BorderRadius.circular(14)),
alignment: Alignment.center,
child: Text("Ainda não há jogos terminados para $_selectedTeamName.", style: TextStyle(color: Colors.grey)),
@@ -232,47 +215,33 @@ class _HomeScreenState extends State<HomeScreen> {
return Column(
children: gamesList.map((game) {
// Lê os dados brutos da base de dados
String dbMyTeam = game['my_team']?.toString() ?? '';
String dbOppTeam = game['opponent_team']?.toString() ?? '';
int dbMyScore = int.tryParse(game['my_score'].toString()) ?? 0;
int dbOppScore = int.tryParse(game['opponent_score'].toString()) ?? 0;
String opponent;
int myScore;
int oppScore;
String opponent; int myScore; int oppScore;
// 🔄 MAGIA DA INVERSÃO DE RESULTADOS 🔄
// Garante que os pontos da equipa selecionada aparecem sempre do lado esquerdo
if (dbMyTeam == _selectedTeamName) {
// A equipa que escolhemos está guardada no 'my_team'
opponent = dbOppTeam;
myScore = dbMyScore;
oppScore = dbOppScore;
opponent = dbOppTeam; myScore = dbMyScore; oppScore = dbOppScore;
} else {
// A equipa que escolhemos está guardada no 'opponent_team'
opponent = dbMyTeam;
myScore = dbOppScore;
oppScore = dbMyScore;
opponent = dbMyTeam; myScore = dbOppScore; oppScore = dbMyScore;
}
// Limpa a data (Remove as horas e deixa só YYYY-MM-DD)
String rawDate = game['game_date']?.toString() ?? '---';
String date = rawDate.length >= 10 ? rawDate.substring(0, 10) : rawDate;
// Calcula Vitória, Empate ou Derrota para a equipa selecionada
String result = 'E';
if (myScore > oppScore) result = 'V';
if (myScore < oppScore) result = 'D';
return _buildGameHistoryCard(
context: context, // Usamos o context para o sf
opponent: opponent,
result: result,
myScore: myScore,
oppScore: oppScore,
date: date,
sf: sf,
topPts: game['top_pts_name'] ?? '---',
topAst: game['top_ast_name'] ?? '---',
topRbs: game['top_rbs_name'] ?? '---',
@@ -284,7 +253,7 @@ class _HomeScreenState extends State<HomeScreen> {
},
),
SizedBox(height: 20 * sf),
SizedBox(height: 20 * context.sf),
],
),
),
@@ -308,7 +277,7 @@ class _HomeScreenState extends State<HomeScreen> {
return {'pts_name': getBest(ptsMap), 'pts_val': getBestVal(ptsMap), 'ast_name': getBest(astMap), 'ast_val': getBestVal(astMap), 'rbs_name': getBest(rbsMap), 'rbs_val': getBestVal(rbsMap)};
}
Widget _buildStatCard({required String title, required String playerName, required String statValue, required String statLabel, required Color color, bool isHighlighted = false}) {
Widget _buildStatCard({required BuildContext context, required String title, required String playerName, required String statValue, required String statLabel, required Color color, bool isHighlighted = false}) {
return Card(
elevation: 4, margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14), side: isHighlighted ? const BorderSide(color: Colors.amber, width: 2) : BorderSide.none),
@@ -329,8 +298,7 @@ class _HomeScreenState extends State<HomeScreen> {
SizedBox(
width: double.infinity,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
fit: BoxFit.scaleDown, alignment: Alignment.centerLeft,
child: Text(playerName, style: TextStyle(fontSize: ch * 0.08, fontWeight: FontWeight.bold, color: Colors.white)),
),
),
@@ -340,8 +308,7 @@ class _HomeScreenState extends State<HomeScreen> {
Center(child: Text(statLabel, style: TextStyle(fontSize: ch * 0.05, color: Colors.white70))),
const Spacer(),
Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: ch * 0.035),
width: double.infinity, padding: EdgeInsets.symmetric(vertical: ch * 0.035),
decoration: BoxDecoration(color: Colors.white24, borderRadius: BorderRadius.circular(ch * 0.03)),
child: Center(child: Text('DETALHES', style: TextStyle(color: Colors.white, fontSize: ch * 0.05, fontWeight: FontWeight.bold)))
),
@@ -355,7 +322,7 @@ class _HomeScreenState extends State<HomeScreen> {
}
Widget _buildGameHistoryCard({
required String opponent, required String result, required int myScore, required int oppScore, required String date, required double sf,
required BuildContext context, required String opponent, required String result, required int myScore, required int oppScore, required String date,
required String topPts, required String topAst, required String topRbs, required String topDef, required String mvp
}) {
bool isWin = result == 'V';
@@ -363,44 +330,42 @@ class _HomeScreenState extends State<HomeScreen> {
Color statusColor = isWin ? Colors.green : (isDraw ? Colors.yellow.shade700 : Colors.red);
return Container(
margin: EdgeInsets.only(bottom: 14 * sf),
margin: EdgeInsets.only(bottom: 14 * context.sf),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade200),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.04), blurRadius: 8, offset: const Offset(0, 4))],
color: Colors.white, borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade200), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.04), blurRadius: 8, offset: const Offset(0, 4))],
),
child: Column(
children: [
Padding(
padding: EdgeInsets.all(14 * sf),
padding: EdgeInsets.all(14 * context.sf),
child: Row(
children: [
Container(
width: 36 * sf, height: 36 * sf,
width: 36 * context.sf, height: 36 * context.sf,
decoration: BoxDecoration(color: statusColor.withOpacity(0.15), shape: BoxShape.circle),
child: Center(child: Text(result, style: TextStyle(color: statusColor, fontWeight: FontWeight.bold, fontSize: 16 * sf))),
child: Center(child: Text(result, style: TextStyle(color: statusColor, fontWeight: FontWeight.bold, fontSize: 16 * context.sf))),
),
SizedBox(width: 14 * sf),
SizedBox(width: 14 * context.sf),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(date, style: TextStyle(fontSize: 11 * sf, color: Colors.grey, fontWeight: FontWeight.w600)),
SizedBox(height: 6 * sf),
Text(date, style: TextStyle(fontSize: 11 * context.sf, color: Colors.grey, fontWeight: FontWeight.w600)),
SizedBox(height: 6 * context.sf),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: Text(_selectedTeamName == "Selecionar Equipa" ? "Minha Equipa" : _selectedTeamName, style: TextStyle(fontSize: 14 * sf, fontWeight: FontWeight.bold), maxLines: 1, overflow: TextOverflow.ellipsis)),
Expanded(child: Text(_selectedTeamName == "Selecionar Equipa" ? "Minha Equipa" : _selectedTeamName, style: TextStyle(fontSize: 14 * context.sf, fontWeight: FontWeight.bold), maxLines: 1, overflow: TextOverflow.ellipsis)),
Padding(
padding: EdgeInsets.symmetric(horizontal: 8 * sf),
padding: EdgeInsets.symmetric(horizontal: 8 * context.sf),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8 * sf, vertical: 4 * sf),
padding: EdgeInsets.symmetric(horizontal: 8 * context.sf, vertical: 4 * context.sf),
decoration: BoxDecoration(color: Colors.grey.shade100, borderRadius: BorderRadius.circular(8)),
child: Text('$myScore - $oppScore', style: TextStyle(fontSize: 15 * sf, fontWeight: FontWeight.w900, letterSpacing: 1.5, color: Colors.black87)),
child: Text('$myScore - $oppScore', style: TextStyle(fontSize: 15 * context.sf, fontWeight: FontWeight.w900, letterSpacing: 1.5, color: Colors.black87)),
),
),
Expanded(child: Text(opponent, style: TextStyle(fontSize: 14 * sf, fontWeight: FontWeight.bold), textAlign: TextAlign.right, maxLines: 1, overflow: TextOverflow.ellipsis)),
Expanded(child: Text(opponent, style: TextStyle(fontSize: 14 * context.sf, fontWeight: FontWeight.bold), textAlign: TextAlign.right, maxLines: 1, overflow: TextOverflow.ellipsis)),
],
),
],
@@ -409,35 +374,29 @@ class _HomeScreenState extends State<HomeScreen> {
],
),
),
Divider(height: 1, color: Colors.grey.shade100, thickness: 1.5),
Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 16 * sf, vertical: 12 * sf),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(16), bottomRight: Radius.circular(16)),
),
width: double.infinity, padding: EdgeInsets.symmetric(horizontal: 16 * context.sf, vertical: 12 * context.sf),
decoration: BoxDecoration(color: Colors.grey.shade50, borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(16), bottomRight: Radius.circular(16))),
child: Column(
children: [
Row(
children: [
Expanded(child: _buildGridStatRow(Icons.workspace_premium, Colors.amber.shade700, "MVP", mvp, sf, isMvp: true)),
Expanded(child: _buildGridStatRow(Icons.shield, Colors.deepOrange.shade700, "Defesa", topDef, sf)),
Expanded(child: _buildGridStatRow(context, Icons.workspace_premium, Colors.amber.shade700, "MVP", mvp, isMvp: true)),
Expanded(child: _buildGridStatRow(context, Icons.shield, Colors.deepOrange.shade700, "Defesa", topDef)),
],
),
SizedBox(height: 8 * sf),
SizedBox(height: 8 * context.sf),
Row(
children: [
Expanded(child: _buildGridStatRow(Icons.bolt, Colors.blue.shade700, "Pontos", topPts, sf)),
Expanded(child: _buildGridStatRow(Icons.trending_up, Colors.purple.shade700, "Rebotes", topRbs, sf)),
Expanded(child: _buildGridStatRow(context, Icons.bolt, Colors.blue.shade700, "Pontos", topPts)),
Expanded(child: _buildGridStatRow(context, Icons.trending_up, Colors.purple.shade700, "Rebotes", topRbs)),
],
),
SizedBox(height: 8 * sf),
SizedBox(height: 8 * context.sf),
Row(
children: [
Expanded(child: _buildGridStatRow(Icons.star, Colors.green.shade700, "Assists", topAst, sf)),
Expanded(child: _buildGridStatRow(context, Icons.star, Colors.green.shade700, "Assists", topAst)),
const Expanded(child: SizedBox()),
],
),
@@ -449,22 +408,21 @@ class _HomeScreenState extends State<HomeScreen> {
);
}
Widget _buildGridStatRow(IconData icon, Color color, String label, String value, double sf, {bool isMvp = false}) {
Widget _buildGridStatRow(BuildContext context, IconData icon, Color color, String label, String value, {bool isMvp = false}) {
return Row(
children: [
Icon(icon, size: 14 * sf, color: color),
SizedBox(width: 4 * sf),
Text('$label: ', style: TextStyle(fontSize: 11 * sf, color: Colors.grey.shade600, fontWeight: FontWeight.bold)),
Icon(icon, size: 14 * context.sf, color: color),
SizedBox(width: 4 * context.sf),
Text('$label: ', style: TextStyle(fontSize: 11 * context.sf, color: Colors.grey.shade600, fontWeight: FontWeight.bold)),
Expanded(
child: Text(
value,
style: TextStyle(
fontSize: 11 * sf,
fontSize: 11 * context.sf,
color: isMvp ? Colors.amber.shade900 : Colors.black87,
fontWeight: FontWeight.bold
),
maxLines: 1,
overflow: TextOverflow.ellipsis
maxLines: 1, overflow: TextOverflow.ellipsis
)
),
],