fazer pdf

This commit is contained in:
2026-04-14 17:19:21 +01:00
parent 2544e52636
commit fb85566e3f
10 changed files with 544 additions and 35 deletions

View File

@@ -78,7 +78,6 @@ class PlacarController extends ChangeNotifier {
String? pendingPlayerId; String? pendingPlayerId;
List<ShotRecord> matchShots = []; List<ShotRecord> matchShots = [];
// Lista para o Histórico de Jogadas
List<String> playByPlay = []; List<String> playByPlay = [];
ValueNotifier<Duration> durationNotifier = ValueNotifier(const Duration(minutes: 10)); ValueNotifier<Duration> durationNotifier = ValueNotifier(const Duration(minutes: 10));
@@ -113,7 +112,6 @@ class PlacarController extends ChangeNotifier {
gameWasAlreadyFinished = gameResponse['status'] == 'Terminado'; gameWasAlreadyFinished = gameResponse['status'] == 'Terminado';
// CARREGAR HISTÓRICO DA BASE DE DADOS
if (gameResponse['play_by_play'] != null) { if (gameResponse['play_by_play'] != null) {
playByPlay = List<String>.from(gameResponse['play_by_play']); playByPlay = List<String>.from(gameResponse['play_by_play']);
} else { } else {
@@ -147,6 +145,7 @@ class PlacarController extends ChangeNotifier {
"stl": s['stl'] ?? 0, "tov": s['tov'] ?? 0, "blk": s['blk'] ?? 0, "stl": s['stl'] ?? 0, "tov": s['tov'] ?? 0, "blk": s['blk'] ?? 0,
"fls": s['fls'] ?? 0, "fgm": s['fgm'] ?? 0, "fga": s['fga'] ?? 0, "fls": s['fls'] ?? 0, "fgm": s['fgm'] ?? 0, "fga": s['fga'] ?? 0,
"ftm": s['ftm'] ?? 0, "fta": s['fta'] ?? 0, "orb": s['orb'] ?? 0, "drb": s['drb'] ?? 0, "ftm": s['ftm'] ?? 0, "fta": s['fta'] ?? 0, "orb": s['orb'] ?? 0, "drb": s['drb'] ?? 0,
"p2m": s['p2m'] ?? 0, "p2a": s['p2a'] ?? 0, "p3m": s['p3m'] ?? 0, "p3a": s['p3a'] ?? 0,
}; };
myFouls += (s['fls'] as int? ?? 0); myFouls += (s['fls'] as int? ?? 0);
} }
@@ -166,6 +165,7 @@ class PlacarController extends ChangeNotifier {
"stl": s['stl'] ?? 0, "tov": s['tov'] ?? 0, "blk": s['blk'] ?? 0, "stl": s['stl'] ?? 0, "tov": s['tov'] ?? 0, "blk": s['blk'] ?? 0,
"fls": s['fls'] ?? 0, "fgm": s['fgm'] ?? 0, "fga": s['fga'] ?? 0, "fls": s['fls'] ?? 0, "fgm": s['fgm'] ?? 0, "fga": s['fga'] ?? 0,
"ftm": s['ftm'] ?? 0, "fta": s['fta'] ?? 0, "orb": s['orb'] ?? 0, "drb": s['drb'] ?? 0, "ftm": s['ftm'] ?? 0, "fta": s['fta'] ?? 0, "orb": s['orb'] ?? 0, "drb": s['drb'] ?? 0,
"p2m": s['p2m'] ?? 0, "p2a": s['p2a'] ?? 0, "p3m": s['p3m'] ?? 0, "p3a": s['p3a'] ?? 0,
}; };
opponentFouls += (s['fls'] as int? ?? 0); opponentFouls += (s['fls'] as int? ?? 0);
} }
@@ -204,7 +204,8 @@ class PlacarController extends ChangeNotifier {
playerStats[id] = { playerStats[id] = {
"pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0, "pts": 0, "rbs": 0, "ast": 0, "stl": 0, "tov": 0, "blk": 0,
"fls": 0, "fgm": 0, "fga": 0, "ftm": 0, "fta": 0, "orb": 0, "drb": 0 "fls": 0, "fgm": 0, "fga": 0, "ftm": 0, "fta": 0, "orb": 0, "drb": 0,
"p2m": 0, "p2a": 0, "p3m": 0, "p3a": 0
}; };
if (isMyTeam) { if (isMyTeam) {
@@ -231,7 +232,7 @@ class PlacarController extends ChangeNotifier {
'playerStats': playerStats, 'playerStats': playerStats,
'myCourt': myCourt, 'myBench': myBench, 'oppCourt': oppCourt, 'oppBench': oppBench, 'myCourt': myCourt, 'myBench': myBench, 'oppCourt': oppCourt, 'oppBench': oppBench,
'matchShots': matchShots.map((s) => s.toJson()).toList(), 'matchShots': matchShots.map((s) => s.toJson()).toList(),
'playByPlay': playByPlay, // Guarda o histórico no telemóvel 'playByPlay': playByPlay,
}; };
await prefs.setString('backup_$gameId', jsonEncode(backupData)); await prefs.setString('backup_$gameId', jsonEncode(backupData));
} catch (e) { } catch (e) {
@@ -357,13 +358,8 @@ class PlacarController extends ChangeNotifier {
String name = playerNames[playerId] ?? "Jogador"; String name = playerNames[playerId] ?? "Jogador";
matchShots.add(ShotRecord( matchShots.add(ShotRecord(
relativeX: relativeX, relativeX: relativeX, relativeY: relativeY, isMake: isMake,
relativeY: relativeY, playerId: playerId, playerName: name, zone: zone, points: points
isMake: isMake,
playerId: playerId,
playerName: name,
zone: zone,
points: points
)); ));
String finalAction = isMake ? "add_pts_$points" : "miss_$points"; String finalAction = isMake ? "add_pts_$points" : "miss_$points";
@@ -440,8 +436,11 @@ class PlacarController extends ChangeNotifier {
int pts = int.parse(action.split("_").last); int pts = int.parse(action.split("_").last);
if (isOpponent) opponentScore += pts; else myScore += pts; if (isOpponent) opponentScore += pts; else myScore += pts;
stats["pts"] = stats["pts"]! + pts; stats["pts"] = stats["pts"]! + pts;
if (pts == 2 || pts == 3) { stats["fgm"] = stats["fgm"]! + 1; stats["fga"] = stats["fga"]! + 1; }
if (pts == 2) { stats["fgm"] = stats["fgm"]! + 1; stats["fga"] = stats["fga"]! + 1; stats["p2m"] = stats["p2m"]! + 1; stats["p2a"] = stats["p2a"]! + 1; }
if (pts == 3) { stats["fgm"] = stats["fgm"]! + 1; stats["fga"] = stats["fga"]! + 1; stats["p3m"] = stats["p3m"]! + 1; stats["p3a"] = stats["p3a"]! + 1; }
if (pts == 1) { stats["ftm"] = stats["ftm"]! + 1; stats["fta"] = stats["fta"]! + 1; } if (pts == 1) { stats["ftm"] = stats["ftm"]! + 1; stats["fta"] = stats["fta"]! + 1; }
logText = "marcou $pts pontos 🏀"; logText = "marcou $pts pontos 🏀";
} }
else if (action.startsWith("sub_pts_")) { else if (action.startsWith("sub_pts_")) {
@@ -449,9 +448,18 @@ class PlacarController extends ChangeNotifier {
if (isOpponent) { opponentScore = (opponentScore - pts < 0) ? 0 : opponentScore - pts; } if (isOpponent) { opponentScore = (opponentScore - pts < 0) ? 0 : opponentScore - pts; }
else { myScore = (myScore - pts < 0) ? 0 : myScore - pts; } else { myScore = (myScore - pts < 0) ? 0 : myScore - pts; }
stats["pts"] = (stats["pts"]! - pts < 0) ? 0 : stats["pts"]! - pts; stats["pts"] = (stats["pts"]! - pts < 0) ? 0 : stats["pts"]! - pts;
if (pts == 2 || pts == 3) {
if (pts == 2) {
if (stats["fgm"]! > 0) stats["fgm"] = stats["fgm"]! - 1; if (stats["fgm"]! > 0) stats["fgm"] = stats["fgm"]! - 1;
if (stats["fga"]! > 0) stats["fga"] = stats["fga"]! - 1; if (stats["fga"]! > 0) stats["fga"] = stats["fga"]! - 1;
if (stats["p2m"]! > 0) stats["p2m"] = stats["p2m"]! - 1;
if (stats["p2a"]! > 0) stats["p2a"] = stats["p2a"]! - 1;
}
if (pts == 3) {
if (stats["fgm"]! > 0) stats["fgm"] = stats["fgm"]! - 1;
if (stats["fga"]! > 0) stats["fga"] = stats["fga"]! - 1;
if (stats["p3m"]! > 0) stats["p3m"] = stats["p3m"]! - 1;
if (stats["p3a"]! > 0) stats["p3a"] = stats["p3a"]! - 1;
} }
if (pts == 1) { if (pts == 1) {
if (stats["ftm"]! > 0) stats["ftm"] = stats["ftm"]! - 1; if (stats["ftm"]! > 0) stats["ftm"] = stats["ftm"]! - 1;
@@ -460,12 +468,12 @@ class PlacarController extends ChangeNotifier {
logText = "teve $pts pontos retirados ❌"; logText = "teve $pts pontos retirados ❌";
} }
else if (action == "miss_1") { stats["fta"] = stats["fta"]! + 1; logText = "falhou lance livre ❌"; } else if (action == "miss_1") { stats["fta"] = stats["fta"]! + 1; logText = "falhou lance livre ❌"; }
else if (action == "miss_2" || action == "miss_3") { stats["fga"] = stats["fga"]! + 1; logText = "falhou lançamento ❌"; } else if (action == "miss_2") { stats["fga"] = stats["fga"]! + 1; stats["p2a"] = stats["p2a"]! + 1; logText = "falhou lançamento de 2 "; }
else if (action == "miss_3") { stats["fga"] = stats["fga"]! + 1; stats["p3a"] = stats["p3a"]! + 1; logText = "falhou lançamento de 3 ❌"; }
else if (action == "add_orb") { stats["orb"] = stats["orb"]! + 1; stats["rbs"] = stats["rbs"]! + 1; logText = "ganhou ressalto ofensivo 🔄"; } else if (action == "add_orb") { stats["orb"] = stats["orb"]! + 1; stats["rbs"] = stats["rbs"]! + 1; logText = "ganhou ressalto ofensivo 🔄"; }
else if (action == "add_drb") { stats["drb"] = stats["drb"]! + 1; stats["rbs"] = stats["rbs"]! + 1; logText = "ganhou ressalto defensivo 🛡️"; } else if (action == "add_drb") { stats["drb"] = stats["drb"]! + 1; stats["rbs"] = stats["rbs"]! + 1; logText = "ganhou ressalto defensivo 🛡️"; }
else if (action == "add_ast") { else if (action == "add_ast") {
stats["ast"] = stats["ast"]! + 1; stats["ast"] = stats["ast"]! + 1;
if (playByPlay.isNotEmpty && playByPlay[0].contains("marcou") && !playByPlay[0].contains("Assistência")) { if (playByPlay.isNotEmpty && playByPlay[0].contains("marcou") && !playByPlay[0].contains("Assistência")) {
playByPlay[0] = "${playByPlay[0]} (Assistência: $name 🤝)"; playByPlay[0] = "${playByPlay[0]} (Assistência: $name 🤝)";
_saveLocalBackup(); _saveLocalBackup();
@@ -531,7 +539,6 @@ class PlacarController extends ChangeNotifier {
if (mvpScore > maxMvpScore && mvpScore > 0) { maxMvpScore = mvpScore; mvpName = pName; } if (mvpScore > maxMvpScore && mvpScore > 0) { maxMvpScore = mvpScore; mvpName = pName; }
}); });
// ATUALIZA O JOGO COM OS NOVOS ESTADOS E COM O HISTÓRICO DE JOGADAS!
await supabase.from('games').update({ await supabase.from('games').update({
'my_score': myScore, 'my_score': myScore,
'opponent_score': opponentScore, 'opponent_score': opponentScore,
@@ -545,7 +552,7 @@ class PlacarController extends ChangeNotifier {
'top_rbs_name': topRbsName, 'top_rbs_name': topRbsName,
'top_def_name': topDefName, 'top_def_name': topDefName,
'mvp_name': mvpName, 'mvp_name': mvpName,
'play_by_play': playByPlay, // Envia o histórico para a base de dados 'play_by_play': playByPlay,
}).eq('id', gameId); }).eq('id', gameId);
if (isGameFinishedNow && !gameWasAlreadyFinished && myTeamDbId != null && oppTeamDbId != null) { if (isGameFinishedNow && !gameWasAlreadyFinished && myTeamDbId != null && oppTeamDbId != null) {
@@ -579,6 +586,7 @@ class PlacarController extends ChangeNotifier {
batchStats.add({ batchStats.add({
'game_id': gameId, 'member_id': playerId, 'team_id': isMyTeamPlayer ? myTeamDbId! : oppTeamDbId!, 'game_id': gameId, 'member_id': playerId, 'team_id': isMyTeamPlayer ? myTeamDbId! : oppTeamDbId!,
'pts': stats['pts'], 'rbs': stats['rbs'], 'ast': stats['ast'], 'stl': stats['stl'], 'blk': stats['blk'], 'tov': stats['tov'], 'fls': stats['fls'], 'fgm': stats['fgm'], 'fga': stats['fga'], 'ftm': stats['ftm'], 'fta': stats['fta'], 'orb': stats['orb'], 'drb': stats['drb'], 'pts': stats['pts'], 'rbs': stats['rbs'], 'ast': stats['ast'], 'stl': stats['stl'], 'blk': stats['blk'], 'tov': stats['tov'], 'fls': stats['fls'], 'fgm': stats['fgm'], 'fga': stats['fga'], 'ftm': stats['ftm'], 'fta': stats['fta'], 'orb': stats['orb'], 'drb': stats['drb'],
'p2m': stats['p2m'], 'p2a': stats['p2a'], 'p3m': stats['p3m'], 'p3a': stats['p3a'],
}); });
} }
}); });

View File

@@ -6,8 +6,8 @@ import '../controllers/team_controller.dart';
import '../controllers/game_controller.dart'; import '../controllers/game_controller.dart';
import '../models/game_model.dart'; import '../models/game_model.dart';
import '../utils/size_extension.dart'; import '../utils/size_extension.dart';
import 'pdf_export_service.dart';
// --- CARD DE EXIBIÇÃO DO JOGO ---
class GameResultCard extends StatelessWidget { class GameResultCard extends StatelessWidget {
final String gameId, myTeam, opponentTeam, myScore, opponentScore, status, season; final String gameId, myTeam, opponentTeam, myScore, opponentScore, status, season;
final String? myTeamLogo, opponentTeamLogo; final String? myTeamLogo, opponentTeamLogo;
@@ -44,14 +44,36 @@ class GameResultCard extends StatelessWidget {
Expanded(child: _buildTeamInfo(opponentTeam, Colors.grey.shade600, opponentTeamLogo, sf, textColor)), Expanded(child: _buildTeamInfo(opponentTeam, Colors.grey.shade600, opponentTeamLogo, sf, textColor)),
], ],
), ),
Positioned( Positioned(
top: -10 * sf, top: -10 * sf,
right: -10 * sf, right: -10 * sf,
child: IconButton( child: Row(
children: [
IconButton(
icon: Icon(Icons.picture_as_pdf, color: AppTheme.primaryRed.withOpacity(0.8), size: 22 * sf),
splashRadius: 20 * sf,
tooltip: 'Gerar PDF',
onPressed: () async {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('A gerar PDF...'), duration: Duration(seconds: 1)));
await PdfExportService.generateAndPrintBoxScore(
gameId: gameId,
myTeam: myTeam,
opponentTeam: opponentTeam,
myScore: myScore,
opponentScore: opponentScore,
season: season,
);
},
),
IconButton(
icon: Icon(Icons.delete_outline, color: Colors.grey.shade400, size: 22 * sf), icon: Icon(Icons.delete_outline, color: Colors.grey.shade400, size: 22 * sf),
splashRadius: 20 * sf, splashRadius: 20 * sf,
tooltip: 'Eliminar Jogo',
onPressed: () => _showDeleteConfirmation(context), onPressed: () => _showDeleteConfirmation(context),
), ),
],
),
), ),
], ],
), ),
@@ -141,7 +163,6 @@ class GameResultCard extends StatelessWidget {
); );
} }
// --- POPUP DE CRIAÇÃO ---
class CreateGameDialogManual extends StatefulWidget { class CreateGameDialogManual extends StatefulWidget {
final TeamController teamController; final TeamController teamController;
final GameController gameController; final GameController gameController;
@@ -280,7 +301,6 @@ class _CreateGameDialogManualState extends State<CreateGameDialogManual> {
} }
} }
// --- PÁGINA PRINCIPAL DOS JOGOS ---
class GamePage extends StatefulWidget { class GamePage extends StatefulWidget {
const GamePage({super.key}); const GamePage({super.key});
@@ -351,9 +371,7 @@ class _GamePageState extends State<GamePage> {
bool success = await gameController.deleteGame(game.id); bool success = await gameController.deleteGame(game.id);
if (context.mounted) { if (context.mounted) {
if (success) { if (success) {
// 👇 ISTO FORÇA A LISTA A ATUALIZAR IMEDIATAMENTE 👇
setState(() {}); setState(() {});
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Jogo eliminado com sucesso!'), backgroundColor: Colors.green) const SnackBar(content: Text('Jogo eliminado com sucesso!'), backgroundColor: Colors.green)
); );

View File

@@ -0,0 +1,374 @@
import 'dart:typed_data';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
class PdfExportService {
static Future<void> generateAndPrintBoxScore({
required String gameId,
required String myTeam,
required String opponentTeam,
required String myScore,
required String opponentScore,
required String season,
}) async {
final supabase = Supabase.instance.client;
final gameData = await supabase.from('games').select().eq('id', gameId).single();
final teamsData = await supabase.from('teams').select('id, name').inFilter('name', [myTeam, opponentTeam]);
String? myTeamId, oppTeamId;
for (var t in teamsData) {
if (t['name'] == myTeam) myTeamId = t['id'].toString();
if (t['name'] == opponentTeam) oppTeamId = t['id'].toString();
}
List<dynamic> myPlayers = myTeamId != null ? await supabase.from('members').select().eq('team_id', myTeamId).eq('type', 'Jogador') : [];
List<dynamic> oppPlayers = oppTeamId != null ? await supabase.from('members').select().eq('team_id', oppTeamId).eq('type', 'Jogador') : [];
final statsData = await supabase.from('player_stats').select().eq('game_id', gameId);
Map<String, Map<String, dynamic>> statsMap = {};
for (var s in statsData) {
statsMap[s['member_id'].toString()] = s;
}
List<List<String>> myTeamTable = _buildTeamTableData(myPlayers, statsMap);
List<List<String>> oppTeamTable = _buildTeamTableData(oppPlayers, statsMap);
final pdf = pw.Document();
pdf.addPage(
pw.Page( // 1. Trocado de MultiPage para Page
pageFormat: PdfPageFormat.a4.landscape,
margin: const pw.EdgeInsets.all(16), // Margens ligeiramente reduzidas para aproveitar o espaço
build: (pw.Context context) {
// 2. Envolvemos tudo num FittedBox
return pw.FittedBox(
fit: pw.BoxFit.scaleDown, // Reduz o tamanho apenas se não couber na página
child: pw.Container(
// Fixamos a largura do contentor à largura útil da página
width: PdfPageFormat.a4.landscape.availableWidth,
// 3. Colocamos todos os elementos dentro de uma Column
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Text('Relatório Estatístico', style: pw.TextStyle(fontSize: 24, fontWeight: pw.FontWeight.bold)),
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.end,
children: [
pw.Text('$myTeam vs $opponentTeam', style: pw.TextStyle(fontSize: 16, fontWeight: pw.FontWeight.bold)),
pw.Text('Resultado: $myScore - $opponentScore', style: const pw.TextStyle(fontSize: 14)),
pw.Text('Época: $season', style: const pw.TextStyle(fontSize: 12)),
]
)
]
),
pw.SizedBox(height: 15), // Espaçamentos verticais um pouco mais otimizados
pw.Text('Equipa: $myTeam', style: pw.TextStyle(fontSize: 14, fontWeight: pw.FontWeight.bold, color: const PdfColor.fromInt(0xFFA00000))),
pw.SizedBox(height: 4),
_buildPdfTable(myTeamTable, const PdfColor.fromInt(0xFFA00000)),
pw.SizedBox(height: 15),
pw.Text('Equipa: $opponentTeam', style: pw.TextStyle(fontSize: 14, fontWeight: pw.FontWeight.bold, color: PdfColors.grey700)),
pw.SizedBox(height: 4),
_buildPdfTable(oppTeamTable, PdfColors.grey700),
pw.SizedBox(height: 15),
pw.Row(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSummaryBox('Melhor Marcador', gameData['top_pts_name'] ?? '---'),
pw.SizedBox(width: 10),
_buildSummaryBox('Melhor Ressaltador', gameData['top_rbs_name'] ?? '---'),
pw.SizedBox(width: 10),
_buildSummaryBox('Melhor Passador', gameData['top_ast_name'] ?? '---'),
pw.SizedBox(width: 10),
_buildSummaryBox('MVP', gameData['mvp_name'] ?? '---'),
]
),
],
),
),
);
},
),
);
await Printing.layoutPdf(
onLayout: (PdfPageFormat format) async => pdf.save(),
name: 'BoxScore_${myTeam}_vs_${opponentTeam}.pdf',
);
}
static List<List<String>> _buildTeamTableData(List<dynamic> players, Map<String, Map<String, dynamic>> statsMap) {
List<List<String>> tableData = [];
int tPts = 0, tFgm = 0, tFga = 0, tFtm = 0, tFta = 0, tFls = 0;
int tOrb = 0, tDrb = 0, tTr = 0, tStl = 0, tAst = 0, tTov = 0, tBlk = 0;
int tP3m = 0, tP2m = 0, tP3a = 0, tP2a = 0;
players.sort((a, b) {
int numA = int.tryParse(a['number']?.toString() ?? '0') ?? 0;
int numB = int.tryParse(b['number']?.toString() ?? '0') ?? 0;
return numA.compareTo(numB);
});
for (var p in players) {
String id = p['id'].toString();
String name = p['name'] ?? 'Desconhecido';
String number = p['number']?.toString() ?? '-';
var stat = statsMap[id] ?? {};
int pts = stat['pts'] ?? 0;
int fgm = stat['fgm'] ?? 0;
int fga = stat['fga'] ?? 0;
int ftm = stat['ftm'] ?? 0;
int fta = stat['fta'] ?? 0;
int p2m = stat['p2m'] ?? 0;
int p2a = stat['p2a'] ?? 0;
int p3m = stat['p3m'] ?? 0;
int p3a = stat['p3a'] ?? 0;
int fls = stat['fls'] ?? 0;
int orb = stat['orb'] ?? 0;
int drb = stat['drb'] ?? 0;
int tr = orb + drb;
int stl = stat['stl'] ?? 0;
int ast = stat['ast'] ?? 0;
int tov = stat['tov'] ?? 0;
int blk = stat['blk'] ?? 0;
tPts += pts; tFgm += fgm; tFga += fga; tFtm += ftm; tFta += fta;
tFls += fls; tOrb += orb; tDrb += drb; tTr += tr; tStl += stl;
tAst += ast; tTov += tov; tBlk += blk;
tP3m += p3m; tP2m += p2m; tP3a += p3a; tP2a += p2a;
String p2Pct = p2a > 0 ? ((p2m / p2a) * 100).toStringAsFixed(0) + '%' : '0%';
String p3Pct = p3a > 0 ? ((p3m / p3a) * 100).toStringAsFixed(0) + '%' : '0%';
String globalPct = fga > 0 ? ((fgm / fga) * 100).toStringAsFixed(0) + '%' : '0%';
String llPct = fta > 0 ? ((ftm / fta) * 100).toStringAsFixed(0) + '%' : '0%';
tableData.add([
number, name, pts.toString(),
p2m.toString(), p2a.toString(), p2Pct,
p3m.toString(), p3a.toString(), p3Pct,
fgm.toString(), fga.toString(), globalPct,
ftm.toString(), fta.toString(), llPct,
fls.toString(), orb.toString(), drb.toString(), tr.toString(),
stl.toString(), ast.toString(), tov.toString(), blk.toString()
]);
}
if (tableData.isEmpty) {
tableData.add([
'-', 'Sem jogadores registados', '0',
'0', '0', '0%',
'0', '0', '0%',
'0', '0', '0%',
'0', '0', '0%',
'0', '0', '0', '0', '0', '0', '0', '0'
]);
}
String tP2Pct = tP2a > 0 ? ((tP2m / tP2a) * 100).toStringAsFixed(0) + '%' : '0%';
String tP3Pct = tP3a > 0 ? ((tP3m / tP3a) * 100).toStringAsFixed(0) + '%' : '0%';
String tGlobalPct = tFga > 0 ? ((tFgm / tFga) * 100).toStringAsFixed(0) + '%' : '0%';
String tLlPct = tFta > 0 ? ((tFtm / tFta) * 100).toStringAsFixed(0) + '%' : '0%';
tableData.add([
'', 'TOTAIS', tPts.toString(),
tP2m.toString(), tP2a.toString(), tP2Pct,
tP3m.toString(), tP3a.toString(), tP3Pct,
tFgm.toString(), tFga.toString(), tGlobalPct,
tFtm.toString(), tFta.toString(), tLlPct,
tFls.toString(), tOrb.toString(), tDrb.toString(), tTr.toString(),
tStl.toString(), tAst.toString(), tTov.toString(), tBlk.toString()
]);
return tableData;
}
static pw.Widget _buildPdfTable(List<List<String>> data, PdfColor headerColor) {
final headerStyle = pw.TextStyle(color: PdfColors.white, fontWeight: pw.FontWeight.bold, fontSize: 8);
final subHeaderStyle = pw.TextStyle(color: PdfColors.white, fontWeight: pw.FontWeight.bold, fontSize: 7);
final cellStyle = const pw.TextStyle(fontSize: 8);
// Agora usamos apenas 15 colunas principais na tabela.
// Os grupos (2P, 3P, etc.) são subdivididos INTERNAMENTE para evitar erros de colSpan.
return pw.Table(
border: pw.TableBorder.all(color: PdfColors.grey400, width: 0.5),
columnWidths: {
0: const pw.FlexColumnWidth(1.2), // Nº
1: const pw.FlexColumnWidth(5.0), // NOME (Maior para caber nomes como S.Gilgeous-alexander)
2: const pw.FlexColumnWidth(1.5), // PT
3: const pw.FlexColumnWidth(4.5), // 2 PONTOS (Grupo de 3)
4: const pw.FlexColumnWidth(4.5), // 3 PONTOS (Grupo de 3)
5: const pw.FlexColumnWidth(4.5), // GLOBAL (Grupo de 3)
6: const pw.FlexColumnWidth(4.5), // L. LIVRES (Grupo de 3)
7: const pw.FlexColumnWidth(1.5), // FLS
8: const pw.FlexColumnWidth(1.5), // RO
9: const pw.FlexColumnWidth(1.5), // RD
10: const pw.FlexColumnWidth(1.5), // TR
11: const pw.FlexColumnWidth(1.5), // BR
12: const pw.FlexColumnWidth(1.5), // AS
13: const pw.FlexColumnWidth(1.5), // BP
14: const pw.FlexColumnWidth(1.5), // BLK
},
children: [
// --- LINHA 1: CABEÇALHOS ---
pw.TableRow(
decoration: pw.BoxDecoration(color: headerColor),
children: [
_simpleHeader('', subHeaderStyle),
_simpleHeader('NOME', subHeaderStyle, align: pw.Alignment.centerLeft),
_simpleHeader('PT', subHeaderStyle),
_groupHeader('2 PONTOS', headerStyle, subHeaderStyle),
_groupHeader('3 PONTOS', headerStyle, subHeaderStyle),
_groupHeader('GLOBAL', headerStyle, subHeaderStyle),
_groupHeader('L. LIVRES', headerStyle, subHeaderStyle),
_simpleHeader('FLS', subHeaderStyle),
_simpleHeader('RO', subHeaderStyle),
_simpleHeader('RD', subHeaderStyle),
_simpleHeader('TR', subHeaderStyle),
_simpleHeader('BR', subHeaderStyle),
_simpleHeader('AS', subHeaderStyle),
_simpleHeader('BP', subHeaderStyle),
_simpleHeader('BLK', subHeaderStyle),
],
),
// --- LINHAS 2+: DADOS ---
...data.map((row) {
bool isTotais = row[1] == 'TOTAIS';
var rowStyle = isTotais ? pw.TextStyle(fontSize: 8, fontWeight: pw.FontWeight.bold) : cellStyle;
return pw.TableRow(
decoration: pw.BoxDecoration(
color: isTotais ? PdfColors.grey200 : PdfColors.white,
),
children: [
_simpleData(row[0], rowStyle),
_simpleData(row[1], rowStyle, align: pw.Alignment.centerLeft),
_simpleData(row[2], rowStyle),
_groupData(row[3], row[4], row[5], rowStyle), // 2P: C, T, %
_groupData(row[6], row[7], row[8], rowStyle), // 3P: C, T, %
_groupData(row[9], row[10], row[11], rowStyle), // GLOBAL: C, T, %
_groupData(row[12], row[13], row[14], rowStyle), // L. LIVRES: C, T, %
_simpleData(row[15], rowStyle),
_simpleData(row[16], rowStyle),
_simpleData(row[17], rowStyle),
_simpleData(row[18], rowStyle),
_simpleData(row[19], rowStyle),
_simpleData(row[20], rowStyle),
_simpleData(row[21], rowStyle),
_simpleData(row[22], rowStyle),
],
);
}),
],
);
}
// ==== WIDGETS AUXILIARES PARA RESOLVER A ESTRUTURA DO PDF ====
// Cabeçalho simples (Colunas que não se dividem)
static pw.Widget _simpleHeader(String text, pw.TextStyle style, {pw.Alignment align = pw.Alignment.center}) {
return pw.Container(
alignment: align,
padding: const pw.EdgeInsets.symmetric(vertical: 2, horizontal: 2),
child: pw.Text(text, style: style),
);
}
// Dados simples
static pw.Widget _simpleData(String text, pw.TextStyle style, {pw.Alignment align = pw.Alignment.center}) {
return pw.Container(
alignment: align,
padding: const pw.EdgeInsets.symmetric(vertical: 3, horizontal: 2),
child: pw.Text(text, style: style),
);
}
// Cria a divisão do Cabeçalho (O falso ColSpan que une "2 PONTOS" sobre "C | T | %")
static pw.Widget _groupHeader(String title, pw.TextStyle hStyle, pw.TextStyle sStyle) {
return pw.Column(
children: [
pw.Container(
width: double.infinity,
alignment: pw.Alignment.center,
padding: const pw.EdgeInsets.symmetric(vertical: 2),
decoration: const pw.BoxDecoration(
border: pw.Border(bottom: pw.BorderSide(color: PdfColors.white, width: 0.5)),
),
child: pw.Text(title, style: hStyle),
),
pw.Row(
children: [
pw.Expanded(child: pw.Container(alignment: pw.Alignment.center, child: pw.Text('C', style: sStyle))),
pw.Container(width: 0.5, height: 10, color: PdfColors.white), // Divisória vertical manual
pw.Expanded(child: pw.Container(alignment: pw.Alignment.center, child: pw.Text('T', style: sStyle))),
pw.Container(width: 0.5, height: 10, color: PdfColors.white), // Divisória vertical manual
pw.Expanded(child: pw.Container(alignment: pw.Alignment.center, child: pw.Text('%', style: sStyle))),
],
),
],
);
}
static pw.Widget _groupData(String c, String t, String pct, pw.TextStyle style) {
return pw.Row(
children: [
pw.Expanded(
child: pw.Container(
alignment: pw.Alignment.center,
padding: const pw.EdgeInsets.symmetric(vertical: 3),
child: pw.Text(c, style: style),
),
),
pw.Container(width: 0.5, height: 12, color: PdfColors.grey400), // Divisória cinza
pw.Expanded(
child: pw.Container(
alignment: pw.Alignment.center,
padding: const pw.EdgeInsets.symmetric(vertical: 3),
child: pw.Text(t, style: style),
),
),
pw.Container(width: 0.5, height: 12, color: PdfColors.grey400), // Divisória cinza
pw.Expanded(
child: pw.Container(
alignment: pw.Alignment.center,
padding: const pw.EdgeInsets.symmetric(vertical: 3),
child: pw.Text(pct, style: style),
),
),
],
);
}
static pw.Widget _buildSummaryBox(String title, String value) {
return pw.Container(
width: 120,
decoration: pw.BoxDecoration(
border: pw.TableBorder.all(color: PdfColors.black, width: 1),
),
child: pw.Column(
children: [
pw.Container(
width: double.infinity,
padding: const pw.EdgeInsets.all(4),
color: const PdfColor.fromInt(0xFFA00000),
child: pw.Text(title, style: pw.TextStyle(color: PdfColors.white, fontSize: 9, fontWeight: pw.FontWeight.bold), textAlign: pw.TextAlign.center),
),
pw.Container(
width: double.infinity,
padding: const pw.EdgeInsets.all(6),
child: pw.Text(value, style: pw.TextStyle(fontSize: 10, fontWeight: pw.FontWeight.bold), textAlign: pw.TextAlign.center),
),
]
)
);
}
}

View File

@@ -8,6 +8,7 @@
#include <file_selector_linux/file_selector_plugin.h> #include <file_selector_linux/file_selector_plugin.h>
#include <gtk/gtk_plugin.h> #include <gtk/gtk_plugin.h>
#include <printing/printing_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
@@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) gtk_registrar = g_autoptr(FlPluginRegistrar) gtk_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
gtk_plugin_register_with_registrar(gtk_registrar); gtk_plugin_register_with_registrar(gtk_registrar);
g_autoptr(FlPluginRegistrar) printing_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin");
printing_plugin_register_with_registrar(printing_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View File

@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux file_selector_linux
gtk gtk
printing
url_launcher_linux url_launcher_linux
) )

View File

@@ -8,6 +8,7 @@ import Foundation
import app_links import app_links
import file_selector_macos import file_selector_macos
import path_provider_foundation import path_provider_foundation
import printing
import shared_preferences_foundation import shared_preferences_foundation
import sqflite_darwin import sqflite_darwin
import url_launcher_macos import url_launcher_macos
@@ -16,6 +17,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))

View File

@@ -41,6 +41,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
archive:
dependency: transitive
description:
name: archive
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
url: "https://pub.dev"
source: hosted
version: "4.0.9"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -49,6 +57,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.13.0" version: "2.13.0"
barcode:
dependency: transitive
description:
name: barcode
sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4"
url: "https://pub.dev"
source: hosted
version: "2.2.9"
bidi:
dependency: transitive
description:
name: bidi
sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d"
url: "https://pub.dev"
source: hosted
version: "2.0.13"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@@ -85,10 +109,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.1" version: "1.4.0"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@@ -296,6 +320,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.2" version: "4.1.2"
image:
dependency: transitive
description:
name: image
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
url: "https://pub.dev"
source: hosted
version: "4.8.0"
image_cropper: image_cropper:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -436,18 +468,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.18" version: "0.12.17"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.13.0" version: "0.11.1"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@@ -488,6 +520,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.9.1"
path_parsing:
dependency: transitive
description:
name: path_parsing
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
path_provider: path_provider:
dependency: transitive dependency: transitive
description: description:
@@ -536,6 +576,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
pdf:
dependency: "direct main"
description:
name: pdf
sha256: e47a275b267873d5944ad5f5ff0dcc7ac2e36c02b3046a0ffac9b72fd362c44b
url: "https://pub.dev"
source: hosted
version: "3.12.0"
pdf_widget_wrapper:
dependency: transitive
description:
name: pdf_widget_wrapper
sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5
url: "https://pub.dev"
source: hosted
version: "1.0.4"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
url: "https://pub.dev"
source: hosted
version: "7.0.2"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@@ -560,6 +624,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "4.0.0"
posix:
dependency: transitive
description:
name: posix
sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
url: "https://pub.dev"
source: hosted
version: "6.5.0"
postgrest: postgrest:
dependency: transitive dependency: transitive
description: description:
@@ -568,6 +640,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.6.0" version: "2.6.0"
printing:
dependency: "direct main"
description:
name: printing
sha256: "689170c9ddb1bda85826466ba80378aa8993486d3c959a71cd7d2d80cb606692"
url: "https://pub.dev"
source: hosted
version: "5.14.3"
provider: provider:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -576,6 +656,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.5+1" version: "6.1.5+1"
qr:
dependency: transitive
description:
name: qr
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
realtime_client: realtime_client:
dependency: transitive dependency: transitive
description: description:
@@ -785,10 +873,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.9" version: "0.7.7"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -917,6 +1005,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
url: "https://pub.dev"
source: hosted
version: "6.6.1"
yet_another_json_isolate: yet_another_json_isolate:
dependency: transitive dependency: transitive
description: description:

View File

@@ -41,6 +41,8 @@ dependencies:
shimmer: ^3.0.0 shimmer: ^3.0.0
cached_network_image: ^3.4.1 cached_network_image: ^3.4.1
shared_preferences: ^2.5.4 shared_preferences: ^2.5.4
printing: ^5.14.3
pdf: ^3.12.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -8,6 +8,7 @@
#include <app_links/app_links_plugin_c_api.h> #include <app_links/app_links_plugin_c_api.h>
#include <file_selector_windows/file_selector_windows.h> #include <file_selector_windows/file_selector_windows.h>
#include <printing/printing_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
@@ -15,6 +16,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("AppLinksPluginCApi")); registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
FileSelectorWindowsRegisterWithRegistrar( FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows")); registry->GetRegistrarForPlugin("FileSelectorWindows"));
PrintingPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PrintingPlugin"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows")); registry->GetRegistrarForPlugin("UrlLauncherWindows"));
} }

View File

@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
app_links app_links
file_selector_windows file_selector_windows
printing
url_launcher_windows url_launcher_windows
) )