Conflitos resolvidos: mantido o código local

This commit is contained in:
2026-03-16 22:28:41 +00:00
20 changed files with 1244 additions and 1171 deletions

View File

@@ -1,18 +1,27 @@
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';
class GameResultCard extends StatelessWidget {
final String gameId, myTeam, opponentTeam, myScore, opponentScore, status, season;
final String? myTeamLogo, opponentTeamLogo;
final double sf;
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
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,
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
});
@override
@@ -22,76 +31,291 @@ class GameResultCard extends StatelessWidget {
final textColor = Theme.of(context).colorScheme.onSurface;
return Container(
margin: EdgeInsets.only(bottom: 16 * sf),
padding: EdgeInsets.all(16 * sf),
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: bgColor, // Usa a cor do tema
color: Colors.white,
borderRadius: BorderRadius.circular(20 * sf),
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 10 * sf)],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: _buildTeamInfo(myTeam, AppTheme.primaryRed, myTeamLogo, sf, textColor)), // Usa o primaryRed
Expanded(child: _buildTeamInfo(myTeam, const Color(0xFFE74C3C), myTeamLogo, sf)),
_buildScoreCenter(context, gameId, sf),
Expanded(child: _buildTeamInfo(opponentTeam, textColor, opponentTeamLogo, sf, textColor)),
Expanded(child: _buildTeamInfo(opponentTeam, Colors.black87, opponentTeamLogo, sf)),
],
),
);
}
Widget _buildTeamInfo(String name, Color color, String? logoUrl, double sf, Color textColor) {
Widget _buildTeamInfo(String name, Color color, String? logoUrl, double sf) {
return Column(
children: [
CircleAvatar(
radius: 24 * sf,
radius: 24 * sf, // Ajuste do tamanho do logo
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),
const SizedBox(height: 4),
Text(name,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13 * sf, color: textColor), // Adapta à noite/dia
textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, maxLines: 2,
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
),
],
);
}
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, AppTheme.successGreen, sf), // Verde do tema
Text(" : ", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22 * sf, color: textColor)),
_scoreBox(myScore, Colors.green, sf),
Text(" : ", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22 * sf)),
_scoreBox(opponentScore, Colors.grey, sf),
],
),
SizedBox(height: 10 * sf),
const SizedBox(height: 8),
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: AppTheme.primaryRed),
label: Text("RETORNAR", style: TextStyle(fontSize: 11 * sf, color: AppTheme.primaryRed, 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: const Color(0xFFE74C3C)),
label: Text("RETORNAR", style: TextStyle(fontSize: 11 * sf, color: const Color(0xFFE74C3C), fontWeight: FontWeight.bold)),
style: TextButton.styleFrom(
backgroundColor: AppTheme.primaryRed.withOpacity(0.1),
backgroundColor: const Color(0xFFE74C3C).withOpacity(0.1),
padding: EdgeInsets.symmetric(horizontal: 14 * sf, vertical: 8 * sf),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20 * sf)),
visualDensity: VisualDensity.compact,
),
),
SizedBox(height: 6 * sf),
Text(status, style: TextStyle(fontSize: 12 * sf, color: Colors.blue, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(status, style: const TextStyle(fontSize: 10, color: Colors.blue, fontWeight: FontWeight.bold)),
],
);
}
Widget _scoreBox(String pts, Color c, double sf) => Container(
padding: EdgeInsets.symmetric(horizontal: 12 * sf, vertical: 6 * sf),
decoration: BoxDecoration(color: c, borderRadius: BorderRadius.circular(8 * sf)),
child: Text(pts, style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16 * sf)),
Widget _scoreBox(String pts, Color c) => Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(color: c, borderRadius: BorderRadius.circular(8)),
child: Text(pts, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
);
}
// --- 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()
),
);
},
);
},
);
}
}