pagina da dashboard com cards

main
230404 2025-12-09 17:19:35 +00:00
parent dc3a7840bb
commit bd89eba772
6 changed files with 852 additions and 170 deletions

View File

@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
class HomeConfig {
// Dimensões dos cards
static const double cardwidthPadding = 400;
static const double cardheightPadding = 300;
// Cores principais
static const Color primaryColor = Colors.orange;
static const Color secondaryColor = Colors.blue;
static const Color accentColor = Colors.green;
// Estilos de texto
static TextStyle titleStyle = TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
);
static TextStyle subtitleStyle = TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(0.8),
);
static TextStyle statValueStyle = TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Colors.white,
);
static TextStyle statLabelStyle = TextStyle(
fontSize: 12,
color: Colors.white.withOpacity(0.8),
letterSpacing: 1.5,
);
}

View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:playmaker/grafico%20de%20pizza/dados_grafico.dart';
class PieChartController extends ChangeNotifier {
PieChartData _chartData = PieChartData(victories: 25, defeats: 10);
PieChartData get chartData => _chartData;
void updateData({int? victories, int? defeats, int? draws}) {
_chartData = PieChartData(
victories: victories ?? _chartData.victories,
defeats: defeats ?? _chartData.defeats,
draws: draws ?? _chartData.draws,
);
notifyListeners();
}
void incrementVictories() {
updateData(victories: _chartData.victories + 1);
}
void incrementDefeats() {
updateData(defeats: _chartData.defeats + 1);
}
void reset() {
updateData(victories: 0, defeats: 0, draws: 0);
}
}

View File

@ -0,0 +1,26 @@
class PieChartData {
final int victories;
final int defeats;
final int draws;
const PieChartData({
required this.victories,
required this.defeats,
this.draws = 0,
});
int get total => victories + defeats + draws;
double get victoryPercentage => total > 0 ? victories / total : 0;
double get defeatPercentage => total > 0 ? defeats / total : 0;
double get drawPercentage => total > 0 ? draws / total : 0;
Map<String, dynamic> toJson() => {
'victories': victories,
'defeats': defeats,
'draws': draws,
'total': total,
'victoryPercentage': victoryPercentage,
'defeatPercentage': defeatPercentage,
};
}

View File

@ -0,0 +1,404 @@
import 'package:flutter/material.dart';
import 'package:playmaker/grafico%20de%20pizza/widgets/grafico_widgets.dart';
import 'dados_grafico.dart';
import 'controllers/contollers_grafico.dart';
import 'package:playmaker/classe/home.config.dart';
class PieChartCard extends StatefulWidget {
final PieChartController? controller;
final String title;
final String subtitle;
final Color backgroundColor;
final VoidCallback? onTap;
const PieChartCard({
super.key,
this.controller,
this.title = 'DESEMPENHO',
this.subtitle = 'Vitórias vs Derrotas',
this.onTap, required this.backgroundColor,
});
@override
State<PieChartCard> createState() => _PieChartCardState();
}
class _PieChartCardState extends State<PieChartCard> with SingleTickerProviderStateMixin {
late PieChartController _controller;
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = widget.controller ?? PieChartController();
_animationController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeOutBack,
),
);
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final data = _controller.chartData;
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.scale(
scale: 0.95 + (_animation.value * 0.05),
child: Opacity(
opacity: _animation.value,
child: child,
),
);
},
child: Container(
width: HomeConfig.cardwidthPadding,
height: HomeConfig.cardheightPadding,
child: Card(
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: InkWell(
onTap: widget.onTap,
borderRadius: BorderRadius.circular(20),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
widget.backgroundColor.withOpacity(0.9),
widget.backgroundColor.withOpacity(0.7),
],
),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Cabeçalho compacto
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.title,
style: TextStyle(
fontSize: 14, // Pequeno
fontWeight: FontWeight.bold,
color: Colors.white.withOpacity(0.9),
letterSpacing: 1.5,
),
),
SizedBox(height: 2), // Muito pequeno
Text(
widget.subtitle,
style: TextStyle(
fontSize: 16, // Moderado
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
Container(
padding: EdgeInsets.all(6), // Pequeno
decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.8),
shape: BoxShape.circle,
),
child: Icon(
Icons.pie_chart,
size: 18, // Pequeno
color: Colors.white,
),
),
],
),
SizedBox(height: 8), // Pequeno espaço
// Conteúdo principal - COMPACTO E CONTROLADO
Expanded(
child: Row(
children: [
// Gráfico de pizza
Expanded(
flex: 3,
child: Center(
child: PieChartWidget(
victoryPercentage: data.victoryPercentage,
defeatPercentage: data.defeatPercentage,
drawPercentage: data.drawPercentage,
size: 130, // Pequeno para caber
),
),
),
SizedBox(width: 8), // Pequeno espaço
// Estatísticas - EXTREMAMENTE COMPACTAS
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Vitórias - Layout horizontal ultra compacto
Container(
margin: EdgeInsets.only(bottom: 6), // Muito pequeno
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Número
Text(
data.victories.toString(),
style: TextStyle(
fontSize: 26, // Pequeno mas legível
fontWeight: FontWeight.bold,
color: Colors.green,
height: 0.8,
),
),
SizedBox(width: 6), // Pequeno
// Info compacta
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Container(
width: 8,
height: 8,
margin: EdgeInsets.only(right: 4),
decoration: BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
),
),
Text(
'VIT',
style: TextStyle(
fontSize: 10, // Muito pequeno
color: Colors.white.withOpacity(0.8),
fontWeight: FontWeight.w600,
),
),
],
),
SizedBox(height: 2),
Text(
'${(data.victoryPercentage * 100).toStringAsFixed(0)}%',
style: TextStyle(
fontSize: 12, // Pequeno
color: Colors.green,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
// Divisor sutil
Container(
height: 0.5,
color: Colors.white.withOpacity(0.1),
margin: EdgeInsets.symmetric(vertical: 4),
),
// Derrotas
Container(
margin: EdgeInsets.only(bottom: 6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
data.defeats.toString(),
style: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Colors.red,
height: 0.8,
),
),
SizedBox(width: 6),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Container(
width: 8,
height: 8,
margin: EdgeInsets.only(right: 4),
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
),
Text(
'DER',
style: TextStyle(
fontSize: 10,
color: Colors.white.withOpacity(0.8),
fontWeight: FontWeight.w600,
),
),
],
),
SizedBox(height: 2),
Text(
'${(data.defeatPercentage * 100).toStringAsFixed(0)}%',
style: TextStyle(
fontSize: 12,
color: Colors.red,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
// Divisor sutil
Container(
height: 0.5,
color: Colors.white.withOpacity(0.1),
margin: EdgeInsets.symmetric(vertical: 4),
),
// Total
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
data.total.toString(),
style: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Colors.white,
height: 0.8,
),
),
SizedBox(width: 6),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Container(
width: 8,
height: 8,
margin: EdgeInsets.only(right: 4),
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
),
Text(
'TOT',
style: TextStyle(
fontSize: 10,
color: Colors.white.withOpacity(0.8),
fontWeight: FontWeight.w600,
),
),
],
),
SizedBox(height: 2),
Text(
'100%',
style: TextStyle(
fontSize: 12,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
],
),
),
],
),
),
SizedBox(height: 10), // Espaço controlado
// Win rate - Sempre visível e não sobreposto
Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
data.victoryPercentage > 0.5
? Icons.trending_up
: Icons.trending_down,
color: data.victoryPercentage > 0.5
? Colors.green
: Colors.red,
size: 18, // Pequeno
),
SizedBox(width: 8),
Text(
'Win Rate: ${(data.victoryPercentage * 100).toStringAsFixed(1)}%',
style: TextStyle(
fontSize: 14, // Pequeno
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
),
],
),
),
),
),
),
),
);
}
}

View File

@ -0,0 +1,144 @@
import 'package:flutter/material.dart';
import 'dart:math' as math;
class PieChartWidget extends StatelessWidget {
final double victoryPercentage;
final double defeatPercentage;
final double drawPercentage;
final double size;
const PieChartWidget({
super.key,
required this.victoryPercentage,
required this.defeatPercentage,
this.drawPercentage = 0,
this.size = 140, // Aumentado para 400x300
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: size,
height: size,
child: CustomPaint(
painter: _PieChartPainter(
victoryPercentage: victoryPercentage,
defeatPercentage: defeatPercentage,
drawPercentage: drawPercentage,
),
child: _buildCenterLabels(),
),
);
}
Widget _buildCenterLabels() {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${(victoryPercentage * 100).toStringAsFixed(1)}%',
style: TextStyle(
fontSize: size * 0.2, // Tamanho responsivo (28px para 140px)
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(height: 4),
Text(
'Vitórias',
style: TextStyle(
fontSize: size * 0.1, // Tamanho responsivo (14px para 140px)
color: Colors.white.withOpacity(0.8),
),
),
],
),
);
}
}
class _PieChartPainter extends CustomPainter {
final double victoryPercentage;
final double defeatPercentage;
final double drawPercentage;
_PieChartPainter({
required this.victoryPercentage,
required this.defeatPercentage,
required this.drawPercentage,
});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 2 - 5;
// Cores
const victoryColor = Colors.green;
const defeatColor = Colors.red;
const drawColor = Colors.yellow;
const borderColor = Colors.white30;
double startAngle = 0;
// Vitórias (verde)
if (victoryPercentage > 0) {
final sweepAngle = 2 * math.pi * victoryPercentage;
_drawSector(canvas, center, radius, startAngle, sweepAngle, victoryColor);
startAngle += sweepAngle;
}
// Empates (amarelo)
if (drawPercentage > 0) {
final sweepAngle = 2 * math.pi * drawPercentage;
_drawSector(canvas, center, radius, startAngle, sweepAngle, drawColor);
startAngle += sweepAngle;
}
// Derrotas (vermelho)
if (defeatPercentage > 0) {
final sweepAngle = 2 * math.pi * defeatPercentage;
_drawSector(canvas, center, radius, startAngle, sweepAngle, defeatColor);
}
// Borda
final borderPaint = Paint()
..color = borderColor
..style = PaintingStyle.stroke
..strokeWidth = 2;
canvas.drawCircle(center, radius, borderPaint);
}
void _drawSector(Canvas canvas, Offset center, double radius,
double startAngle, double sweepAngle, Color color) {
final paint = Paint()
..color = color
..style = PaintingStyle.fill;
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
startAngle,
sweepAngle,
true,
paint,
);
// Linha divisória
if (sweepAngle < 2 * math.pi) {
final linePaint = Paint()
..color = Colors.white.withOpacity(0.5)
..style = PaintingStyle.stroke
..strokeWidth = 1.5;
final lineX = center.dx + radius * math.cos(startAngle);
final lineY = center.dy + radius * math.sin(startAngle);
canvas.drawLine(center, Offset(lineX, lineY), linePaint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

View File

@ -1,4 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:playmaker/classe/home.config.dart';
import 'package:playmaker/grafico%20de%20pizza/grafico.dart';
class HomeScreen extends StatelessWidget { class HomeScreen extends StatelessWidget {
const HomeScreen({super.key}); const HomeScreen({super.key});
@ -8,149 +10,75 @@ class HomeScreen extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('PlayMaker'), title: const Text('PlayMaker'),
backgroundColor: Colors.orange, backgroundColor: HomeConfig.primaryColor,
foregroundColor: Colors.white, foregroundColor: Colors.white,
leading: IconButton(
icon: const Icon(Icons.person),
onPressed: () {},
),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Padding( child: Padding(
padding: const EdgeInsets.all(40.0), padding: const EdgeInsets.all(20.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
// Título // Primeira linha com 2 cards
const Text( Row(
'PLAYMAKER', mainAxisAlignment: MainAxisAlignment.center,
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.orange,
letterSpacing: 2,
),
),
const SizedBox(height: 40),
// Botão PRINCIPAL para iniciar jogo
SizedBox(
width: double.infinity,
height: 250,
child: ElevatedButton(
onPressed: () {
// Navegar para tela de novo jogo
print('Iniciar novo jogo!');
// Navigator.push(context, MaterialPageRoute(
// builder: (context) => NovoJogoScreen()
// ));
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
elevation: 10,
shadowColor: Colors.orange.withOpacity(0.5),
),
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.sports_basketball,
size: 80,
color: Colors.white,
),
SizedBox(height: 20),
Text(
'NOVO JOGO',
style: TextStyle(
fontSize: 38,
fontWeight: FontWeight.w900,
color: Colors.white,
letterSpacing: 2,
),
),
],
),
),
),
const SizedBox(height: 40),
// Texto explicativo
const Text(
'Toque para iniciar uma nova partida',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
fontStyle: FontStyle.italic,
),
),
const SizedBox(height: 50),
// Seção de opções rápidas
const Text(
'Acesso Rápido',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.orange,
),
),
const SizedBox(height: 20),
// Grid de opções
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
childAspectRatio: 1.3,
mainAxisSpacing: 20,
crossAxisSpacing: 20,
children: [ children: [
// Estatísticas // Card 1 - Estatísticas de Basquete
_buildQuickOption( _buildStatCard(
icon: Icons.insights, title: 'Mais Pontos',
title: 'Estatísticas', playerName: 'Michael Jordan',
subtitle: 'Ver desempenho', statValue: '34.5',
color: Colors.green, statLabel: 'PPG',
onTap: () { color: Colors.blue[800]!,
print('Ver estatísticas'); icon: Icons.sports_basketball,
// Pode navegar para estatísticas ou mudar para índice 3 no navigation isHighlighted: true,
},
), ),
// Equipas SizedBox(width: 20),
_buildQuickOption(
icon: Icons.people, // Card 2 - Estatísticas de Futebol
title: 'Minhas Equipas', _buildStatCard(
subtitle: 'Gerir equipas', title: 'Mais Assistências',
color: Colors.blue, playerName: 'magic Johnson',
onTap: () { statValue: '12.8',
print('Ver equipas'); statLabel: 'APG',
// Pode navegar para equipas ou mudar para índice 2 no navigation color: Colors.green[800]!,
}, icon: Icons.sports_soccer,
isHighlighted: false,
),
],
),
SizedBox(height: 20),
// Segunda linha com 2 cards
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Card 3 - Estatísticas de Tênis
_buildStatCard(
title: 'Mais Rebotes',
playerName: 'Denis Rodman',
statValue: '15.3',
statLabel: 'RPG',
color: Colors.purple[800]!,
icon: Icons.sports_tennis,
isHighlighted: false,
), ),
// Histórico SizedBox(width: 20),
_buildQuickOption(
icon: Icons.history,
title: 'Histórico',
subtitle: 'Jogos anteriores',
color: Colors.purple,
onTap: () {
print('Ver histórico');
},
),
// Configurações // Card 4 - Estatísticas de Vôlei
_buildQuickOption( PieChartCard(
icon: Icons.settings, title: 'DESEMPENHO',
title: 'Configurações', subtitle: 'Vitórias vs Derrotas',
subtitle: 'Ajustar app', backgroundColor: Colors.red[800]!,
color: Colors.grey,
onTap: () { onTap: () {
print('Abrir configurações'); print('Gráfico clicado!');
}, },
), ),
], ],
@ -162,53 +90,168 @@ class HomeScreen extends StatelessWidget {
); );
} }
// Widget para construir opções rápidas // Método para criar cards de estatísticas
Widget _buildQuickOption({ Widget _buildStatCard({
required IconData icon,
required String title, required String title,
required String subtitle, required String playerName,
required String statValue,
required String statLabel,
required Color color, required Color color,
required VoidCallback onTap, required IconData icon,
bool isHighlighted = false,
}) { }) {
return Card( return Container(
elevation: 5, width: HomeConfig.cardwidthPadding,
shape: RoundedRectangleBorder( height: HomeConfig.cardheightPadding,
borderRadius: BorderRadius.circular(15), child: Card(
), elevation: isHighlighted ? 12 : 8,
child: InkWell( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(20),
onTap: onTap, side: isHighlighted
child: Padding( ? BorderSide(color: Colors.amber, width: 2)
padding: const EdgeInsets.all(16.0), : BorderSide.none,
child: Column( ),
mainAxisAlignment: MainAxisAlignment.center, child: Container(
children: [ decoration: BoxDecoration(
Icon( borderRadius: BorderRadius.circular(20),
icon, gradient: LinearGradient(
size: 40, begin: Alignment.topCenter,
color: color, end: Alignment.bottomCenter,
), colors: [
const SizedBox(height: 10), color.withOpacity(0.9),
Text( color.withOpacity(0.7),
title, ],
style: TextStyle( ),
fontSize: 16, boxShadow: [
fontWeight: FontWeight.bold, BoxShadow(
color: color, color: color.withOpacity(0.3),
), blurRadius: 15,
textAlign: TextAlign.center, spreadRadius: 2,
),
const SizedBox(height: 5),
Text(
subtitle,
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
textAlign: TextAlign.center,
), ),
], ],
), ),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Cabeçalho com título e ícone
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title.toUpperCase(),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.white.withOpacity(0.9),
letterSpacing: 1.5,
),
),
SizedBox(height: 5),
Text(
playerName,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
if (isHighlighted)
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.amber,
shape: BoxShape.circle,
),
child: Icon(
Icons.star,
size: 20,
color: Colors.white,
),
),
],
),
SizedBox(height: 10),
// Ícone do esporte
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Icon(
icon,
size: 30,
color: Colors.white,
),
),
Spacer(),
// Estatística central
Center(
child: Column(
children: [
Text(
statValue,
style: TextStyle(
fontSize: 42,
fontWeight: FontWeight.bold,
color: Colors.white,
height: 1,
),
),
SizedBox(height: 5),
Text(
statLabel.toUpperCase(),
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(0.8),
letterSpacing: 2,
),
),
],
),
),
Spacer(),
// Rodapé com botão
Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(15),
),
child: Center(
child: Text(
'VER DETALHES',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 14,
letterSpacing: 1,
),
),
),
),
],
),
),
), ),
), ),
); );