domingo
This commit is contained in:
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:image_cropper/image_cropper.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart'; // 👇 A MAGIA DO CACHE AQUI
|
||||
import 'package:playmaker/screens/team_stats_page.dart';
|
||||
import 'package:playmaker/classe/theme.dart';
|
||||
import '../controllers/team_controller.dart';
|
||||
@@ -172,7 +173,6 @@ class _TeamsPageState extends State<TeamsPage> {
|
||||
);
|
||||
}
|
||||
|
||||
// 👇 AGORA USA FUTUREBUILDER E É MUITO MAIS RÁPIDO 👇
|
||||
Widget _buildTeamsList() {
|
||||
return FutureBuilder<List<Map<String, dynamic>>>(
|
||||
future: controller.getTeamsWithStats(),
|
||||
@@ -196,7 +196,7 @@ class _TeamsPageState extends State<TeamsPage> {
|
||||
|
||||
return RefreshIndicator(
|
||||
color: AppTheme.primaryRed,
|
||||
onRefresh: () async => setState(() {}), // Puxa para baixo para recarregar
|
||||
onRefresh: () async => setState(() {}),
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16 * context.sf),
|
||||
itemCount: data.length,
|
||||
@@ -209,9 +209,9 @@ class _TeamsPageState extends State<TeamsPage> {
|
||||
controller: controller,
|
||||
onFavoriteTap: () async {
|
||||
await controller.toggleFavorite(team.id, team.isFavorite);
|
||||
setState(() {}); // Atualiza a estrela na hora
|
||||
setState(() {});
|
||||
},
|
||||
onDelete: () => setState(() {}), // Atualiza a lista quando apaga
|
||||
onDelete: () => setState(() {}),
|
||||
sf: context.sf,
|
||||
),
|
||||
);
|
||||
@@ -229,7 +229,7 @@ class _TeamsPageState extends State<TeamsPage> {
|
||||
sf: context.sf,
|
||||
onConfirm: (name, season, imageFile) async {
|
||||
await controller.createTeam(name, season, imageFile);
|
||||
setState(() {}); // 👇 Atualiza a lista quando acaba de criar a equipa!
|
||||
setState(() {});
|
||||
}
|
||||
),
|
||||
);
|
||||
@@ -241,7 +241,7 @@ class TeamCard extends StatelessWidget {
|
||||
final Team team;
|
||||
final TeamController controller;
|
||||
final VoidCallback onFavoriteTap;
|
||||
final VoidCallback onDelete; // 👇 Avisa o pai quando é apagado
|
||||
final VoidCallback onDelete;
|
||||
final double sf;
|
||||
|
||||
const TeamCard({
|
||||
@@ -257,6 +257,7 @@ class TeamCard extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final bgColor = Theme.of(context).cardTheme.color ?? Theme.of(context).colorScheme.surface;
|
||||
final textColor = Theme.of(context).colorScheme.onSurface;
|
||||
final double avatarSize = 56 * sf; // 2 * radius (28)
|
||||
|
||||
return Container(
|
||||
margin: EdgeInsets.only(bottom: 12 * sf),
|
||||
@@ -274,18 +275,29 @@ class TeamCard extends StatelessWidget {
|
||||
leading: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 28 * sf,
|
||||
backgroundColor: Colors.grey.withOpacity(0.2),
|
||||
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,
|
||||
// 👇 AVATAR DA EQUIPA OTIMIZADO COM CACHE 👇
|
||||
ClipOval(
|
||||
child: Container(
|
||||
width: avatarSize,
|
||||
height: avatarSize,
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
child: (team.imageUrl.isNotEmpty && team.imageUrl.startsWith('http'))
|
||||
? CachedNetworkImage(
|
||||
imageUrl: team.imageUrl,
|
||||
fit: BoxFit.cover,
|
||||
fadeInDuration: Duration.zero, // Fica instantâneo no scroll
|
||||
placeholder: (context, url) => const SizedBox(), // Fica só o fundo cinza
|
||||
errorWidget: (context, url, error) => Center(
|
||||
child: Text("🏀", style: TextStyle(fontSize: 24 * sf)),
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
child: Text(
|
||||
team.imageUrl.isEmpty ? "🏀" : team.imageUrl,
|
||||
style: TextStyle(fontSize: 24 * sf),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: -15 * sf,
|
||||
@@ -313,8 +325,6 @@ class TeamCard extends StatelessWidget {
|
||||
children: [
|
||||
Icon(Icons.groups_outlined, size: 16 * sf, color: Colors.grey),
|
||||
SizedBox(width: 4 * sf),
|
||||
|
||||
// 👇 ESTATÍSTICA MUITO MAIS LEVE. LÊ O VALOR DIRETAMENTE! 👇
|
||||
Text(
|
||||
"${team.playerCount} Jogs.",
|
||||
style: TextStyle(
|
||||
@@ -323,7 +333,6 @@ class TeamCard extends StatelessWidget {
|
||||
fontSize: 13 * sf,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(width: 8 * sf),
|
||||
Expanded(
|
||||
child: Text("| ${team.season}", style: TextStyle(color: Colors.grey, fontSize: 13 * sf), overflow: TextOverflow.ellipsis),
|
||||
@@ -337,7 +346,7 @@ class TeamCard extends StatelessWidget {
|
||||
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))).then((_) => onDelete()), // Atualiza se algo mudou
|
||||
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (context) => TeamStatsPage(team: team))).then((_) => onDelete()),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: 'Eliminar Equipa',
|
||||
@@ -351,7 +360,7 @@ class TeamCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _confirmDelete(BuildContext context, double sf, Color cardColor, Color textColor) {
|
||||
void _confirmDelete(BuildContext context, double sf, Color cardColor, Color textColor) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
@@ -366,12 +375,8 @@ void _confirmDelete(BuildContext context, double sf, Color cardColor, Color text
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// ⚡ 1. FECHA LOGO O POP-UP!
|
||||
Navigator.pop(ctx);
|
||||
// ⚡ 2. AVISA O PAI PARA ESCONDER A EQUIPA DO ECRÃ NA HORA!
|
||||
onDelete();
|
||||
|
||||
// 3. APAGA NO FUNDO (Sem o utilizador ficar à espera)
|
||||
controller.deleteTeam(team.id).catchError((e) {
|
||||
if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Erro ao eliminar: $e'), backgroundColor: Colors.red));
|
||||
});
|
||||
@@ -401,7 +406,7 @@ class _CreateTeamDialogState extends State<CreateTeamDialog> {
|
||||
|
||||
File? _selectedImage;
|
||||
bool _isLoading = false;
|
||||
bool _isPickerActive = false; // 👇 ESCUDO ANTI-DUPLO-CLIQUE
|
||||
bool _isPickerActive = false;
|
||||
|
||||
Future<void> _pickImage() async {
|
||||
if (_isPickerActive) return;
|
||||
@@ -412,7 +417,6 @@ class _CreateTeamDialogState extends State<CreateTeamDialog> {
|
||||
final XFile? pickedFile = await picker.pickImage(source: ImageSource.gallery);
|
||||
|
||||
if (pickedFile != null) {
|
||||
// 👇 USA O CROPPER QUE CONFIGURASTE PARA AS CARAS
|
||||
CroppedFile? croppedFile = await ImageCropper().cropImage(
|
||||
sourcePath: pickedFile.path,
|
||||
aspectRatio: const CropAspectRatio(ratioX: 1, ratioY: 1),
|
||||
|
||||
Reference in New Issue
Block a user