vai te lixar github
This commit is contained in:
@@ -1,104 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:playmaker/pages/PlacarPage.dart';
|
||||
import 'package:playmaker/classe/theme.dart'; // 👇 IMPORT DO TEMA!
|
||||
import '../controllers/team_controller.dart';
|
||||
import '../controllers/game_controller.dart';
|
||||
|
||||
// --- CARD DE EXIBIÇÃO DO JOGO ---
|
||||
class GameResultCard extends StatelessWidget {
|
||||
final String gameId;
|
||||
final String myTeam, opponentTeam, myScore, opponentScore, status, season;
|
||||
final String? myTeamLogo;
|
||||
final String? opponentTeamLogo;
|
||||
final double sf; // NOVA VARIÁVEL DE ESCALA
|
||||
final String gameId, myTeam, opponentTeam, myScore, opponentScore, status, season;
|
||||
final String? myTeamLogo, opponentTeamLogo;
|
||||
final double sf;
|
||||
|
||||
const GameResultCard({
|
||||
super.key,
|
||||
required this.gameId,
|
||||
required this.myTeam,
|
||||
required this.opponentTeam,
|
||||
required this.myScore,
|
||||
required this.opponentScore,
|
||||
required this.status,
|
||||
required this.season,
|
||||
this.myTeamLogo,
|
||||
this.opponentTeamLogo,
|
||||
required this.sf, // OBRIGATÓRIO RECEBER A ESCALA
|
||||
super.key, required this.gameId, required this.myTeam, required this.opponentTeam,
|
||||
required this.myScore, required this.opponentScore, required this.status, required this.season,
|
||||
this.myTeamLogo, this.opponentTeamLogo, required this.sf,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 👇 Puxa as cores de fundo dependendo do Modo (Claro/Escuro)
|
||||
final bgColor = Theme.of(context).colorScheme.surface;
|
||||
final textColor = Theme.of(context).colorScheme.onSurface;
|
||||
|
||||
return Container(
|
||||
margin: EdgeInsets.only(bottom: 16 * sf),
|
||||
padding: EdgeInsets.all(16 * sf),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
color: bgColor, // Usa a cor do tema
|
||||
borderRadius: BorderRadius.circular(20 * sf),
|
||||
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 10 * sf)],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(child: _buildTeamInfo(myTeam, const Color(0xFFE74C3C), myTeamLogo, sf)),
|
||||
Expanded(child: _buildTeamInfo(myTeam, AppTheme.primaryRed, myTeamLogo, sf, textColor)), // Usa o primaryRed
|
||||
_buildScoreCenter(context, gameId, sf),
|
||||
Expanded(child: _buildTeamInfo(opponentTeam, Colors.black87, opponentTeamLogo, sf)),
|
||||
Expanded(child: _buildTeamInfo(opponentTeam, textColor, opponentTeamLogo, sf, textColor)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTeamInfo(String name, Color color, String? logoUrl, double sf) {
|
||||
Widget _buildTeamInfo(String name, Color color, String? logoUrl, double sf, Color textColor) {
|
||||
return Column(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 24 * sf, // Ajuste do tamanho do logo
|
||||
radius: 24 * sf,
|
||||
backgroundColor: color,
|
||||
backgroundImage: (logoUrl != null && logoUrl.isNotEmpty)
|
||||
? NetworkImage(logoUrl)
|
||||
: null,
|
||||
child: (logoUrl == null || logoUrl.isEmpty)
|
||||
? Icon(Icons.shield, color: Colors.white, size: 24 * sf)
|
||||
: null,
|
||||
backgroundImage: (logoUrl != null && logoUrl.isNotEmpty) ? NetworkImage(logoUrl) : null,
|
||||
child: (logoUrl == null || logoUrl.isEmpty) ? Icon(Icons.shield, color: Colors.white, size: 24 * sf) : null,
|
||||
),
|
||||
SizedBox(height: 6 * sf),
|
||||
Text(name,
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13 * sf),
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2, // Permite 2 linhas para nomes compridos não cortarem
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13 * sf, color: textColor), // Adapta à noite/dia
|
||||
textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, maxLines: 2,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildScoreCenter(BuildContext context, String id, double sf) {
|
||||
final textColor = Theme.of(context).colorScheme.onSurface;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_scoreBox(myScore, Colors.green, sf),
|
||||
Text(" : ", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22 * sf)),
|
||||
_scoreBox(myScore, AppTheme.successGreen, sf), // Verde do tema
|
||||
Text(" : ", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22 * sf, color: textColor)),
|
||||
_scoreBox(opponentScore, Colors.grey, sf),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 10 * sf),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PlacarPage(
|
||||
gameId: id,
|
||||
myTeam: myTeam,
|
||||
opponentTeam: opponentTeam,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: Icon(Icons.play_circle_fill, size: 18 * sf, color: const Color(0xFFE74C3C)),
|
||||
label: Text("RETORNAR", style: TextStyle(fontSize: 11 * sf, color: const Color(0xFFE74C3C), fontWeight: FontWeight.bold)),
|
||||
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (context) => PlacarPage(gameId: id, myTeam: myTeam, opponentTeam: opponentTeam))),
|
||||
icon: Icon(Icons.play_circle_fill, size: 18 * sf, color: AppTheme.primaryRed),
|
||||
label: Text("RETORNAR", style: TextStyle(fontSize: 11 * sf, color: AppTheme.primaryRed, fontWeight: FontWeight.bold)),
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFE74C3C).withOpacity(0.1),
|
||||
backgroundColor: AppTheme.primaryRed.withOpacity(0.1),
|
||||
padding: EdgeInsets.symmetric(horizontal: 14 * sf, vertical: 8 * sf),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20 * sf)),
|
||||
visualDensity: VisualDensity.compact,
|
||||
@@ -115,204 +94,4 @@ class GameResultCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(color: c, borderRadius: BorderRadius.circular(8 * sf)),
|
||||
child: Text(pts, style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16 * sf)),
|
||||
);
|
||||
}
|
||||
|
||||
// --- POPUP DE CRIAÇÃO ---
|
||||
class CreateGameDialogManual extends StatefulWidget {
|
||||
final TeamController teamController;
|
||||
final GameController gameController;
|
||||
final double sf; // NOVA VARIÁVEL DE ESCALA
|
||||
|
||||
const CreateGameDialogManual({
|
||||
super.key,
|
||||
required this.teamController,
|
||||
required this.gameController,
|
||||
required this.sf,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CreateGameDialogManual> createState() => _CreateGameDialogManualState();
|
||||
}
|
||||
|
||||
class _CreateGameDialogManualState extends State<CreateGameDialogManual> {
|
||||
late TextEditingController _seasonController;
|
||||
final TextEditingController _myTeamController = TextEditingController();
|
||||
final TextEditingController _opponentController = TextEditingController();
|
||||
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_seasonController = TextEditingController(text: _calculateSeason());
|
||||
}
|
||||
|
||||
String _calculateSeason() {
|
||||
final now = DateTime.now();
|
||||
return now.month >= 7 ? "${now.year}/${(now.year + 1).toString().substring(2)}" : "${now.year - 1}/${now.year.toString().substring(2)}";
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20 * widget.sf)),
|
||||
title: Text('Configurar Partida', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18 * widget.sf)),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: _seasonController,
|
||||
style: TextStyle(fontSize: 14 * widget.sf),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Temporada',
|
||||
labelStyle: TextStyle(fontSize: 14 * widget.sf),
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.calendar_today, size: 20 * widget.sf)
|
||||
),
|
||||
),
|
||||
SizedBox(height: 15 * widget.sf),
|
||||
|
||||
_buildSearch(label: "Minha Equipa", controller: _myTeamController, sf: widget.sf),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 10 * widget.sf),
|
||||
child: Text("VS", style: TextStyle(fontWeight: FontWeight.bold, color: Colors.grey, fontSize: 16 * widget.sf))
|
||||
),
|
||||
|
||||
_buildSearch(label: "Adversário", controller: _opponentController, sf: widget.sf),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('CANCELAR', style: TextStyle(fontSize: 14 * widget.sf))
|
||||
),
|
||||
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFE74C3C),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10 * widget.sf)),
|
||||
padding: EdgeInsets.symmetric(horizontal: 16 * widget.sf, vertical: 10 * widget.sf)
|
||||
),
|
||||
onPressed: _isLoading ? null : () async {
|
||||
if (_myTeamController.text.isNotEmpty && _opponentController.text.isNotEmpty) {
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
String? newGameId = await widget.gameController.createGame(
|
||||
_myTeamController.text,
|
||||
_opponentController.text,
|
||||
_seasonController.text,
|
||||
);
|
||||
|
||||
setState(() => _isLoading = false);
|
||||
|
||||
if (newGameId != null && context.mounted) {
|
||||
Navigator.pop(context);
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PlacarPage(
|
||||
gameId: newGameId,
|
||||
myTeam: _myTeamController.text,
|
||||
opponentTeam: _opponentController.text,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: _isLoading
|
||||
? SizedBox(width: 20 * widget.sf, height: 20 * widget.sf, child: const CircularProgressIndicator(color: Colors.white, strokeWidth: 2))
|
||||
: Text('CRIAR', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14 * widget.sf)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSearch({required String label, required TextEditingController controller, required double sf}) {
|
||||
return StreamBuilder<List<Map<String, dynamic>>>(
|
||||
stream: widget.teamController.teamsStream,
|
||||
builder: (context, snapshot) {
|
||||
List<Map<String, dynamic>> teamList = snapshot.hasData ? snapshot.data! : [];
|
||||
|
||||
return Autocomplete<Map<String, dynamic>>(
|
||||
displayStringForOption: (Map<String, dynamic> option) => option['name'].toString(),
|
||||
|
||||
optionsBuilder: (TextEditingValue val) {
|
||||
if (val.text.isEmpty) return const Iterable<Map<String, dynamic>>.empty();
|
||||
return teamList.where((t) =>
|
||||
t['name'].toString().toLowerCase().contains(val.text.toLowerCase()));
|
||||
},
|
||||
|
||||
onSelected: (Map<String, dynamic> selection) {
|
||||
controller.text = selection['name'].toString();
|
||||
},
|
||||
|
||||
optionsViewBuilder: (context, onSelected, options) {
|
||||
return Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Material(
|
||||
elevation: 4.0,
|
||||
borderRadius: BorderRadius.circular(8 * sf),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: 250 * sf, maxWidth: MediaQuery.of(context).size.width * 0.7),
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: options.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final option = options.elementAt(index);
|
||||
final String name = option['name'].toString();
|
||||
final String? imageUrl = option['image_url'];
|
||||
|
||||
return ListTile(
|
||||
leading: CircleAvatar(
|
||||
radius: 20 * sf,
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
backgroundImage: (imageUrl != null && imageUrl.isNotEmpty)
|
||||
? NetworkImage(imageUrl)
|
||||
: null,
|
||||
child: (imageUrl == null || imageUrl.isEmpty)
|
||||
? Icon(Icons.shield, color: Colors.grey, size: 20 * sf)
|
||||
: null,
|
||||
),
|
||||
title: Text(name, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14 * sf)),
|
||||
onTap: () {
|
||||
onSelected(option);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
fieldViewBuilder: (ctx, txtCtrl, node, submit) {
|
||||
if (txtCtrl.text.isEmpty && controller.text.isNotEmpty) {
|
||||
txtCtrl.text = controller.text;
|
||||
}
|
||||
txtCtrl.addListener(() {
|
||||
controller.text = txtCtrl.text;
|
||||
});
|
||||
|
||||
return TextField(
|
||||
controller: txtCtrl,
|
||||
focusNode: node,
|
||||
style: TextStyle(fontSize: 14 * sf),
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
labelStyle: TextStyle(fontSize: 14 * sf),
|
||||
prefixIcon: Icon(Icons.search, size: 20 * sf),
|
||||
border: const OutlineInputBorder()
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:playmaker/controllers/login_controller.dart';
|
||||
import 'package:playmaker/pages/RegisterPage.dart';
|
||||
import '../utils/size_extension.dart'; // 👇 O NOSSO SUPERPODER!
|
||||
import 'package:playmaker/classe/theme.dart'; // 👇 IMPORT DO TEMA
|
||||
import '../utils/size_extension.dart';
|
||||
|
||||
class BasketTrackHeader extends StatelessWidget {
|
||||
const BasketTrackHeader({super.key});
|
||||
@@ -11,7 +12,7 @@ class BasketTrackHeader extends StatelessWidget {
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 200 * context.sf, // Ajusta o tamanho da imagem suavemente
|
||||
width: 200 * context.sf,
|
||||
height: 200 * context.sf,
|
||||
child: Image.asset(
|
||||
'assets/playmaker-logos.png',
|
||||
@@ -23,7 +24,7 @@ class BasketTrackHeader extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 36 * context.sf,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.grey[900],
|
||||
color: Theme.of(context).colorScheme.onSurface, // 👇 Adaptável ao Modo Escuro
|
||||
),
|
||||
),
|
||||
SizedBox(height: 6 * context.sf),
|
||||
@@ -31,7 +32,7 @@ class BasketTrackHeader extends StatelessWidget {
|
||||
'Gere as tuas equipas e estatísticas',
|
||||
style: TextStyle(
|
||||
fontSize: 16 * context.sf,
|
||||
color: Colors.grey[600],
|
||||
color: Colors.grey, // Mantemos cinza para subtítulo
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
@@ -52,13 +53,17 @@ class LoginFormFields extends StatelessWidget {
|
||||
children: [
|
||||
TextField(
|
||||
controller: controller.emailController,
|
||||
style: TextStyle(fontSize: 15 * context.sf),
|
||||
style: TextStyle(fontSize: 15 * context.sf, color: Theme.of(context).colorScheme.onSurface),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'E-mail',
|
||||
labelStyle: TextStyle(fontSize: 15 * context.sf),
|
||||
prefixIcon: Icon(Icons.email_outlined, size: 22 * context.sf),
|
||||
prefixIcon: Icon(Icons.email_outlined, size: 22 * context.sf, color: AppTheme.primaryRed), // 👇 Cor do tema
|
||||
errorText: controller.emailError,
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12 * context.sf)),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12 * context.sf),
|
||||
borderSide: BorderSide(color: AppTheme.primaryRed, width: 2), // 👇 Cor do tema ao focar
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 18 * context.sf, horizontal: 16 * context.sf),
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
@@ -67,16 +72,21 @@ class LoginFormFields extends StatelessWidget {
|
||||
TextField(
|
||||
controller: controller.passwordController,
|
||||
obscureText: controller.obscurePassword,
|
||||
style: TextStyle(fontSize: 15 * context.sf),
|
||||
style: TextStyle(fontSize: 15 * context.sf, color: Theme.of(context).colorScheme.onSurface),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Palavra-passe',
|
||||
labelStyle: TextStyle(fontSize: 15 * context.sf),
|
||||
prefixIcon: Icon(Icons.lock_outlined, size: 22 * context.sf),
|
||||
prefixIcon: Icon(Icons.lock_outlined, size: 22 * context.sf, color: AppTheme.primaryRed), // 👇 Cor do tema
|
||||
errorText: controller.passwordError,
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12 * context.sf),
|
||||
borderSide: BorderSide(color: AppTheme.primaryRed, width: 2), // 👇 Cor do tema ao focar
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
controller.obscurePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined,
|
||||
size: 22 * context.sf
|
||||
size: 22 * context.sf,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: controller.togglePasswordVisibility,
|
||||
),
|
||||
@@ -106,7 +116,7 @@ class LoginButton extends StatelessWidget {
|
||||
if (success) onLoginSuccess();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFE74C3C),
|
||||
backgroundColor: AppTheme.primaryRed, // 👇 Usando a cor do tema
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14 * context.sf)),
|
||||
elevation: 3,
|
||||
@@ -135,8 +145,8 @@ class CreateAccountButton extends StatelessWidget {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => const RegisterPage()));
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: const Color(0xFFE74C3C),
|
||||
side: BorderSide(color: const Color(0xFFE74C3C), width: 2 * context.sf),
|
||||
foregroundColor: AppTheme.primaryRed, // 👇 Usando a cor do tema
|
||||
side: BorderSide(color: AppTheme.primaryRed, width: 2 * context.sf), // 👇 Usando a cor do tema
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14 * context.sf)),
|
||||
),
|
||||
child: Text('Criar Conta', style: TextStyle(fontSize: 18 * context.sf, fontWeight: FontWeight.bold)),
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:playmaker/controllers/placar_controller.dart';
|
||||
import 'package:playmaker/zone_map_dialog.dart';
|
||||
|
||||
// --- PLACAR SUPERIOR ---
|
||||
// ============================================================================
|
||||
// 1. PLACAR SUPERIOR (CRONÓMETRO E RESULTADO)
|
||||
// ============================================================================
|
||||
class TopScoreboard extends StatelessWidget {
|
||||
final PlacarController controller;
|
||||
final double sf;
|
||||
@@ -105,7 +108,9 @@ class TopScoreboard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// --- BANCO DE SUPLENTES ---
|
||||
// ============================================================================
|
||||
// 2. BANCO DE SUPLENTES (DRAG & DROP)
|
||||
// ============================================================================
|
||||
class BenchPlayersList extends StatelessWidget {
|
||||
final PlacarController controller;
|
||||
final bool isOpponent;
|
||||
@@ -173,7 +178,12 @@ class BenchPlayersList extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// --- CARTÃO DO JOGADOR NO CAMPO ---
|
||||
// ============================================================================
|
||||
// 3. CARTÃO DO JOGADOR NO CAMPO (TARGET DE FALTAS/PONTOS/SUBSTITUIÇÕES)
|
||||
// ============================================================================
|
||||
// ============================================================================
|
||||
// 3. CARTÃO DO JOGADOR NO CAMPO (AGORA ABRE O POPUP AMARELO)
|
||||
// ============================================================================
|
||||
class PlayerCourtCard extends StatelessWidget {
|
||||
final PlacarController controller;
|
||||
final String name;
|
||||
@@ -203,7 +213,27 @@ class PlayerCourtCard extends StatelessWidget {
|
||||
child: DragTarget<String>(
|
||||
onAcceptWithDetails: (details) {
|
||||
final action = details.data;
|
||||
if (action.startsWith("add_") || action.startsWith("sub_") || action.startsWith("miss_")) {
|
||||
|
||||
// 👇 SE FOR UM LANÇAMENTO DE CAMPO (2 OU 3 PONTOS), ABRE O POPUP AMARELO!
|
||||
if (action == "add_pts_2" || action == "add_pts_3" || action == "miss_2" || action == "miss_3") {
|
||||
bool isMake = action.startsWith("add_");
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => ZoneMapDialog(
|
||||
playerName: name,
|
||||
isMake: isMake,
|
||||
onZoneSelected: (zone, points, relX, relY) {
|
||||
Navigator.pop(ctx); // Fecha o popup amarelo
|
||||
|
||||
// 👇 MANDA OS DADOS PARA O CONTROLLER! (Vais ter de criar esta função no PlacarController)
|
||||
controller.registerShotFromPopup(context, action, "$prefix$name", zone, points, relX, relY);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
// Se for 1 Ponto (Lance Livre), Falta, Ressalto ou Roubo, FAZ TUDO NORMAL!
|
||||
else if (action.startsWith("add_") || action.startsWith("sub_") || action.startsWith("miss_")) {
|
||||
controller.handleActionDrag(context, action, "$prefix$name");
|
||||
} else if (action.startsWith("bench_")) {
|
||||
controller.handleSubbing(context, action, name, isOpponent);
|
||||
@@ -219,15 +249,13 @@ class PlayerCourtCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _playerCardUI(String number, String name, Map<String, int> stats, Color teamColor, bool isSubbing, bool isActionHover, double sf) {
|
||||
// ... (Mantém o teu código de design _playerCardUI que já tinhas aqui dentro, fica igualzinho!)
|
||||
bool isFouledOut = stats["fls"]! >= 5;
|
||||
Color bgColor = isFouledOut ? Colors.red.shade50 : Colors.white;
|
||||
Color borderColor = isFouledOut ? Colors.redAccent : Colors.transparent;
|
||||
|
||||
if (isSubbing) {
|
||||
bgColor = Colors.blue.shade50; borderColor = Colors.blue;
|
||||
} else if (isActionHover && !isFouledOut) {
|
||||
bgColor = Colors.orange.shade50; borderColor = Colors.orange;
|
||||
}
|
||||
if (isSubbing) { bgColor = Colors.blue.shade50; borderColor = Colors.blue; }
|
||||
else if (isActionHover && !isFouledOut) { bgColor = Colors.orange.shade50; borderColor = Colors.orange; }
|
||||
|
||||
int fgm = stats["fgm"]!;
|
||||
int fga = stats["fga"]!;
|
||||
@@ -260,19 +288,10 @@ class PlayerCourtCard extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
displayName,
|
||||
style: TextStyle(fontSize: 16 * sf, fontWeight: FontWeight.bold, color: isFouledOut ? Colors.red : Colors.black87, decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none)
|
||||
),
|
||||
Text(displayName, style: TextStyle(fontSize: 16 * sf, fontWeight: FontWeight.bold, color: isFouledOut ? Colors.red : Colors.black87, decoration: isFouledOut ? TextDecoration.lineThrough : TextDecoration.none)),
|
||||
SizedBox(height: 2.5 * sf),
|
||||
Text(
|
||||
"${stats["pts"]} Pts | FG: $fgm/$fga ($fgPercent%)",
|
||||
style: TextStyle(fontSize: 12 * sf, color: isFouledOut ? Colors.red : Colors.grey[700], fontWeight: FontWeight.w600)
|
||||
),
|
||||
Text(
|
||||
"${stats["ast"]} Ast | ${stats["orb"]! + stats["drb"]!} Rbs | ${stats["fls"]} Fls",
|
||||
style: TextStyle(fontSize: 12 * sf, color: isFouledOut ? Colors.red : Colors.grey[500], fontWeight: FontWeight.w600)
|
||||
),
|
||||
Text("${stats["pts"]} Pts | FG: $fgm/$fga ($fgPercent%)", style: TextStyle(fontSize: 12 * sf, color: isFouledOut ? Colors.red : Colors.grey[700], fontWeight: FontWeight.w600)),
|
||||
Text("${stats["ast"]} Ast | ${stats["orb"]! + stats["drb"]!} Rbs | ${stats["fls"]} Fls", style: TextStyle(fontSize: 12 * sf, color: isFouledOut ? Colors.red : Colors.grey[500], fontWeight: FontWeight.w600)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -284,7 +303,9 @@ class PlayerCourtCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// --- PAINEL DE BOTÕES DE AÇÃO ---
|
||||
// ============================================================================
|
||||
// 4. PAINEL DE BOTÕES DE AÇÃO (PONTOS, RESSALTOS, ETC)
|
||||
// ============================================================================
|
||||
class ActionButtonsPanel extends StatelessWidget {
|
||||
final PlacarController controller;
|
||||
final double sf;
|
||||
@@ -293,8 +314,8 @@ class ActionButtonsPanel extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double baseSize = 65 * sf; // Reduzido (Antes era 75)
|
||||
final double feedSize = 82 * sf; // Reduzido (Antes era 95)
|
||||
final double baseSize = 65 * sf;
|
||||
final double feedSize = 82 * sf;
|
||||
final double gap = 7 * sf;
|
||||
|
||||
return Row(
|
||||
@@ -347,7 +368,7 @@ class ActionButtonsPanel extends StatelessWidget {
|
||||
child: _circle(label, color, icon, false, baseSize, feedSize, sf, isX: isX)
|
||||
),
|
||||
child: DragTarget<String>(
|
||||
onAcceptWithDetails: (details) {},
|
||||
onAcceptWithDetails: (details) {}, // O PlayerCourtCard é que processa a ação!
|
||||
builder: (context, candidateData, rejectedData) {
|
||||
bool isHovered = candidateData.any((data) => data != null && data.startsWith("player_"));
|
||||
return Transform.scale(
|
||||
@@ -408,7 +429,9 @@ class ActionButtonsPanel extends StatelessWidget {
|
||||
children: [
|
||||
Container(
|
||||
width: size, height: size,
|
||||
decoration: (isPointBtn || isBlkBtn) ? const BoxDecoration(color: Colors.transparent) : BoxDecoration(gradient: RadialGradient(colors: [color.withOpacity(0.7), color], radius: 0.8), shape: BoxShape.circle, boxShadow: [BoxShadow(color: Colors.black38, blurRadius: 6 * sf, offset: Offset(0, 3 * sf))]),
|
||||
decoration: (isPointBtn || isBlkBtn)
|
||||
? const BoxDecoration(color: Colors.transparent)
|
||||
: BoxDecoration(gradient: RadialGradient(colors: [color.withOpacity(0.7), color], radius: 0.8), shape: BoxShape.circle, boxShadow: [BoxShadow(color: Colors.black38, blurRadius: 6 * sf, offset: Offset(0, 3 * sf))]),
|
||||
alignment: Alignment.center,
|
||||
child: content,
|
||||
),
|
||||
@@ -416,4 +439,5 @@ class ActionButtonsPanel extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:playmaker/classe/theme.dart'; // 👇 IMPORT DO TEMA
|
||||
import '../controllers/register_controller.dart';
|
||||
import '../utils/size_extension.dart'; // 👇 O NOSSO SUPERPODER!
|
||||
|
||||
@@ -9,16 +10,20 @@ class RegisterHeader extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Icon(Icons.person_add_outlined, size: 100 * context.sf, color: const Color(0xFFE74C3C)),
|
||||
Icon(Icons.person_add_outlined, size: 100 * context.sf, color: AppTheme.primaryRed), // 👇 Cor do tema
|
||||
SizedBox(height: 10 * context.sf),
|
||||
Text(
|
||||
'Nova Conta',
|
||||
style: TextStyle(fontSize: 36 * context.sf, fontWeight: FontWeight.bold, color: Colors.grey[900]),
|
||||
style: TextStyle(
|
||||
fontSize: 36 * context.sf,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.onSurface, // 👇 Adaptável ao Modo Escuro
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5 * context.sf),
|
||||
Text(
|
||||
'Cria o teu perfil no BasketTrack',
|
||||
style: TextStyle(fontSize: 16 * context.sf, color: Colors.grey[600], fontWeight: FontWeight.w500),
|
||||
style: TextStyle(fontSize: 16 * context.sf, color: Colors.grey, fontWeight: FontWeight.w500),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
@@ -45,12 +50,16 @@ class _RegisterFormFieldsState extends State<RegisterFormFields> {
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: widget.controller.nameController,
|
||||
style: TextStyle(fontSize: 15 * context.sf),
|
||||
style: TextStyle(fontSize: 15 * context.sf, color: Theme.of(context).colorScheme.onSurface),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Nome Completo',
|
||||
labelStyle: TextStyle(fontSize: 15 * context.sf),
|
||||
prefixIcon: Icon(Icons.person_outline, size: 22 * context.sf),
|
||||
prefixIcon: Icon(Icons.person_outline, size: 22 * context.sf, color: AppTheme.primaryRed), // 👇 Cor do tema
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12 * context.sf)),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12 * context.sf),
|
||||
borderSide: BorderSide(color: AppTheme.primaryRed, width: 2), // 👇 Destaque ao focar
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 18 * context.sf, horizontal: 16 * context.sf),
|
||||
),
|
||||
),
|
||||
@@ -59,12 +68,16 @@ class _RegisterFormFieldsState extends State<RegisterFormFields> {
|
||||
TextFormField(
|
||||
controller: widget.controller.emailController,
|
||||
validator: widget.controller.validateEmail,
|
||||
style: TextStyle(fontSize: 15 * context.sf),
|
||||
style: TextStyle(fontSize: 15 * context.sf, color: Theme.of(context).colorScheme.onSurface),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'E-mail',
|
||||
labelStyle: TextStyle(fontSize: 15 * context.sf),
|
||||
prefixIcon: Icon(Icons.email_outlined, size: 22 * context.sf),
|
||||
prefixIcon: Icon(Icons.email_outlined, size: 22 * context.sf, color: AppTheme.primaryRed),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12 * context.sf)),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12 * context.sf),
|
||||
borderSide: BorderSide(color: AppTheme.primaryRed, width: 2),
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 18 * context.sf, horizontal: 16 * context.sf),
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
@@ -75,13 +88,17 @@ class _RegisterFormFieldsState extends State<RegisterFormFields> {
|
||||
controller: widget.controller.passwordController,
|
||||
obscureText: _obscurePassword,
|
||||
validator: widget.controller.validatePassword,
|
||||
style: TextStyle(fontSize: 15 * context.sf),
|
||||
style: TextStyle(fontSize: 15 * context.sf, color: Theme.of(context).colorScheme.onSurface),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Palavra-passe',
|
||||
labelStyle: TextStyle(fontSize: 15 * context.sf),
|
||||
prefixIcon: Icon(Icons.lock_outlined, size: 22 * context.sf),
|
||||
prefixIcon: Icon(Icons.lock_outlined, size: 22 * context.sf, color: AppTheme.primaryRed),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12 * context.sf),
|
||||
borderSide: BorderSide(color: AppTheme.primaryRed, width: 2),
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(_obscurePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined, size: 22 * context.sf),
|
||||
icon: Icon(_obscurePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined, size: 22 * context.sf, color: Colors.grey),
|
||||
onPressed: () => setState(() => _obscurePassword = !_obscurePassword),
|
||||
),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12 * context.sf)),
|
||||
@@ -94,11 +111,15 @@ class _RegisterFormFieldsState extends State<RegisterFormFields> {
|
||||
controller: widget.controller.confirmPasswordController,
|
||||
obscureText: _obscurePassword,
|
||||
validator: widget.controller.validateConfirmPassword,
|
||||
style: TextStyle(fontSize: 15 * context.sf),
|
||||
style: TextStyle(fontSize: 15 * context.sf, color: Theme.of(context).colorScheme.onSurface),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Confirmar Palavra-passe',
|
||||
labelStyle: TextStyle(fontSize: 15 * context.sf),
|
||||
prefixIcon: Icon(Icons.lock_clock_outlined, size: 22 * context.sf),
|
||||
prefixIcon: Icon(Icons.lock_clock_outlined, size: 22 * context.sf, color: AppTheme.primaryRed),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12 * context.sf),
|
||||
borderSide: BorderSide(color: AppTheme.primaryRed, width: 2),
|
||||
),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12 * context.sf)),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 18 * context.sf, horizontal: 16 * context.sf),
|
||||
),
|
||||
@@ -121,7 +142,7 @@ class RegisterButton extends StatelessWidget {
|
||||
child: ElevatedButton(
|
||||
onPressed: controller.isLoading ? null : () => controller.signUp(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFE74C3C),
|
||||
backgroundColor: AppTheme.primaryRed, // 👇 Cor do tema
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14 * context.sf)),
|
||||
elevation: 3,
|
||||
|
||||
@@ -1,158 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:playmaker/screens/team_stats_page.dart';
|
||||
import 'package:playmaker/classe/theme.dart'; // 👇 IMPORT DO TEMA
|
||||
import '../models/team_model.dart';
|
||||
import '../controllers/team_controller.dart';
|
||||
import '../models/person_model.dart';
|
||||
import '../utils/size_extension.dart'; // 👇 O NOSSO SUPERPODER!
|
||||
|
||||
class TeamCard extends StatelessWidget {
|
||||
// --- CABEÇALHO ---
|
||||
class StatsHeader extends StatelessWidget {
|
||||
final Team team;
|
||||
final TeamController controller;
|
||||
final VoidCallback onFavoriteTap;
|
||||
final double sf; // <-- Variável de escala
|
||||
|
||||
const TeamCard({
|
||||
super.key,
|
||||
required this.team,
|
||||
required this.controller,
|
||||
required this.onFavoriteTap,
|
||||
required this.sf,
|
||||
});
|
||||
const StatsHeader({super.key, required this.team});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
color: Colors.white,
|
||||
elevation: 3,
|
||||
margin: EdgeInsets.only(bottom: 12 * sf),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15 * sf)),
|
||||
child: ListTile(
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16 * sf, vertical: 8 * sf),
|
||||
|
||||
// --- 1. IMAGEM + FAVORITO ---
|
||||
leading: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 28 * sf,
|
||||
backgroundColor: Colors.grey[200],
|
||||
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: TextStyle(fontSize: 24 * sf),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
Positioned(
|
||||
left: -15 * sf,
|
||||
top: -10 * sf,
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
team.isFavorite ? Icons.star : Icons.star_border,
|
||||
color: team.isFavorite ? Colors.amber : Colors.black.withOpacity(0.1),
|
||||
size: 28 * sf,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Colors.black.withOpacity(team.isFavorite ? 0.3 : 0.1),
|
||||
blurRadius: 4 * sf,
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: onFavoriteTap,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// --- 2. TÍTULO ---
|
||||
title: Text(
|
||||
team.name,
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16 * sf),
|
||||
overflow: TextOverflow.ellipsis, // Previne overflows em nomes longos
|
||||
),
|
||||
|
||||
// --- 3. SUBTÍTULO (Contagem + Época em TEMPO REAL) ---
|
||||
subtitle: Padding(
|
||||
padding: EdgeInsets.only(top: 6.0 * sf),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.groups_outlined, size: 16 * sf, color: Colors.grey),
|
||||
SizedBox(width: 4 * sf),
|
||||
|
||||
// 👇 A CORREÇÃO ESTÁ AQUI: StreamBuilder em vez de FutureBuilder 👇
|
||||
StreamBuilder<int>(
|
||||
stream: controller.getPlayerCountStream(team.id),
|
||||
initialData: 0,
|
||||
builder: (context, snapshot) {
|
||||
final count = snapshot.data ?? 0;
|
||||
return Text(
|
||||
"$count Jogs.", // Abreviado para poupar espaço
|
||||
style: TextStyle(
|
||||
color: count > 0 ? Colors.green[700] : Colors.orange,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 13 * sf,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
SizedBox(width: 8 * sf),
|
||||
Expanded( // Garante que a temporada se adapta se faltar espaço
|
||||
child: Text(
|
||||
"| ${team.season}",
|
||||
style: TextStyle(color: Colors.grey, fontSize: 13 * sf),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// --- 4. BOTÕES (Estatísticas e Apagar) ---
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min, // <-- ISTO RESOLVE O OVERFLOW DAS RISCAS AMARELAS
|
||||
children: [
|
||||
IconButton(
|
||||
tooltip: 'Ver Estatísticas',
|
||||
icon: Icon(Icons.bar_chart_rounded, color: Colors.blue, size: 24 * sf),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => TeamStatsPage(team: team),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
tooltip: 'Eliminar Equipa',
|
||||
icon: Icon(Icons.delete_outline, color: const Color(0xFFE74C3C), size: 24 * sf),
|
||||
onPressed: () => _confirmDelete(context),
|
||||
),
|
||||
],
|
||||
return Container(
|
||||
padding: EdgeInsets.only(
|
||||
top: 50 * context.sf,
|
||||
left: 20 * context.sf,
|
||||
right: 20 * context.sf,
|
||||
bottom: 20 * context.sf
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primaryRed, // 👇 Usando a cor do teu tema!
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(30 * context.sf),
|
||||
bottomRight: Radius.circular(30 * context.sf)
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _confirmDelete(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Eliminar Equipa?', style: TextStyle(fontSize: 18 * sf, fontWeight: FontWeight.bold)),
|
||||
content: Text('Tens a certeza que queres eliminar "${team.name}"?', style: TextStyle(fontSize: 14 * sf)),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.arrow_back, color: Colors.white, size: 24 * context.sf),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Cancelar', style: TextStyle(fontSize: 14 * sf)),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
controller.deleteTeam(team.id);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text('Eliminar', style: TextStyle(color: Colors.red, fontSize: 14 * sf)),
|
||||
SizedBox(width: 10 * context.sf),
|
||||
CircleAvatar(
|
||||
radius: 24 * context.sf,
|
||||
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: TextStyle(fontSize: 20 * context.sf),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
SizedBox(width: 15 * context.sf),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
team.name,
|
||||
style: TextStyle(color: Colors.white, fontSize: 20 * context.sf, fontWeight: FontWeight.bold),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
team.season,
|
||||
style: TextStyle(color: Colors.white70, fontSize: 14 * context.sf)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -160,90 +69,164 @@ class TeamCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// --- DIALOG DE CRIAÇÃO ---
|
||||
class CreateTeamDialog extends StatefulWidget {
|
||||
final Function(String name, String season, String imageUrl) onConfirm;
|
||||
final double sf; // Recebe a escala
|
||||
// --- CARD DE RESUMO ---
|
||||
class StatsSummaryCard extends StatelessWidget {
|
||||
final int total;
|
||||
|
||||
const CreateTeamDialog({super.key, required this.onConfirm, required this.sf});
|
||||
|
||||
@override
|
||||
State<CreateTeamDialog> createState() => _CreateTeamDialogState();
|
||||
}
|
||||
|
||||
class _CreateTeamDialogState extends State<CreateTeamDialog> {
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
final TextEditingController _imageController = TextEditingController();
|
||||
String _selectedSeason = '2024/25';
|
||||
const StatsSummaryCard({super.key, required this.total});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15 * widget.sf)),
|
||||
title: Text('Nova Equipa', style: TextStyle(fontSize: 18 * widget.sf, fontWeight: FontWeight.bold)),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
// 👇 Adaptável ao Modo Escuro
|
||||
final cardColor = Theme.of(context).brightness == Brightness.dark
|
||||
? const Color(0xFF1E1E1E)
|
||||
: Colors.white;
|
||||
|
||||
return Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20 * context.sf)),
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(20 * context.sf),
|
||||
decoration: BoxDecoration(
|
||||
color: cardColor,
|
||||
borderRadius: BorderRadius.circular(20 * context.sf),
|
||||
border: Border.all(color: Colors.grey.withOpacity(0.15)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
style: TextStyle(fontSize: 14 * widget.sf),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Nome da Equipa',
|
||||
labelStyle: TextStyle(fontSize: 14 * widget.sf)
|
||||
),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.groups, color: AppTheme.primaryRed, size: 28 * context.sf), // 👇 Cor do tema
|
||||
SizedBox(width: 10 * context.sf),
|
||||
Text(
|
||||
"Total de Membros",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface, // 👇 Adaptável
|
||||
fontSize: 16 * context.sf,
|
||||
fontWeight: FontWeight.w600
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 15 * widget.sf),
|
||||
DropdownButtonFormField<String>(
|
||||
value: _selectedSeason,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Temporada',
|
||||
labelStyle: TextStyle(fontSize: 14 * widget.sf)
|
||||
),
|
||||
style: TextStyle(fontSize: 14 * widget.sf, color: Colors.black87),
|
||||
items: ['2023/24', '2024/25', '2025/26']
|
||||
.map((s) => DropdownMenuItem(value: s, child: Text(s)))
|
||||
.toList(),
|
||||
onChanged: (val) => setState(() => _selectedSeason = val!),
|
||||
),
|
||||
SizedBox(height: 15 * widget.sf),
|
||||
TextField(
|
||||
controller: _imageController,
|
||||
style: TextStyle(fontSize: 14 * widget.sf),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'URL Imagem ou Emoji',
|
||||
labelStyle: TextStyle(fontSize: 14 * widget.sf),
|
||||
hintText: 'Ex: 🏀 ou https://...',
|
||||
hintStyle: TextStyle(fontSize: 14 * widget.sf)
|
||||
),
|
||||
Text(
|
||||
"$total",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface, // 👇 Adaptável
|
||||
fontSize: 28 * context.sf,
|
||||
fontWeight: FontWeight.bold
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Cancelar', style: TextStyle(fontSize: 14 * widget.sf))
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFE74C3C),
|
||||
padding: EdgeInsets.symmetric(horizontal: 16 * widget.sf, vertical: 10 * widget.sf)
|
||||
),
|
||||
onPressed: () {
|
||||
if (_nameController.text.trim().isNotEmpty) {
|
||||
widget.onConfirm(
|
||||
_nameController.text.trim(),
|
||||
_selectedSeason,
|
||||
_imageController.text.trim(),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Text('Criar', style: TextStyle(color: Colors.white, fontSize: 14 * widget.sf)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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: TextStyle(
|
||||
fontSize: 18 * context.sf,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.onSurface // 👇 Adaptável
|
||||
)
|
||||
),
|
||||
Divider(color: Colors.grey.withOpacity(0.3)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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) {
|
||||
// 👇 Cores adaptáveis para o Card
|
||||
final defaultBg = Theme.of(context).brightness == Brightness.dark
|
||||
? const Color(0xFF1E1E1E)
|
||||
: Colors.white;
|
||||
|
||||
final coachBg = Theme.of(context).brightness == Brightness.dark
|
||||
? AppTheme.warningAmber.withOpacity(0.1) // Amarelo escuro se for modo noturno
|
||||
: const Color(0xFFFFF9C4); // Amarelo claro original
|
||||
|
||||
return Card(
|
||||
margin: EdgeInsets.only(top: 12 * context.sf),
|
||||
elevation: 2,
|
||||
color: isCoach ? coachBg : defaultBg,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15 * context.sf)),
|
||||
child: ListTile(
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16 * context.sf, vertical: 4 * context.sf),
|
||||
leading: isCoach
|
||||
? CircleAvatar(
|
||||
radius: 22 * context.sf,
|
||||
backgroundColor: AppTheme.warningAmber, // 👇 Cor do tema
|
||||
child: Icon(Icons.person, color: Colors.white, size: 24 * context.sf)
|
||||
)
|
||||
: Container(
|
||||
width: 45 * context.sf,
|
||||
height: 45 * context.sf,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primaryRed.withOpacity(0.1), // 👇 Cor do tema
|
||||
borderRadius: BorderRadius.circular(10 * context.sf)
|
||||
),
|
||||
child: Text(
|
||||
person.number ?? "J",
|
||||
style: TextStyle(
|
||||
color: AppTheme.primaryRed, // 👇 Cor do tema
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16 * context.sf
|
||||
)
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
person.name,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16 * context.sf,
|
||||
color: Theme.of(context).colorScheme.onSurface, // 👇 Adaptável
|
||||
)
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit_outlined, color: Colors.blue, size: 22 * context.sf),
|
||||
onPressed: onEdit,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.delete_outline, color: AppTheme.primaryRed, size: 22 * context.sf), // 👇 Cor do tema
|
||||
onPressed: onDelete,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user