mjn
This commit is contained in:
208
lib/calibrador_page.dart
Normal file
208
lib/calibrador_page.dart
Normal file
@@ -0,0 +1,208 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
class CalibradorPage extends StatefulWidget {
|
||||
const CalibradorPage({super.key});
|
||||
|
||||
@override
|
||||
State<CalibradorPage> createState() => _CalibradorPageState();
|
||||
}
|
||||
|
||||
class _CalibradorPageState extends State<CalibradorPage> {
|
||||
// --- 👇 VALORES INICIAIS 👇 ---
|
||||
double hoopBaseX = 0.08;
|
||||
double arcRadius = 0.28;
|
||||
double cornerY = 0.40;
|
||||
// -----------------------------------------------------
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.landscapeRight,
|
||||
DeviceOrientation.landscapeLeft,
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double wScreen = MediaQuery.of(context).size.width;
|
||||
final double hScreen = MediaQuery.of(context).size.height;
|
||||
|
||||
// O MESMO CÁLCULO EXATO DO PLACAR
|
||||
final double sf = math.min(wScreen / 1150, hScreen / 720);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFF266174),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: Stack(
|
||||
children: [
|
||||
// 👇 1. O CAMPO COM AS MARGENS EXATAS DO PLACAR 👇
|
||||
Container(
|
||||
margin: EdgeInsets.only(left: 65 * sf, right: 65 * sf, bottom: 55 * sf),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.white, width: 2.5),
|
||||
image: const DecorationImage(
|
||||
image: AssetImage('assets/campo.png'),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return CustomPaint(
|
||||
painter: LinePainter(
|
||||
hoopBaseX: hoopBaseX,
|
||||
arcRadius: arcRadius,
|
||||
cornerY: cornerY,
|
||||
color: Colors.redAccent,
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// 👇 2. TOPO: MOSTRADORES DE VALORES COM FITTEDBOX (Não transborda) 👇
|
||||
Positioned(
|
||||
top: 0, left: 0, right: 0,
|
||||
child: Container(
|
||||
color: Colors.black87.withOpacity(0.8),
|
||||
padding: EdgeInsets.symmetric(vertical: 5 * sf, horizontal: 15 * sf),
|
||||
child: FittedBox( // Isto impede o ecrã de dar o erro dos 179 pixels!
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildValueDisplay("Aro X", hoopBaseX, sf),
|
||||
SizedBox(width: 20 * sf),
|
||||
_buildValueDisplay("Raio", arcRadius, sf),
|
||||
SizedBox(width: 20 * sf),
|
||||
_buildValueDisplay("Canto", cornerY, sf),
|
||||
SizedBox(width: 30 * sf),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: Icon(Icons.check, size: 18 * sf),
|
||||
label: Text("FECHAR", style: TextStyle(fontSize: 14 * sf, fontWeight: FontWeight.bold)),
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 👇 3. FUNDO: SLIDERS (Com altura fixa para não dar o erro "hasSize") 👇
|
||||
Positioned(
|
||||
bottom: 0, left: 0, right: 0,
|
||||
child: Container(
|
||||
color: Colors.black87.withOpacity(0.8),
|
||||
height: 80 * sf, // Altura segura para os sliders
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: _buildSlider("Pos. do Aro", hoopBaseX, 0.0, 0.25, (val) => setState(() => hoopBaseX = val), sf)),
|
||||
Expanded(child: _buildSlider("Tam. da Curva", arcRadius, 0.1, 0.5, (val) => setState(() => arcRadius = val), sf)),
|
||||
Expanded(child: _buildSlider("Pos. do Canto", cornerY, 0.2, 0.5, (val) => setState(() => cornerY = val), sf)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildValueDisplay(String label, double value, double sf) {
|
||||
return Row(
|
||||
children: [
|
||||
Text("$label: ", style: TextStyle(color: Colors.white70, fontSize: 16 * sf)),
|
||||
Text(value.toStringAsFixed(3), style: TextStyle(color: Colors.yellow, fontSize: 20 * sf, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSlider(String label, double value, double min, double max, ValueChanged<double> onChanged, double sf) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(label, style: TextStyle(color: Colors.white, fontSize: 12 * sf)),
|
||||
SizedBox(
|
||||
height: 40 * sf, // Altura exata para o Slider não crashar
|
||||
child: Slider(
|
||||
value: value, min: min, max: max,
|
||||
activeColor: Colors.yellow, inactiveColor: Colors.white24,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================================
|
||||
// 📐 PINTOR: DESENHA A LINHA MATEMÁTICA NA TELA
|
||||
// ==============================================================
|
||||
class LinePainter extends CustomPainter {
|
||||
final double hoopBaseX;
|
||||
final double arcRadius;
|
||||
final double cornerY;
|
||||
final Color color;
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
LinePainter({
|
||||
required this.hoopBaseX, required this.arcRadius, required this.cornerY,
|
||||
required this.color, required this.width, required this.height,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = color
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 4;
|
||||
|
||||
double aspectRatio = width / height;
|
||||
double hoopY = 0.50 * height;
|
||||
|
||||
// O cornerY controla a que distância do meio (50%) estão as linhas retas
|
||||
double cornerDistY = cornerY * height;
|
||||
|
||||
// --- CESTO ESQUERDO ---
|
||||
double hoopLX = hoopBaseX * width;
|
||||
|
||||
canvas.drawLine(Offset(0, hoopY - cornerDistY), Offset(width * 0.35, hoopY - cornerDistY), paint); // Cima
|
||||
canvas.drawLine(Offset(0, hoopY + cornerDistY), Offset(width * 0.35, hoopY + cornerDistY), paint); // Baixo
|
||||
|
||||
canvas.drawArc(
|
||||
Rect.fromCenter(center: Offset(hoopLX, hoopY), width: arcRadius * width * 2 / aspectRatio, height: arcRadius * height * 2),
|
||||
-math.pi / 2, math.pi, false, paint,
|
||||
);
|
||||
|
||||
// --- CESTO DIREITO ---
|
||||
double hoopRX = (1.0 - hoopBaseX) * width;
|
||||
|
||||
canvas.drawLine(Offset(width, hoopY - cornerDistY), Offset(width * 0.65, hoopY - cornerDistY), paint); // Cima
|
||||
canvas.drawLine(Offset(width, hoopY + cornerDistY), Offset(width * 0.65, hoopY + cornerDistY), paint); // Baixo
|
||||
|
||||
canvas.drawArc(
|
||||
Rect.fromCenter(center: Offset(hoopRX, hoopY), width: arcRadius * width * 2 / aspectRatio, height: arcRadius * height * 2),
|
||||
math.pi / 2, math.pi, false, paint,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant LinePainter oldDelegate) {
|
||||
return oldDelegate.hoopBaseX != hoopBaseX || oldDelegate.arcRadius != arcRadius || oldDelegate.cornerY != cornerY;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
@@ -6,7 +7,7 @@ class ShotRecord {
|
||||
final double relativeX;
|
||||
final double relativeY;
|
||||
final bool isMake;
|
||||
final String playerName; // Bónus: Agora guardamos quem foi o jogador!
|
||||
final String playerName;
|
||||
|
||||
ShotRecord({
|
||||
required this.relativeX,
|
||||
@@ -32,7 +33,6 @@ class PlacarController {
|
||||
bool isLoading = true;
|
||||
bool isSaving = false;
|
||||
|
||||
// 👇 TRINCO DE SEGURANÇA: Evita contar vitórias duas vezes se clicares no Guardar repetidamente!
|
||||
bool gameWasAlreadyFinished = false;
|
||||
|
||||
int myScore = 0;
|
||||
@@ -67,7 +67,12 @@ class PlacarController {
|
||||
Timer? timer;
|
||||
bool isRunning = false;
|
||||
|
||||
// --- 🔄 CARREGAMENTO COMPLETO (DADOS REAIS + ESTATÍSTICAS SALVAS) ---
|
||||
// 👇 VARIÁVEIS DE CALIBRAÇÃO DO CAMPO (OS TEUS NÚMEROS!) 👇
|
||||
bool isCalibrating = false;
|
||||
double hoopBaseX = 0.088;
|
||||
double arcRadius = 0.459;
|
||||
double cornerY = 0.440;
|
||||
|
||||
Future<void> loadPlayers() async {
|
||||
final supabase = Supabase.instance.client;
|
||||
try {
|
||||
@@ -95,7 +100,6 @@ class PlacarController {
|
||||
opponentTimeoutsUsed = int.tryParse(gameResponse['opp_timeouts']?.toString() ?? '0') ?? 0;
|
||||
currentQuarter = int.tryParse(gameResponse['current_quarter']?.toString() ?? '1') ?? 1;
|
||||
|
||||
// 👇 Verifica se o jogo já tinha acabado noutra sessão
|
||||
gameWasAlreadyFinished = gameResponse['status'] == 'Terminado';
|
||||
|
||||
final teamsResponse = await supabase.from('teams').select('id, name').inFilter('name', [myTeam, opponentTeam]);
|
||||
@@ -269,29 +273,34 @@ class PlacarController {
|
||||
onUpdate();
|
||||
}
|
||||
|
||||
void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
// =========================================================================
|
||||
// 👇 A MÁGICA DOS PONTOS ACONTECE AQUI 👇
|
||||
// =========================================================================
|
||||
void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
if (pendingAction == null || pendingPlayer == null) return;
|
||||
|
||||
bool is3Pt = pendingAction!.contains("_3");
|
||||
bool is2Pt = pendingAction!.contains("_2");
|
||||
|
||||
// O ÁRBITRO MATEMÁTICO COM AS TUAS VARIÁVEIS CALIBRADAS
|
||||
if (is3Pt || is2Pt) {
|
||||
bool isValid = _validateShotZone(position, size, is3Pt);
|
||||
|
||||
// SE A JOGADA FOI NO SÍTIO ERRADO
|
||||
if (!isValid) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('🛑 Local incompatível com a pontuação.'), backgroundColor: Colors.red, duration: Duration(seconds: 2)));
|
||||
return;
|
||||
|
||||
return; // <-- ESTE RETURN BLOQUEIA A GRAVAÇÃO DO PONTO!
|
||||
}
|
||||
}
|
||||
|
||||
// SE A JOGADA FOI VÁLIDA:
|
||||
bool isMake = pendingAction!.startsWith("add_pts_");
|
||||
|
||||
// 👇 A MÁGICA DAS COORDENADAS RELATIVAS (0.0 a 1.0) 👇
|
||||
double relX = position.dx / size.width;
|
||||
double relY = position.dy / size.height;
|
||||
|
||||
// Extrai só o nome do jogador
|
||||
String name = pendingPlayer!.replaceAll("player_my_", "").replaceAll("player_opp_", "");
|
||||
|
||||
// Guarda na lista!
|
||||
matchShots.add(ShotRecord(
|
||||
relativeX: relX,
|
||||
relativeY: relY,
|
||||
@@ -307,18 +316,36 @@ void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
onUpdate();
|
||||
}
|
||||
|
||||
bool _validateShotZone(Offset pos, Size size, bool is3Pt) {
|
||||
double w = size.width; double h = size.height;
|
||||
Offset leftHoop = Offset(w * 0.12, h * 0.5);
|
||||
Offset rightHoop = Offset(w * 0.88, h * 0.5);
|
||||
double threePointRadius = w * 0.28;
|
||||
Offset activeHoop = pos.dx < w / 2 ? leftHoop : rightHoop;
|
||||
double distanceToHoop = (pos - activeHoop).distance;
|
||||
bool isCorner3 = (pos.dy < h * 0.15 || pos.dy > h * 0.85) && (pos.dx < w * 0.20 || pos.dx > w * 0.80);
|
||||
bool _validateShotZone(Offset position, Size size, bool is3Pt) {
|
||||
double relX = position.dx / size.width;
|
||||
double relY = position.dy / size.height;
|
||||
|
||||
if (is3Pt) return distanceToHoop >= threePointRadius || isCorner3;
|
||||
else return distanceToHoop < threePointRadius && !isCorner3;
|
||||
bool isLeftHalf = relX < 0.5;
|
||||
double hoopX = isLeftHalf ? hoopBaseX : (1.0 - hoopBaseX);
|
||||
double hoopY = 0.50;
|
||||
|
||||
double aspectRatio = size.width / size.height;
|
||||
double distFromCenterY = (relY - hoopY).abs();
|
||||
|
||||
bool isInside2Pts;
|
||||
|
||||
// Lógica das laterais (Cantos)
|
||||
if (distFromCenterY > cornerY) {
|
||||
double distToBaseline = isLeftHalf ? relX : (1.0 - relX);
|
||||
isInside2Pts = distToBaseline <= hoopBaseX;
|
||||
}
|
||||
// Lógica da Curva Frontal
|
||||
else {
|
||||
double dx = (relX - hoopX) * aspectRatio;
|
||||
double dy = (relY - hoopY);
|
||||
double distanceToHoop = math.sqrt((dx * dx) + (dy * dy));
|
||||
isInside2Pts = distanceToHoop < arcRadius;
|
||||
}
|
||||
|
||||
if (is3Pt) return !isInside2Pts;
|
||||
return isInside2Pts;
|
||||
}
|
||||
// 👆 ===================================================================== 👆
|
||||
|
||||
void cancelShotLocation() {
|
||||
isSelectingShotLocation = false; pendingAction = null; pendingPlayer = null; onUpdate();
|
||||
@@ -368,7 +395,6 @@ void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- 💾 FUNÇÃO PARA GUARDAR DADOS NA BD ---
|
||||
Future<void> saveGameStats(BuildContext context) async {
|
||||
final supabase = Supabase.instance.client;
|
||||
isSaving = true;
|
||||
@@ -378,14 +404,12 @@ void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
bool isGameFinishedNow = currentQuarter >= 4 && duration.inSeconds == 0;
|
||||
String newStatus = isGameFinishedNow ? 'Terminado' : 'Pausado';
|
||||
|
||||
// 👇👇👇 0. CÉREBRO: CALCULAR OS LÍDERES E MVP DO JOGO 👇👇👇
|
||||
String topPtsName = '---'; int maxPts = -1;
|
||||
String topAstName = '---'; int maxAst = -1;
|
||||
String topRbsName = '---'; int maxRbs = -1;
|
||||
String topDefName = '---'; int maxDef = -1;
|
||||
String mvpName = '---'; int maxMvpScore = -1;
|
||||
|
||||
// Passa por todos os jogadores e calcula a matemática
|
||||
playerStats.forEach((playerName, stats) {
|
||||
int pts = stats['pts'] ?? 0;
|
||||
int ast = stats['ast'] ?? 0;
|
||||
@@ -393,19 +417,16 @@ void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
int stl = stats['stl'] ?? 0;
|
||||
int blk = stats['blk'] ?? 0;
|
||||
|
||||
int defScore = stl + blk; // Defesa: Roubos + Cortes
|
||||
int mvpScore = pts + ast + rbs + defScore; // Impacto Total (MVP)
|
||||
int defScore = stl + blk;
|
||||
int mvpScore = pts + ast + rbs + defScore;
|
||||
|
||||
// Compara com o máximo atual e substitui se for maior
|
||||
if (pts > maxPts && pts > 0) { maxPts = pts; topPtsName = '$playerName ($pts)'; }
|
||||
if (ast > maxAst && ast > 0) { maxAst = ast; topAstName = '$playerName ($ast)'; }
|
||||
if (rbs > maxRbs && rbs > 0) { maxRbs = rbs; topRbsName = '$playerName ($rbs)'; }
|
||||
if (defScore > maxDef && defScore > 0) { maxDef = defScore; topDefName = '$playerName ($defScore)'; }
|
||||
if (mvpScore > maxMvpScore && mvpScore > 0) { maxMvpScore = mvpScore; mvpName = playerName; } // MVP não leva nº à frente, fica mais limpo
|
||||
if (mvpScore > maxMvpScore && mvpScore > 0) { maxMvpScore = mvpScore; mvpName = playerName; }
|
||||
});
|
||||
// 👆👆👆 FIM DO CÉREBRO 👆👆👆
|
||||
|
||||
// 1. Atualizar o Jogo na BD (Agora inclui os Reis da partida!)
|
||||
await supabase.from('games').update({
|
||||
'my_score': myScore,
|
||||
'opponent_score': opponentScore,
|
||||
@@ -414,8 +435,6 @@ void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
'opp_timeouts': opponentTimeoutsUsed,
|
||||
'current_quarter': currentQuarter,
|
||||
'status': newStatus,
|
||||
|
||||
// ENVIA A MATEMÁTICA PARA A TUA BASE DE DADOS
|
||||
'top_pts_name': topPtsName,
|
||||
'top_ast_name': topAstName,
|
||||
'top_rbs_name': topRbsName,
|
||||
@@ -423,7 +442,6 @@ void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
'mvp_name': mvpName,
|
||||
}).eq('id', gameId);
|
||||
|
||||
// 2. LÓGICA DE VITÓRIAS, DERROTAS E EMPATES
|
||||
if (isGameFinishedNow && !gameWasAlreadyFinished && myTeamDbId != null && oppTeamDbId != null) {
|
||||
|
||||
final teamsData = await supabase.from('teams').select('id, wins, losses, draws').inFilter('id', [myTeamDbId, oppTeamDbId]);
|
||||
@@ -458,7 +476,6 @@ void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
gameWasAlreadyFinished = true;
|
||||
}
|
||||
|
||||
// 3. Atualizar as Estatísticas dos Jogadores
|
||||
List<Map<String, dynamic>> batchStats = [];
|
||||
playerStats.forEach((playerName, stats) {
|
||||
String? memberDbId = playerDbIds[playerName];
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../controllers/placar_controller.dart'; // Ajusta o caminho se for preciso
|
||||
|
||||
class HeatmapPage extends StatefulWidget {
|
||||
final List<ShotRecord> shots;
|
||||
final String teamName;
|
||||
|
||||
const HeatmapPage({super.key, required this.shots, required this.teamName});
|
||||
|
||||
@override
|
||||
State<HeatmapPage> createState() => _HeatmapPageState();
|
||||
}
|
||||
|
||||
class _HeatmapPageState extends State<HeatmapPage> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Força o ecrã a ficar deitado para vermos bem o campo
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.landscapeRight,
|
||||
DeviceOrientation.landscapeLeft,
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Volta ao normal quando saímos
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFF16202C),
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
title: Text("Mapa de Lançamentos - ${widget.teamName}", style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
),
|
||||
body: Center(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1150 / 720, // Mantém o campo proporcional
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 20, left: 20, right: 20),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.white, width: 3),
|
||||
image: const DecorationImage(
|
||||
image: AssetImage('assets/campo.png'),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final double w = constraints.maxWidth;
|
||||
final double h = constraints.maxHeight;
|
||||
|
||||
return Stack(
|
||||
children: widget.shots.map((shot) {
|
||||
// 👇 Converte de volta de % para Pixels reais do ecrã atual
|
||||
double pixelX = shot.relativeX * w;
|
||||
double pixelY = shot.relativeY * h;
|
||||
|
||||
return Positioned(
|
||||
left: pixelX - 12, // -12 para centrar a bolinha
|
||||
top: pixelY - 12,
|
||||
child: Tooltip(
|
||||
message: "${shot.playerName}\n${shot.isMake ? 'Cesto' : 'Falha'}",
|
||||
child: CircleAvatar(
|
||||
radius: 12,
|
||||
backgroundColor: shot.isMake ? Colors.green.withOpacity(0.85) : Colors.red.withOpacity(0.85),
|
||||
child: Icon(
|
||||
shot.isMake ? Icons.check : Icons.close,
|
||||
size: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
16
pubspec.lock
16
pubspec.lock
@@ -61,10 +61,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.4.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -268,18 +268,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
version: "0.12.18"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
version: "0.13.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -553,10 +553,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.7"
|
||||
version: "0.7.9"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user