Files
PlayMaker/lib/pages/home.dart
2026-06-11 09:52:06 +01:00

1118 lines
45 KiB
Dart

import 'package:flutter/material.dart';
import 'package:playmaker/classe/theme.dart';
import 'package:playmaker/grafico%20de%20pizza/grafico.dart';
import 'package:playmaker/pages/gamePage.dart';
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 'package:cached_network_image/cached_network_image.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../utils/size_extension.dart';
import 'settings_screen.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _selectedIndex = 0;
final TeamController _teamController = TeamController();
String? _selectedTeamId;
String _selectedTeamName = "Selecionar Equipa";
String? _selectedTeamLogo;
int _teamWins = 0;
int _teamLosses = 0;
int _teamDraws = 0;
final _supabase = Supabase.instance.client;
String? _avatarUrl;
bool _isMemoryLoaded = false;
@override
void initState() {
super.initState();
_loadUserAvatar();
_loadSelectedTeam();
}
String _prefsKey(String key) {
final userId = _supabase.auth.currentUser?.id ?? 'guest';
return '${key}_$userId';
}
Future<void> _loadSelectedTeam() async {
// 1. Carrega primeiro do SharedPreferences para resposta imediata
final prefs = await SharedPreferences.getInstance();
final savedId = prefs.getString(_prefsKey('last_team_id'));
if (savedId != null && mounted) {
setState(() {
_selectedTeamId = savedId;
_selectedTeamName =
prefs.getString(_prefsKey('last_team_name')) ?? "Selecionar Equipa";
_selectedTeamLogo = prefs.getString(_prefsKey('last_team_logo'));
_teamWins = prefs.getInt(_prefsKey('last_team_wins')) ?? 0;
_teamLosses = prefs.getInt(_prefsKey('last_team_losses')) ?? 0;
_teamDraws = prefs.getInt(_prefsKey('last_team_draws')) ?? 0;
});
}
// 2. Depois sincroniza com o Supabase para ter os dados mais recentes
final userId = _supabase.auth.currentUser?.id;
if (userId == null) return;
try {
final profile = await _supabase
.from('profiles')
.select('selected_team_id')
.eq('id', userId)
.maybeSingle();
if (profile != null && profile['selected_team_id'] != null) {
final dbTeamId = profile['selected_team_id'].toString();
final teamData = await _supabase
.from('teams')
.select()
.eq('id', dbTeamId)
.maybeSingle();
if (teamData != null && mounted) {
setState(() {
_selectedTeamId = teamData['id'].toString();
_selectedTeamName = teamData['name'] ?? 'Desconhecido';
_selectedTeamLogo = teamData['image_url'];
_teamWins =
int.tryParse(teamData['wins']?.toString() ?? '0') ?? 0;
_teamLosses =
int.tryParse(teamData['losses']?.toString() ?? '0') ?? 0;
_teamDraws =
int.tryParse(teamData['draws']?.toString() ?? '0') ?? 0;
});
await _saveToSharedPreferences();
} else if (mounted) {
// Se o utilizador não tem equipa selecionada, limpa valores locais para não mostrar dados de outra conta.
setState(() {
_selectedTeamId = null;
_selectedTeamName = 'Selecionar Equipa';
_selectedTeamLogo = null;
_teamWins = 0;
_teamLosses = 0;
_teamDraws = 0;
});
await _clearSelectedTeamFromPreferences();
}
}
} catch (e) {
debugPrint("Erro ao carregar equipa do Supabase: $e");
}
}
Future<void> _saveSelectedTeam() async {
await _saveToSharedPreferences();
final userId = _supabase.auth.currentUser?.id;
if (userId != null && _selectedTeamId != null) {
try {
await _supabase.from('profiles').upsert({
'id': userId,
'selected_team_id': _selectedTeamId,
});
} catch (e) {
debugPrint("Erro ao guardar equipa no Supabase: $e");
}
}
}
Future<void> _saveToSharedPreferences() async {
final prefs = await SharedPreferences.getInstance();
if (_selectedTeamId != null) {
await prefs.setString(_prefsKey('last_team_id'), _selectedTeamId!);
await prefs.setString(_prefsKey('last_team_name'), _selectedTeamName);
if (_selectedTeamLogo != null && _selectedTeamLogo!.isNotEmpty) {
await prefs.setString(_prefsKey('last_team_logo'), _selectedTeamLogo!);
} else {
await prefs.remove(_prefsKey('last_team_logo'));
}
await prefs.setInt(_prefsKey('last_team_wins'), _teamWins);
await prefs.setInt(_prefsKey('last_team_losses'), _teamLosses);
await prefs.setInt(_prefsKey('last_team_draws'), _teamDraws);
}
}
Future<void> _clearSelectedTeamFromPreferences() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_prefsKey('last_team_id'));
await prefs.remove(_prefsKey('last_team_name'));
await prefs.remove(_prefsKey('last_team_logo'));
await prefs.remove(_prefsKey('last_team_wins'));
await prefs.remove(_prefsKey('last_team_losses'));
await prefs.remove(_prefsKey('last_team_draws'));
}
Future<void> _loadUserAvatar() async {
final prefs = await SharedPreferences.getInstance();
final savedUrl = prefs.getString(_prefsKey('meu_avatar_guardado'));
if (mounted) {
setState(() {
if (savedUrl != null) _avatarUrl = savedUrl;
_isMemoryLoaded = true;
});
}
final userId = _supabase.auth.currentUser?.id;
if (userId == null) return;
try {
final data = await _supabase
.from('profiles')
.select('avatar_url')
.eq('id', userId)
.maybeSingle();
if (mounted && data != null && data['avatar_url'] != null) {
final urlDoSupabase = data['avatar_url'];
if (urlDoSupabase != savedUrl) {
await prefs.setString(_prefsKey('meu_avatar_guardado'), urlDoSupabase);
setState(() {
_avatarUrl = urlDoSupabase;
});
}
}
} catch (e) {
debugPrint("Erro ao carregar avatar na Home: $e");
}
}
@override
Widget build(BuildContext context) {
// A StatusPage recebe a equipa selecionada diretamente como parâmetro.
// Quando _selectedTeamId muda aqui, o didUpdateWidget da StatusPage
// atualiza automaticamente — sem precisar de ValueKey nem rebuild forçado.
final List<Widget> pages = [
_buildHomeContent(context),
const GamePage(),
const TeamsPage(),
StatusPage(
initialTeamId: _selectedTeamId,
initialTeamName: _selectedTeamName,
initialTeamLogo: _selectedTeamLogo,
),
];
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: AppBar(
title: Text('PlayMaker',
style: TextStyle(
fontSize: 20 * context.sf, fontWeight: FontWeight.bold)),
backgroundColor: AppTheme.primaryRed,
foregroundColor: Colors.white,
elevation: 0,
leading: Padding(
padding: EdgeInsets.all(10.0 * context.sf),
child: InkWell(
borderRadius: BorderRadius.circular(100),
onTap: () async {
debugPrint('Home: settings button tapped');
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SettingsScreen()),
);
_loadUserAvatar();
},
child: !_isMemoryLoaded
? CircleAvatar(
backgroundColor: Colors.white.withOpacity(0.2))
: _avatarUrl != null && _avatarUrl!.isNotEmpty
? CachedNetworkImage(
imageUrl: _avatarUrl!,
fadeInDuration: Duration.zero,
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundColor: Colors.white.withOpacity(0.2),
backgroundImage: imageProvider,
),
placeholder: (context, url) => CircleAvatar(
backgroundColor: Colors.white.withOpacity(0.2)),
errorWidget: (context, url, error) => CircleAvatar(
backgroundColor: Colors.white.withOpacity(0.2),
child: Icon(Icons.person,
color: Colors.white, size: 20 * context.sf),
),
)
: CircleAvatar(
backgroundColor: Colors.white.withOpacity(0.2),
child: Icon(Icons.person,
color: Colors.white, size: 20 * context.sf),
),
),
),
),
body: IndexedStack(index: _selectedIndex, children: pages),
bottomNavigationBar: NavigationBar(
selectedIndex: _selectedIndex,
onDestinationSelected: (index) {
setState(() => _selectedIndex = index);
if (index == 0) {
_loadSelectedTeam();
}
},
backgroundColor: Theme.of(context).colorScheme.surface,
surfaceTintColor: Theme.of(context).colorScheme.surfaceTint,
elevation: 1,
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'),
NavigationDestination(
icon: Icon(Icons.people_outline),
selectedIcon: Icon(Icons.people),
label: 'Equipas'),
NavigationDestination(
icon: Icon(Icons.insights_outlined),
selectedIcon: Icon(Icons.insights),
label: 'Status'),
],
),
);
}
void _showTeamSelector(BuildContext context) {
debugPrint('showTeamSelector called');
final isWide = MediaQuery.of(context).size.width >= 900;
Widget builderContent(BuildContext ctx) {
return StreamBuilder<List<Map<String, dynamic>>>(
stream: _teamController.teamsStream,
builder: (context, snapshot) {
if (!snapshot.hasData &&
snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox(
height: 200,
child: Center(child: CircularProgressIndicator()));
}
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return SizedBox(
height: 200 * context.sf,
child: Center(
child: Text("Nenhuma equipa criada.",
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface))),
);
}
final teams = snapshot.data!;
return ListView.builder(
shrinkWrap: true,
itemCount: teams.length,
itemBuilder: (context, index) {
final team = teams[index];
final String? logoUrl = team['image_url'];
return ListTile(
leading: ClipOval(
child: Container(
width: 36 * context.sf,
height: 36 * context.sf,
color: AppTheme.primaryRed.withOpacity(0.1),
child: (logoUrl != null && logoUrl.isNotEmpty)
? CachedNetworkImage(
imageUrl: logoUrl,
fit: BoxFit.cover,
placeholder: (context, url) => Icon(
Icons.shield,
color: AppTheme.primaryRed,
size: 20 * context.sf),
errorWidget: (context, url, error) => Icon(
Icons.shield,
color: AppTheme.primaryRed,
size: 20 * context.sf),
)
: Icon(Icons.shield,
color: AppTheme.primaryRed,
size: 20 * context.sf),
),
),
title: Text(
team['name'] ?? 'Sem Nome',
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold),
),
onTap: () async {
setState(() {
_selectedTeamId = team['id'].toString();
_selectedTeamName = team['name'] ?? 'Desconhecido';
_selectedTeamLogo = logoUrl;
_teamWins = int.tryParse(
team['wins']?.toString() ?? '0') ??
0;
_teamLosses = int.tryParse(
team['losses']?.toString() ?? '0') ??
0;
_teamDraws = int.tryParse(
team['draws']?.toString() ?? '0') ??
0;
});
await _saveSelectedTeam();
if (context.mounted) Navigator.pop(context);
},
);
},
);
},
);
}
if (isWide) {
showDialog(
context: context,
builder: (ctx) {
return Dialog(
insetPadding: EdgeInsets.symmetric(horizontal: 200 * context.sf, vertical: 80 * context.sf),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12 * context.sf)),
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: 600 * context.sf),
child: Padding(
padding: EdgeInsets.all(16 * context.sf),
child: builderContent(ctx),
),
),
);
},
);
} else {
showModalBottomSheet(
context: context,
backgroundColor: Theme.of(context).colorScheme.surface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20 * context.sf)),
),
builder: (context) => builderContent(context),
);
}
}
Widget _buildHomeContent(BuildContext context) {
final double wScreen = MediaQuery.of(context).size.width;
final double cardHeight = wScreen * 0.5;
final textColor = Theme.of(context).colorScheme.onSurface;
return StreamBuilder<List<Map<String, dynamic>>>(
stream: _selectedTeamId != null
? _supabase
.from('player_stats_with_names')
.stream(primaryKey: ['id']).eq('team_id', _selectedTeamId!)
: const Stream.empty(),
builder: (context, snapshot) {
Map<String, dynamic> leaders =
_calculateLeaders(snapshot.data ?? []);
return SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 22.0 * context.sf,
vertical: 16.0 * context.sf),
child: LayoutBuilder(
builder: (context, constraints) {
final bool isWide = constraints.maxWidth >= 1100;
final double effectiveCardHeight =
isWide ? 280 * context.sf : cardHeight;
Widget statsSection = Column(
children: [
SizedBox(
height: effectiveCardHeight,
child: Row(
children: [
Expanded(
child: _buildStatCard(
context: context,
title: 'Mais Pontos',
playerName: leaders['pts_name'],
statValue: leaders['pts_val'].toString(),
statLabel: 'TOTAL',
color: AppTheme.statPtsBg,
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: AppTheme.statAstBg)),
],
),
),
SizedBox(height: 12 * context.sf),
SizedBox(
height: effectiveCardHeight,
child: Row(
children: [
Expanded(
child: _buildStatCard(
context: context,
title: 'Rebotes',
playerName: leaders['rbs_name'],
statValue: leaders['rbs_val'].toString(),
statLabel: 'TOTAL',
color: AppTheme.statRebBg)),
SizedBox(width: 12 * context.sf),
Expanded(
child: PieChartCard(
victories: _teamWins,
defeats: _teamLosses,
draws: _teamDraws,
title: 'DESEMPENHO',
subtitle: 'Temporada',
backgroundColor: AppTheme.statPieBg,
sf: context.sf)),
],
),
),
],
);
Widget historySection = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Histórico de Jogos',
style: TextStyle(
fontSize: 20 * context.sf,
fontWeight: FontWeight.bold,
color: textColor)),
SizedBox(height: 16 * context.sf),
_selectedTeamName == "Selecionar Equipa"
? Container(
width: double.infinity,
padding: EdgeInsets.all(24.0 * context.sf),
decoration: BoxDecoration(
color: Theme.of(context).cardTheme.color ??
Colors.white,
borderRadius:
BorderRadius.circular(16 * context.sf),
border: Border.all(
color: Colors.grey.withOpacity(0.1)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 10,
offset: const Offset(0, 4))
],
),
child: Column(
children: [
Container(
padding: EdgeInsets.all(18 * context.sf),
decoration: BoxDecoration(
color: AppTheme.primaryRed
.withOpacity(0.08),
shape: BoxShape.circle),
child: Icon(Icons.shield_outlined,
color: AppTheme.primaryRed,
size: 42 * context.sf),
),
SizedBox(height: 20 * context.sf),
Text("Nenhuma Equipa Ativa",
style: TextStyle(
fontSize: 18 * context.sf,
fontWeight: FontWeight.bold,
color: textColor)),
SizedBox(height: 8 * context.sf),
Text(
"Escolha uma equipa no seletor acima para ver as estatísticas e o histórico.",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13 * context.sf,
color: Colors.grey.shade600,
height: 1.4),
),
SizedBox(height: 24 * context.sf),
SizedBox(
width: double.infinity,
height: 48 * context.sf,
child: ElevatedButton.icon(
onPressed: () => _showTeamSelector(context),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.primaryRed,
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
10 * context.sf)),
),
icon: Icon(Icons.touch_app,
size: 20 * context.sf),
label: Text("Selecionar Agora",
style: TextStyle(
fontSize: 15 * context.sf,
fontWeight: FontWeight.bold)),
),
),
],
),
)
: StreamBuilder<List<Map<String, dynamic>>>(
stream: _supabase
.from('games')
.stream(primaryKey: ['id'])
.order('game_date', ascending: false),
builder: (context, gameSnapshot) {
if (gameSnapshot.hasError) {
return Text(
"Erro: ${gameSnapshot.error}",
style: const TextStyle(
color: Colors.red));
}
if (!gameSnapshot.hasData &&
gameSnapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator());
}
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() ?? '';
return (myT == _selectedTeamName ||
oppT == _selectedTeamName) &&
status == 'Terminado';
}).take(3).toList();
if (gamesList.isEmpty) {
return Container(
width: double.infinity,
padding: EdgeInsets.all(20 * context.sf),
decoration: BoxDecoration(
color: Theme.of(context).cardTheme.color,
borderRadius: BorderRadius.circular(14),
),
alignment: Alignment.center,
child: const Text(
"Ainda não há jogos terminados.",
style: TextStyle(color: Colors.grey)),
);
}
return Column(
children: gamesList.map((game) {
String dbMyTeam =
game['my_team']?.toString() ?? '';
String dbOppTeam =
game['opponent_team']?.toString() ?? '';
int dbMyScore = int.tryParse(
game['my_score']?.toString() ??
'0') ??
0;
int dbOppScore = int.tryParse(
game['opponent_score']
?.toString() ??
'0') ??
0;
String opponent;
int myScore;
int oppScore;
if (dbMyTeam == _selectedTeamName) {
opponent = dbOppTeam;
myScore = dbMyScore;
oppScore = dbOppScore;
} else {
opponent = dbMyTeam;
myScore = dbOppScore;
oppScore = dbMyScore;
}
String rawDate =
game['game_date']?.toString() ?? '---';
String date = rawDate.length >= 10
? rawDate.substring(0, 10)
: rawDate;
String result = myScore > oppScore
? 'V'
: (myScore < oppScore ? 'D' : 'E');
return _buildGameHistoryCard(
context: context,
opponent: opponent,
result: result,
myScore: myScore,
oppScore: oppScore,
date: date,
topPts:
game['top_pts_name'] ?? '---',
topAst:
game['top_ast_name'] ?? '---',
topRbs:
game['top_rbs_name'] ?? '---',
topDef:
game['top_def_name'] ?? '---',
mvp: game['mvp_name'] ?? '---',
);
}).toList(),
);
},
),
],
);
final Widget teamSelector = InkWell(
onTap: () => _showTeamSelector(context),
child: Container(
padding: EdgeInsets.all(12 * context.sf),
decoration: BoxDecoration(
color: Theme.of(context).cardTheme.color,
borderRadius: BorderRadius.circular(15 * context.sf),
border: Border.all(color: Colors.grey.withOpacity(0.2)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(children: [
(_selectedTeamLogo != null && _selectedTeamLogo!.isNotEmpty)
? ClipOval(
child: CachedNetworkImage(
imageUrl: _selectedTeamLogo!,
width: 24 * context.sf,
height: 24 * context.sf,
fit: BoxFit.cover,
placeholder: (context, url) => Icon(
Icons.shield,
color: AppTheme.primaryRed,
size: 24 * context.sf),
errorWidget: (context, url, error) => Icon(
Icons.shield,
color: AppTheme.primaryRed,
size: 24 * context.sf),
),
)
: Icon(Icons.shield, color: AppTheme.primaryRed, size: 24 * context.sf),
SizedBox(width: 10 * context.sf),
Text(_selectedTeamName,
style: TextStyle(fontSize: 16 * context.sf, fontWeight: FontWeight.bold, color: textColor)),
]),
Icon(Icons.arrow_drop_down, color: textColor),
],
),
),
);
Widget mainContent;
if (isWide) {
mainContent = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
teamSelector,
SizedBox(height: 20 * context.sf),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(flex: 5, child: statsSection),
SizedBox(width: 20 * context.sf),
Expanded(flex: 6, child: historySection),
],
),
],
);
} else {
mainContent = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
teamSelector,
SizedBox(height: 20 * context.sf),
statsSection,
SizedBox(height: 40 * context.sf),
historySection,
],
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
mainContent,
SizedBox(height: 20 * context.sf),
],
);
},
),
),
);
},
);
}
Map<String, dynamic> _calculateLeaders(List<Map<String, dynamic>> data) {
Map<String, int> ptsMap = {};
Map<String, int> astMap = {};
Map<String, int> rbsMap = {};
Map<String, String> namesMap = {};
for (var row in data) {
String pid = row['member_id']?.toString() ?? "unknown";
namesMap[pid] = row['player_name']?.toString() ?? "Desconhecido";
ptsMap[pid] = (ptsMap[pid] ?? 0) +
(int.tryParse(row['pts']?.toString() ?? '0') ?? 0);
astMap[pid] = (astMap[pid] ?? 0) +
(int.tryParse(row['ast']?.toString() ?? '0') ?? 0);
rbsMap[pid] = (rbsMap[pid] ?? 0) +
(int.tryParse(row['rbs']?.toString() ?? '0') ?? 0);
}
if (ptsMap.isEmpty) {
return {
'pts_name': '---',
'pts_val': 0,
'ast_name': '---',
'ast_val': 0,
'rbs_name': '---',
'rbs_val': 0,
};
}
String getBest(Map<String, int> map) {
if (map.isEmpty) return '---';
return namesMap[
map.entries.reduce((a, b) => a.value > b.value ? a : b).key] ??
'---';
}
int getBestVal(Map<String, int> map) {
if (map.isEmpty) return 0;
return map.values.reduce((a, b) => a > b ? a : b);
}
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 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: AppTheme.warningAmber, width: 2)
: BorderSide.none,
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [color.withOpacity(0.9), color],
),
),
child: LayoutBuilder(
builder: (context, constraints) {
final double ch = constraints.maxHeight;
final double cw = constraints.maxWidth;
return Padding(
padding: EdgeInsets.all(cw * 0.06),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title.toUpperCase(),
style: TextStyle(
fontSize: ch * 0.06,
fontWeight: FontWeight.bold,
color: Colors.white70),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: ch * 0.011),
SizedBox(
width: double.infinity,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(playerName,
style: TextStyle(
fontSize: ch * 0.08,
fontWeight: FontWeight.bold,
color: Colors.white)),
),
),
const Spacer(),
Center(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
statValue,
style: TextStyle(
fontSize: ch * 0.18,
fontWeight: FontWeight.bold,
color: Colors.white,
height: 1.0),
),
),
),
SizedBox(height: ch * 0.015),
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),
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))),
),
],
),
);
},
),
),
);
}
Widget _buildGameHistoryCard({
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';
bool isDraw = result == 'E';
Color statusColor = isWin
? AppTheme.successGreen
: (isDraw ? AppTheme.warningAmber : AppTheme.oppTeamRed);
final bgColor = Theme.of(context).cardTheme.color;
final textColor = Theme.of(context).colorScheme.onSurface;
return Container(
margin: EdgeInsets.only(bottom: 14 * context.sf),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.withOpacity(0.1)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 8,
offset: const Offset(0, 4))
],
),
child: Column(
children: [
Padding(
padding: EdgeInsets.all(14 * context.sf),
child: Row(
children: [
Container(
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 * context.sf))),
),
SizedBox(width: 14 * context.sf),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
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 * context.sf,
fontWeight: FontWeight.bold,
color: textColor),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Padding(
padding: EdgeInsets.symmetric(
horizontal: 8 * context.sf),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 8 * context.sf,
vertical: 4 * context.sf),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.05),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'$myScore - $oppScore',
style: TextStyle(
fontSize: 15 * context.sf,
fontWeight: FontWeight.w900,
letterSpacing: 1.5,
color: textColor),
),
),
),
Expanded(
child: Text(
opponent,
style: TextStyle(
fontSize: 14 * context.sf,
fontWeight: FontWeight.bold,
color: textColor),
textAlign: TextAlign.right,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
],
),
),
],
),
),
Divider(
height: 1,
color: Colors.grey.withOpacity(0.1),
thickness: 1.5),
Container(
width: double.infinity,
padding: EdgeInsets.symmetric(
horizontal: 16 * context.sf, vertical: 12 * context.sf),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(16),
bottomRight: Radius.circular(16)),
),
child: Column(
children: [
Row(children: [
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 * context.sf),
Row(children: [
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 * context.sf),
Row(children: [
Expanded(
child: _buildGridStatRow(context, Icons.star,
Colors.green.shade700, "Assists", topAst)),
const Expanded(child: SizedBox()),
]),
],
),
),
],
),
);
}
Widget _buildGridStatRow(BuildContext context, IconData icon, Color color,
String label, String value,
{bool isMvp = false}) {
return Row(
children: [
Icon(icon, size: 14 * context.sf, color: color),
SizedBox(width: 4 * context.sf),
Text('$label: ',
style: TextStyle(
fontSize: 11 * context.sf,
color: Colors.grey,
fontWeight: FontWeight.bold)),
Expanded(
child: Text(
value,
style: TextStyle(
fontSize: 11 * context.sf,
color: isMvp
? AppTheme.warningAmber
: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
);
}
}