fazer pdf
This commit is contained in:
@@ -78,7 +78,6 @@ class PlacarController extends ChangeNotifier {
|
||||
String? pendingPlayerId;
|
||||
List<ShotRecord> matchShots = [];
|
||||
|
||||
// Lista para o Histórico de Jogadas
|
||||
List<String> playByPlay = [];
|
||||
|
||||
ValueNotifier<Duration> durationNotifier = ValueNotifier(const Duration(minutes: 10));
|
||||
@@ -113,7 +112,6 @@ class PlacarController extends ChangeNotifier {
|
||||
|
||||
gameWasAlreadyFinished = gameResponse['status'] == 'Terminado';
|
||||
|
||||
// CARREGAR HISTÓRICO DA BASE DE DADOS
|
||||
if (gameResponse['play_by_play'] != null) {
|
||||
playByPlay = List<String>.from(gameResponse['play_by_play']);
|
||||
} else {
|
||||
@@ -147,6 +145,7 @@ class PlacarController extends ChangeNotifier {
|
||||
"stl": s['stl'] ?? 0, "tov": s['tov'] ?? 0, "blk": s['blk'] ?? 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,
|
||||
"p2m": s['p2m'] ?? 0, "p2a": s['p2a'] ?? 0, "p3m": s['p3m'] ?? 0, "p3a": s['p3a'] ?? 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,
|
||||
"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,
|
||||
"p2m": s['p2m'] ?? 0, "p2a": s['p2a'] ?? 0, "p3m": s['p3m'] ?? 0, "p3a": s['p3a'] ?? 0,
|
||||
};
|
||||
opponentFouls += (s['fls'] as int? ?? 0);
|
||||
}
|
||||
@@ -204,7 +204,8 @@ class PlacarController extends ChangeNotifier {
|
||||
|
||||
playerStats[id] = {
|
||||
"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) {
|
||||
@@ -231,7 +232,7 @@ class PlacarController extends ChangeNotifier {
|
||||
'playerStats': playerStats,
|
||||
'myCourt': myCourt, 'myBench': myBench, 'oppCourt': oppCourt, 'oppBench': oppBench,
|
||||
'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));
|
||||
} catch (e) {
|
||||
@@ -357,13 +358,8 @@ class PlacarController extends ChangeNotifier {
|
||||
String name = playerNames[playerId] ?? "Jogador";
|
||||
|
||||
matchShots.add(ShotRecord(
|
||||
relativeX: relativeX,
|
||||
relativeY: relativeY,
|
||||
isMake: isMake,
|
||||
playerId: playerId,
|
||||
playerName: name,
|
||||
zone: zone,
|
||||
points: points
|
||||
relativeX: relativeX, relativeY: relativeY, isMake: isMake,
|
||||
playerId: playerId, playerName: name, zone: zone, points: points
|
||||
));
|
||||
|
||||
String finalAction = isMake ? "add_pts_$points" : "miss_$points";
|
||||
@@ -440,8 +436,11 @@ class PlacarController extends ChangeNotifier {
|
||||
int pts = int.parse(action.split("_").last);
|
||||
if (isOpponent) opponentScore += pts; else myScore += 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; }
|
||||
|
||||
logText = "marcou $pts pontos 🏀";
|
||||
}
|
||||
else if (action.startsWith("sub_pts_")) {
|
||||
@@ -449,9 +448,18 @@ class PlacarController extends ChangeNotifier {
|
||||
if (isOpponent) { opponentScore = (opponentScore - pts < 0) ? 0 : opponentScore - pts; }
|
||||
else { myScore = (myScore - pts < 0) ? 0 : myScore - 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["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 (stats["ftm"]! > 0) stats["ftm"] = stats["ftm"]! - 1;
|
||||
@@ -460,12 +468,12 @@ class PlacarController extends ChangeNotifier {
|
||||
logText = "teve $pts pontos retirados ❌";
|
||||
}
|
||||
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_drb") { stats["drb"] = stats["drb"]! + 1; stats["rbs"] = stats["rbs"]! + 1; logText = "ganhou ressalto defensivo 🛡️"; }
|
||||
else if (action == "add_ast") {
|
||||
stats["ast"] = stats["ast"]! + 1;
|
||||
|
||||
if (playByPlay.isNotEmpty && playByPlay[0].contains("marcou") && !playByPlay[0].contains("Assistência")) {
|
||||
playByPlay[0] = "${playByPlay[0]} (Assistência: $name 🤝)";
|
||||
_saveLocalBackup();
|
||||
@@ -531,7 +539,6 @@ class PlacarController extends ChangeNotifier {
|
||||
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({
|
||||
'my_score': myScore,
|
||||
'opponent_score': opponentScore,
|
||||
@@ -545,7 +552,7 @@ class PlacarController extends ChangeNotifier {
|
||||
'top_rbs_name': topRbsName,
|
||||
'top_def_name': topDefName,
|
||||
'mvp_name': mvpName,
|
||||
'play_by_play': playByPlay, // Envia o histórico para a base de dados
|
||||
'play_by_play': playByPlay,
|
||||
}).eq('id', gameId);
|
||||
|
||||
if (isGameFinishedNow && !gameWasAlreadyFinished && myTeamDbId != null && oppTeamDbId != null) {
|
||||
@@ -579,6 +586,7 @@ class PlacarController extends ChangeNotifier {
|
||||
batchStats.add({
|
||||
'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'],
|
||||
'p2m': stats['p2m'], 'p2a': stats['p2a'], 'p3m': stats['p3m'], 'p3a': stats['p3a'],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,8 +6,8 @@ import '../controllers/team_controller.dart';
|
||||
import '../controllers/game_controller.dart';
|
||||
import '../models/game_model.dart';
|
||||
import '../utils/size_extension.dart';
|
||||
import 'pdf_export_service.dart';
|
||||
|
||||
// --- CARD DE EXIBIÇÃO DO JOGO ---
|
||||
class GameResultCard extends StatelessWidget {
|
||||
final String gameId, myTeam, opponentTeam, myScore, opponentScore, status, season;
|
||||
final String? myTeamLogo, opponentTeamLogo;
|
||||
@@ -44,14 +44,36 @@ class GameResultCard extends StatelessWidget {
|
||||
Expanded(child: _buildTeamInfo(opponentTeam, Colors.grey.shade600, opponentTeamLogo, sf, textColor)),
|
||||
],
|
||||
),
|
||||
|
||||
Positioned(
|
||||
top: -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),
|
||||
splashRadius: 20 * sf,
|
||||
tooltip: 'Eliminar Jogo',
|
||||
onPressed: () => _showDeleteConfirmation(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -141,7 +163,6 @@ class GameResultCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// --- POPUP DE CRIAÇÃO ---
|
||||
class CreateGameDialogManual extends StatefulWidget {
|
||||
final TeamController teamController;
|
||||
final GameController gameController;
|
||||
@@ -280,7 +301,6 @@ class _CreateGameDialogManualState extends State<CreateGameDialogManual> {
|
||||
}
|
||||
}
|
||||
|
||||
// --- PÁGINA PRINCIPAL DOS JOGOS ---
|
||||
class GamePage extends StatefulWidget {
|
||||
const GamePage({super.key});
|
||||
|
||||
@@ -351,9 +371,7 @@ class _GamePageState extends State<GamePage> {
|
||||
bool success = await gameController.deleteGame(game.id);
|
||||
if (context.mounted) {
|
||||
if (success) {
|
||||
// 👇 ISTO FORÇA A LISTA A ATUALIZAR IMEDIATAMENTE 👇
|
||||
setState(() {});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Jogo eliminado com sucesso!'), backgroundColor: Colors.green)
|
||||
);
|
||||
|
||||
374
lib/pages/pdf_export_service.dart
Normal file
374
lib/pages/pdf_export_service.dart
Normal 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('Nº', 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),
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <gtk/gtk_plugin.h>
|
||||
#include <printing/printing_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
@@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) gtk_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
|
||||
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 =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_linux
|
||||
gtk
|
||||
printing
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import Foundation
|
||||
import app_links
|
||||
import file_selector_macos
|
||||
import path_provider_foundation
|
||||
import printing
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
import url_launcher_macos
|
||||
@@ -16,6 +17,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
|
||||
112
pubspec.lock
112
pubspec.lock
@@ -41,6 +41,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.9"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -49,6 +57,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -85,10 +109,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
version: "1.4.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -296,6 +320,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.0"
|
||||
image_cropper:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -436,18 +468,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.18"
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.0"
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -488,6 +520,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -536,6 +576,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -560,6 +624,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
posix:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
postgrest:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -568,6 +640,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
printing:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: printing
|
||||
sha256: "689170c9ddb1bda85826466ba80378aa8993486d3c959a71cd7d2d80cb606692"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.14.3"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -576,6 +656,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -785,10 +873,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.9"
|
||||
version: "0.7.7"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -917,6 +1005,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -41,6 +41,8 @@ dependencies:
|
||||
shimmer: ^3.0.0
|
||||
cached_network_image: ^3.4.1
|
||||
shared_preferences: ^2.5.4
|
||||
printing: ^5.14.3
|
||||
pdf: ^3.12.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <app_links/app_links_plugin_c_api.h>
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <printing/printing_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
@@ -15,6 +16,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
PrintingPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PrintingPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
app_links
|
||||
file_selector_windows
|
||||
printing
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user