refactor: reglages de calibration au-dessus de la photo + boutons de taille fonctionnels
This commit is contained in:
@@ -110,6 +110,9 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
bool _isCalibrating = true;
|
bool _isCalibrating = true;
|
||||||
bool _isAtBottom = false;
|
bool _isAtBottom = false;
|
||||||
|
|
||||||
|
// Affichage du réglage manuel de l'espacement des anneaux.
|
||||||
|
bool _showSpacing = false;
|
||||||
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
final GlobalKey _imageKey = GlobalKey();
|
final GlobalKey _imageKey = GlobalKey();
|
||||||
|
|
||||||
@@ -143,7 +146,12 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
/// La cible du mode Plotting est désormais un élément fixe (aucun zoom à
|
/// La cible du mode Plotting est désormais un élément fixe (aucun zoom à
|
||||||
/// réinitialiser) : on se contente donc de rebasculer l'état.
|
/// réinitialiser) : on se contente donc de rebasculer l'état.
|
||||||
void _enterCalibration() {
|
void _enterCalibration() {
|
||||||
setState(() => _isCalibrating = true);
|
setState(() {
|
||||||
|
_isCalibrating = true;
|
||||||
|
// La calibration est reconstruite à neuf (espacement manuel désactivé) :
|
||||||
|
// on aligne l'état du panneau pour ne pas afficher un mode inactif.
|
||||||
|
_showSpacing = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ouvre l'éditeur d'impacts (plein écran) en PARTAGEANT le provider courant.
|
/// Ouvre l'éditeur d'impacts (plein écran) en PARTAGEANT le provider courant.
|
||||||
@@ -182,6 +190,113 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
return widget.originalImagePath ?? provider.imagePath!;
|
return widget.originalImagePath ?? provider.imagePath!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Panneau de réglages de la calibration (taille + espacement).
|
||||||
|
///
|
||||||
|
/// Rendu AU-DESSUS de l'image (et non plus en surimpression) pour ne pas
|
||||||
|
/// masquer la cible. Les valeurs affichées viennent du provider ; les
|
||||||
|
/// modifications sont poussées dans l'état de [TargetCalibration] via sa clé.
|
||||||
|
Widget _buildCalibrationSettings(AnalysisProvider provider) {
|
||||||
|
final radius = provider.targetRadius.clamp(
|
||||||
|
TargetCalibrationState.minRadius,
|
||||||
|
TargetCalibrationState.maxRadius,
|
||||||
|
);
|
||||||
|
final spacing =
|
||||||
|
(provider.targetRadius > 0
|
||||||
|
? provider.targetInnerRadius / provider.targetRadius
|
||||||
|
: 0.1)
|
||||||
|
.clamp(
|
||||||
|
TargetCalibrationState.minSpacing,
|
||||||
|
TargetCalibrationState.maxSpacing,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(12, 0, 12, 4),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 36,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Taille',
|
||||||
|
style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
const Icon(Icons.zoom_out, size: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Slider(
|
||||||
|
value: radius,
|
||||||
|
min: TargetCalibrationState.minRadius,
|
||||||
|
max: TargetCalibrationState.maxRadius,
|
||||||
|
activeColor: AppTheme.primaryColor,
|
||||||
|
onChanged: (value) =>
|
||||||
|
_calibrationKey.currentState?.setRadius(value),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(Icons.zoom_in, size: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 32,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Options d\'espacement avancées',
|
||||||
|
style: TextStyle(fontSize: 11, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Switch(
|
||||||
|
value: _showSpacing,
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => _showSpacing = value);
|
||||||
|
_calibrationKey.currentState?.setSpacingMode(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_showSpacing)
|
||||||
|
SizedBox(
|
||||||
|
height: 36,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Espacement',
|
||||||
|
style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
const Icon(Icons.compress, size: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Slider(
|
||||||
|
value: spacing,
|
||||||
|
min: TargetCalibrationState.minSpacing,
|
||||||
|
max: TargetCalibrationState.maxSpacing,
|
||||||
|
activeColor: Colors.orange,
|
||||||
|
onChanged: (value) =>
|
||||||
|
_calibrationKey.currentState?.setSpacingRatio(value),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(Icons.expand, size: 16),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.refresh, size: 20),
|
||||||
|
tooltip: 'Réinitialiser l\'espacement',
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
onPressed: () =>
|
||||||
|
_calibrationKey.currentState?.resetSpacing(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final provider = context.watch<AnalysisProvider>();
|
final provider = context.watch<AnalysisProvider>();
|
||||||
@@ -230,22 +345,18 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
// Plus de bloc vide au-dessus de l'image : l'indicateur n'occupe
|
||||||
padding: const EdgeInsets.all(AppConstants.defaultPadding),
|
// de la place que pendant le chargement.
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (provider.state == AnalysisState.loading)
|
if (provider.state == AnalysisState.loading)
|
||||||
const Center(
|
const Padding(
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||||
child: CircularProgressIndicator(),
|
child: Center(child: CircularProgressIndicator()),
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Réglages de calibration : au-dessus de la photo pour ne rien
|
||||||
|
// masquer de la cible.
|
||||||
|
if (_isCalibrating) _buildCalibrationSettings(provider),
|
||||||
|
|
||||||
AspectRatio(
|
AspectRatio(
|
||||||
aspectRatio: provider.imageAspectRatio,
|
aspectRatio: provider.imageAspectRatio,
|
||||||
child: _isCalibrating
|
child: _isCalibrating
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ library;
|
|||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../../../core/theme/app_theme.dart';
|
|
||||||
import '../../../data/models/target_type.dart';
|
import '../../../data/models/target_type.dart';
|
||||||
|
|
||||||
class TargetCalibration extends StatefulWidget {
|
class TargetCalibration extends StatefulWidget {
|
||||||
@@ -44,6 +43,15 @@ class TargetCalibration extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TargetCalibrationState extends State<TargetCalibration> {
|
class TargetCalibrationState extends State<TargetCalibration> {
|
||||||
|
/// Bornes du rayon global (mesurées pour laisser de la liberté sans
|
||||||
|
/// débordement incontrôlé).
|
||||||
|
static const double minRadius = 0.3;
|
||||||
|
static const double maxRadius = 0.95;
|
||||||
|
|
||||||
|
/// Bornes de l'espacement : max bridé à 0.70 pour confiner le dernier cercle.
|
||||||
|
static const double minSpacing = 0.01;
|
||||||
|
static const double maxSpacing = 0.70;
|
||||||
|
|
||||||
late double _centerX;
|
late double _centerX;
|
||||||
late double _centerY;
|
late double _centerY;
|
||||||
late double _radius;
|
late double _radius;
|
||||||
@@ -154,9 +162,9 @@ class TargetCalibrationState extends State<TargetCalibration> {
|
|||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final size = constraints.biggest;
|
final size = constraints.biggest;
|
||||||
|
|
||||||
return Stack(
|
// Les réglages (taille / espacement) sont rendus par l'écran hôte
|
||||||
children: [
|
// AU-DESSUS de l'image : rien ne vient masquer la cible ici.
|
||||||
GestureDetector(
|
return GestureDetector(
|
||||||
onScaleStart: (details) {
|
onScaleStart: (details) {
|
||||||
_baseRadiusBeforeScale = _radius;
|
_baseRadiusBeforeScale = _radius;
|
||||||
final tapX = details.localFocalPoint.dx / size.width;
|
final tapX = details.localFocalPoint.dx / size.width;
|
||||||
@@ -186,125 +194,68 @@ class TargetCalibrationState extends State<TargetCalibration> {
|
|||||||
isDraggingInnerRadius: false,
|
isDraggingInnerRadius: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Positioned(
|
// ---------------------------------------------------------------------------
|
||||||
top: 10,
|
// API publique pilotée par l'écran hôte (panneau de réglages hors de l'image)
|
||||||
left: 40,
|
// ---------------------------------------------------------------------------
|
||||||
right: 40,
|
|
||||||
child: Column(
|
/// Espacement courant (rayon du premier anneau, en fraction du rayon global).
|
||||||
mainAxisSize: MainAxisSize.min,
|
double get spacingRatio => _currentEspacementRatio;
|
||||||
children: [
|
|
||||||
Container(
|
/// Mode d'espacement manuel actif ou non.
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
bool get isSpacingModeEnabled => _showEspacement;
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
|
||||||
decoration: BoxDecoration(
|
/// Applique une nouvelle taille globale (rayon normalisé).
|
||||||
color: Colors.black54,
|
void setRadius(double value) {
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Options d\'espacement avancées',
|
|
||||||
style: TextStyle(color: Colors.white70, fontSize: 11, fontWeight: FontWeight.w500),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 28,
|
|
||||||
child: Switch(
|
|
||||||
value: _showEspacement,
|
|
||||||
activeThumbColor: const Color(0xFF00FF00),
|
|
||||||
onChanged: (bool value) {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_showEspacement = value;
|
_radius = value.clamp(minRadius, maxRadius);
|
||||||
// Quand on désactive l'espacement manuel, on restaure la configuration d'usine !
|
_innerRadius = _radius * _currentEspacementRatio;
|
||||||
if (!value) {
|
// Si le mode avancé n'est pas coché, on applique la taille pure sans
|
||||||
|
// détruire le ratio d'origine.
|
||||||
|
_initRingRadii(forceRecalculate: _showEspacement);
|
||||||
|
});
|
||||||
|
_notifyChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Agrandit / réduit la cible de [deltaPixels] pixels.
|
||||||
|
///
|
||||||
|
/// La conversion utilise la plus petite dimension de [size], exactement comme
|
||||||
|
/// le painter, pour que le pas corresponde bien à un pixel à l'écran.
|
||||||
|
void adjustRadiusByPixels(double deltaPixels, Size size) {
|
||||||
|
final minDim = size.width < size.height ? size.width : size.height;
|
||||||
|
if (minDim <= 0) return;
|
||||||
|
setRadius(_radius + deltaPixels / minDim);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Active ou non le réglage manuel de l'espacement.
|
||||||
|
///
|
||||||
|
/// À la désactivation, on restaure la configuration d'usine.
|
||||||
|
void setSpacingMode(bool enabled) {
|
||||||
|
setState(() {
|
||||||
|
_showEspacement = enabled;
|
||||||
|
if (!enabled) {
|
||||||
_initRingRadii(forceRecalculate: false);
|
_initRingRadii(forceRecalculate: false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_notifyChange();
|
_notifyChange();
|
||||||
},
|
}
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Slider pour la taille (toujours visible)
|
/// Applique un nouvel espacement entre les anneaux.
|
||||||
Container(
|
void setSpacingRatio(double value) {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black54,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Text('Taille ', style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold)),
|
|
||||||
const Icon(Icons.zoom_out, color: Colors.white, size: 16),
|
|
||||||
Expanded(
|
|
||||||
child: Slider(
|
|
||||||
// SÉCURITÉ : Ouverture mesurée des bornes pour plus de liberté sans débordement incontrôlé
|
|
||||||
value: _radius.clamp(0.3, 0.95),
|
|
||||||
min: 0.3,
|
|
||||||
max: 0.95,
|
|
||||||
activeColor: AppTheme.primaryColor,
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_radius = value;
|
_currentEspacementRatio = value.clamp(minSpacing, maxSpacing);
|
||||||
_innerRadius = _radius * _currentEspacementRatio;
|
|
||||||
// CORRECTION : Si le mode avancé n'est pas coché, on applique la taille pure sans détruire le ratio d'origine
|
|
||||||
_initRingRadii(forceRecalculate: _showEspacement);
|
|
||||||
});
|
|
||||||
_notifyChange();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Icon(Icons.zoom_in, color: Colors.white, size: 16),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Affichage conditionnel du slider d'espacement orange
|
|
||||||
if (_showEspacement) ...[
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.fromLTRB(16, 4, 4, 4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black54,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Text('Espacement ', style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold)),
|
|
||||||
const Icon(Icons.compress, color: Colors.white, size: 16),
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Slider(
|
|
||||||
value: _currentEspacementRatio,
|
|
||||||
min: 0.01,
|
|
||||||
// SÉCURITÉ : Écartement max bridé à 0.70 pour confiner le dernier cercle
|
|
||||||
max: 0.70,
|
|
||||||
activeColor: Colors.orange,
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
_currentEspacementRatio = value;
|
|
||||||
_innerRadius = _radius * _currentEspacementRatio;
|
_innerRadius = _radius * _currentEspacementRatio;
|
||||||
_initRingRadii(forceRecalculate: true);
|
_initRingRadii(forceRecalculate: true);
|
||||||
});
|
});
|
||||||
_notifyChange();
|
_notifyChange();
|
||||||
},
|
}
|
||||||
),
|
|
||||||
),
|
/// Restaure le vrai profil d'usine détecté sur l'image.
|
||||||
const Icon(Icons.expand, color: Colors.white, size: 16),
|
void resetSpacing() {
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// CORRECTION DU BOUTON RESET : Restaure désormais le vrai profil d'usine de l'IA
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.refresh, color: Colors.white70, size: 20),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
if (_originalRingRadii != null) {
|
if (_originalRingRadii != null) {
|
||||||
_initRingRadii(forceRecalculate: false);
|
_initRingRadii(forceRecalculate: false);
|
||||||
@@ -319,22 +270,6 @@ class TargetCalibrationState extends State<TargetCalibration> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
_notifyChange();
|
_notifyChange();
|
||||||
},
|
|
||||||
tooltip: 'Réinitialiser l\'espacement',
|
|
||||||
constraints: const BoxConstraints(),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildDirectionalControls(BuildContext context, Size size) {
|
Widget buildDirectionalControls(BuildContext context, Size size) {
|
||||||
@@ -348,7 +283,8 @@ class TargetCalibrationState extends State<TargetCalibration> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
_buildSignLabel('−'),
|
// Réduit la cible d'un pixel.
|
||||||
|
_buildSizeButton('−', () => adjustRadiusByPixels(-1, size)),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Column(
|
Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -366,17 +302,27 @@ class TargetCalibrationState extends State<TargetCalibration> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
_buildSignLabel('+'),
|
// Agrandit la cible d'un pixel.
|
||||||
|
_buildSizeButton('+', () => adjustRadiusByPixels(1, size)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Symbole purement décoratif affiché de part et d'autre de la croix.
|
/// Bouton − / + de part et d'autre de la croix : ajuste la TAILLE de la cible.
|
||||||
Widget _buildSignLabel(String text) {
|
Widget _buildSizeButton(String sign, VoidCallback onPressed) {
|
||||||
return Text(
|
return Container(
|
||||||
text,
|
decoration: BoxDecoration(color: Colors.black87, borderRadius: BorderRadius.circular(8)),
|
||||||
style: const TextStyle(color: Colors.white54, fontSize: 28, fontWeight: FontWeight.bold),
|
child: IconButton(
|
||||||
|
icon: Text(
|
||||||
|
sign,
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
onPressed: onPressed,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(minWidth: 44, minHeight: 44),
|
||||||
|
tooltip: sign == '+' ? 'Agrandir la cible' : 'Réduire la cible',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,7 +361,7 @@ class TargetCalibrationState extends State<TargetCalibration> {
|
|||||||
void _onScaleUpdate(ScaleUpdateDetails details, Size size) {
|
void _onScaleUpdate(ScaleUpdateDetails details, Size size) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (details.pointerCount == 2) {
|
if (details.pointerCount == 2) {
|
||||||
_radius = (_baseRadiusBeforeScale * details.scale).clamp(0.3, 0.95);
|
_radius = (_baseRadiusBeforeScale * details.scale).clamp(minRadius, maxRadius);
|
||||||
_innerRadius = _radius * _currentEspacementRatio;
|
_innerRadius = _radius * _currentEspacementRatio;
|
||||||
_initRingRadii(forceRecalculate: _showEspacement);
|
_initRingRadii(forceRecalculate: _showEspacement);
|
||||||
} else if (_isDraggingCenter) {
|
} else if (_isDraggingCenter) {
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: StatsCard(
|
child: StatsCard(
|
||||||
icon: Icons.emoji_events,
|
icon: Icons.emoji_events,
|
||||||
title: 'Meilleur',
|
title: 'Meilleur score',
|
||||||
value: '${_stats!['bestScore']}',
|
value: '${_stats!['bestScore']}',
|
||||||
color: AppTheme.successColor,
|
color: AppTheme.successColor,
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user