fix(analysis): normaliser les coordonnées de la cible pour annuler l'effet du zoom initial

This commit is contained in:
qguillaume
2026-06-04 23:32:38 +02:00
parent a5d0251545
commit 9e0427f750
2 changed files with 188 additions and 70 deletions

View File

@@ -162,8 +162,10 @@ class AnalysisProvider extends ChangeNotifier {
return; return;
} }
// CORRECTION PARALLÈLE : Changement de la méthode pour l'Isolate asynchrone final result = await _detectionService.detectTargetAsync(
final result = await _detectionService.detectTargetAsync(imagePath, targetType); imagePath,
targetType,
);
if (!result.success) { if (!result.success) {
_state = AnalysisState.error; _state = AnalysisState.error;
@@ -512,11 +514,14 @@ class AnalysisProvider extends ChangeNotifier {
double radius, { double radius, {
int? ringCount, int? ringCount,
List<double>? ringRadii, List<double>? ringRadii,
double zoomScale = 1.0,
Offset offset = Offset.zero,
}) { }) {
_targetCenterX = centerX; _targetCenterX = (centerX - offset.dx) / zoomScale;
_targetCenterY = centerY; _targetCenterY = (centerY - offset.dy) / zoomScale;
_targetInnerRadius = innerRadius; _targetRadius = radius / zoomScale;
_targetRadius = radius; _targetInnerRadius = innerRadius / zoomScale;
if (ringCount != null) { if (ringCount != null) {
_ringCount = ringCount; _ringCount = ringCount;
} }
@@ -720,14 +725,14 @@ class AnalysisProvider extends ChangeNotifier {
return success; return success;
} }
/// Save the session (Refactorisé pour appliquer le paramètre de date personnalisé) /// Save the session
Future<TargetAnalysis> saveSession({ Future<TargetAnalysis> saveSession({
String? notes, String? notes,
String? sessionId, String? sessionId,
String? weaponName, String? weaponName,
String? weaponId, String? weaponId,
int? distance, int? distance,
DateTime? date, // <-- REÇOIT PROPREMENT LA DATE DE DEBUT DE SESSION DateTime? date,
}) async { }) async {
if (_imagePath == null || _targetType == null) { if (_imagePath == null || _targetType == null) {
throw Exception('Cannot save: missing image or target type'); throw Exception('Cannot save: missing image or target type');
@@ -751,7 +756,10 @@ class AnalysisProvider extends ChangeNotifier {
if (sessionId != null && sessionId != 'standalone') { if (sessionId != null && sessionId != 'standalone') {
final existingSession = await _sessionRepository.getSession(sessionId); final existingSession = await _sessionRepository.getSession(sessionId);
if (existingSession != null) { if (existingSession != null) {
await _sessionRepository.addAnalysisToSession(sessionId, targetAnalysis); await _sessionRepository.addAnalysisToSession(
sessionId,
targetAnalysis,
);
} else { } else {
// CORRECTION : Utilise la date injectée plutôt que DateTime.now() // CORRECTION : Utilise la date injectée plutôt que DateTime.now()
await _sessionRepository.createSession( await _sessionRepository.createSession(
@@ -788,7 +796,7 @@ class AnalysisProvider extends ChangeNotifier {
_errorMessage = null; _errorMessage = null;
_imagePath = null; _imagePath = null;
_targetType = null; _targetType = null;
_cropRotation = 0.0; // Reset de la rotation _cropRotation = 0.0;
_targetCenterX = 0.5; _targetCenterX = 0.5;
_targetCenterY = 0.5; _targetCenterY = 0.5;
_targetRadius = 0.4; _targetRadius = 0.4;

View File

@@ -49,7 +49,8 @@ class AnalysisScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Reconstitution de l'Offset pour le traitement métier en arrière-plan // Reconstitution de l'Offset pour le traitement métier en arrière-plan
final manualCenterOffset = (initialCenterX != null && initialCenterY != null) final manualCenterOffset =
(initialCenterX != null && initialCenterY != null)
? Offset(initialCenterX!, initialCenterY!) ? Offset(initialCenterX!, initialCenterY!)
: null; : null;
@@ -65,7 +66,12 @@ class AnalysisScreen extends StatelessWidget {
if (cropRotation != null) { if (cropRotation != null) {
p.setCropRotation(cropRotation!); p.setCropRotation(cropRotation!);
} }
p.analyzeImage(imagePath, targetType, autoAnalyze: false, manualCenter: manualCenterOffset); p.analyzeImage(
imagePath,
targetType,
autoAnalyze: false,
manualCenter: manualCenterOffset,
);
return p; return p;
}, },
child: _AnalysisScreenContent( child: _AnalysisScreenContent(
@@ -93,7 +99,8 @@ class _AnalysisScreenContent extends StatefulWidget {
} }
class _AnalysisScreenContentState extends State<_AnalysisScreenContent> { class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
final GlobalKey<TargetCalibrationState> _calibrationKey = GlobalKey<TargetCalibrationState>(); final GlobalKey<TargetCalibrationState> _calibrationKey =
GlobalKey<TargetCalibrationState>();
// Forcé à TRUE pour démarrer sur l'ajustement des cercles // Forcé à TRUE pour démarrer sur l'ajustement des cercles
bool _isCalibrating = true; bool _isCalibrating = true;
@@ -101,7 +108,8 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
bool _isAtBottom = false; bool _isAtBottom = false;
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
final TransformationController _transformationController = TransformationController(); final TransformationController _transformationController =
TransformationController();
final GlobalKey _imageKey = GlobalKey(); final GlobalKey _imageKey = GlobalKey();
double _currentZoomScale = 1.0; double _currentZoomScale = 1.0;
String? _movingShotId; String? _movingShotId;
@@ -115,7 +123,9 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
void _onScroll() { void _onScroll() {
if (!_scrollController.hasClients) return; if (!_scrollController.hasClients) return;
final isBottom = _scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 20; final isBottom =
_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 20;
if (isBottom != _isAtBottom) { if (isBottom != _isAtBottom) {
setState(() { setState(() {
_isAtBottom = isBottom; _isAtBottom = isBottom;
@@ -145,10 +155,14 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final provider = context.watch<AnalysisProvider>(); final provider = context.watch<AnalysisProvider>();
final sessionProvider = context.watch<SessionProvider>(); final sessionProvider = context.watch<SessionProvider>();
final targetNumber = sessionProvider.isSessionActive ? sessionProvider.targetCount + 1 : null; final targetNumber = sessionProvider.isSessionActive
? sessionProvider.targetCount + 1
: null;
final titlePrefix = _isCalibrating ? 'Calibration' : 'Plotting'; final titlePrefix = _isCalibrating ? 'Calibration' : 'Plotting';
final title = targetNumber != null ? '$titlePrefix - Cible $targetNumber' : (_isCalibrating ? 'Calibration' : 'Plotting du Tir'); final title = targetNumber != null
? '$titlePrefix - Cible $targetNumber'
: (_isCalibrating ? 'Calibration' : 'Plotting du Tir');
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@@ -178,14 +192,20 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
if (!_isCalibrating && !_isSelectingReferences) if (!_isCalibrating && !_isSelectingReferences)
IconButton( IconButton(
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
onPressed: () => provider.analyzeImage(context.read<AnalysisProvider>().imagePath!, context.read<AnalysisProvider>().targetType!), onPressed: () => provider.analyzeImage(
context.read<AnalysisProvider>().imagePath!,
context.read<AnalysisProvider>().targetType!,
),
), ),
if (_isCalibrating) if (_isCalibrating)
TextButton( TextButton(
onPressed: () => setState(() => _isCalibrating = false), onPressed: () => setState(() => _isCalibrating = false),
child: const Text( child: const Text(
'TERMINER', 'TERMINER',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
), ),
), ),
], ],
@@ -212,7 +232,6 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
), ),
), ),
// SECTION CORRIGÉE : Utilise maintenant la réactivité du provider globale
AspectRatio( AspectRatio(
aspectRatio: provider.imageAspectRatio, aspectRatio: provider.imageAspectRatio,
child: _isCalibrating child: _isCalibrating
@@ -224,16 +243,23 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
builder: (context) { builder: (context) {
return Transform( return Transform(
transform: Matrix4.identity() transform: Matrix4.identity()
..setTranslationRaw(widget.cropOffset?.dx ?? 0.0, widget.cropOffset?.dy ?? 0.0, 0.0) ..setTranslationRaw(
widget.cropOffset?.dx ?? 0.0,
widget.cropOffset?.dy ?? 0.0,
0.0,
)
..scale(1.0, 1.0) ..scale(1.0, 1.0)
..rotateZ((provider.cropRotation) * (math.pi / 180)), ..rotateZ(
(provider.cropRotation) *
(math.pi / 180),
),
alignment: Alignment.center, alignment: Alignment.center,
child: Image.file( child: Image.file(
File(provider.imagePath!), File(provider.imagePath!),
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
); );
} },
), ),
), ),
TargetCalibration( TargetCalibration(
@@ -245,7 +271,15 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
initialRingCount: provider.ringCount, initialRingCount: provider.ringCount,
initialRingRadii: provider.ringRadii, initialRingRadii: provider.ringRadii,
targetType: provider.targetType!, targetType: provider.targetType!,
onCalibrationChanged: (centerX, centerY, innerRadius, radius, ringCount, {ringRadii}) { onCalibrationChanged:
(
centerX,
centerY,
innerRadius,
radius,
ringCount, {
ringRadii,
}) {
provider.adjustTargetPosition( provider.adjustTargetPosition(
centerX, centerX,
centerY, centerY,
@@ -253,6 +287,7 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
radius, radius,
ringCount: ringCount, ringCount: ringCount,
ringRadii: ringRadii, ringRadii: ringRadii,
zoomScale: widget.cropScale ?? 1.0,
); );
}, },
), ),
@@ -269,10 +304,18 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
Card( Card(
color: AppTheme.primaryColor.withValues(alpha: 0.1), color: AppTheme.primaryColor.withValues(alpha: 0.1),
child: ListTile( child: ListTile(
leading: const Icon(Icons.tune, color: AppTheme.primaryColor), leading: const Icon(
Icons.tune,
color: AppTheme.primaryColor,
),
title: const Text('Ajuster la calibration'), title: const Text('Ajuster la calibration'),
subtitle: const Text('Modifier le centre ou le rayon global'), subtitle: const Text(
trailing: const Icon(Icons.arrow_forward_ios, size: 16), 'Modifier le centre ou le rayon global',
),
trailing: const Icon(
Icons.arrow_forward_ios,
size: 16,
),
onTap: () => setState(() => _isCalibrating = true), onTap: () => setState(() => _isCalibrating = true),
), ),
), ),
@@ -284,7 +327,8 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
targetType: provider.targetType!, targetType: provider.targetType!,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
if (provider.groupingResult != null && provider.shotCount > 1) if (provider.groupingResult != null &&
provider.shotCount > 1)
GroupingStats( GroupingStats(
groupingResult: provider.groupingResult!, groupingResult: provider.groupingResult!,
targetCenterX: provider.targetCenterX, targetCenterX: provider.targetCenterX,
@@ -303,16 +347,24 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
children: [ children: [
const Text( const Text(
'Ajustement precis (pixel par pixel)', 'Ajustement precis (pixel par pixel)',
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: Colors.white70), style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Colors.white70,
),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Center( Center(
child: Builder( child: Builder(
builder: (context) { builder: (context) {
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
return _calibrationKey.currentState?.buildDirectionalControls(context, size) return _calibrationKey.currentState
?? const SizedBox.shrink(); ?.buildDirectionalControls(
} context,
size,
) ??
const SizedBox.shrink();
},
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -324,19 +376,33 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
children: [ children: [
const Text( const Text(
'Instructions de calibration', 'Instructions de calibration',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildInstructionItem(Icons.open_with, 'Glissez le centre pour positionner le centre de la cible'), _buildInstructionItem(
_buildInstructionItem(Icons.zoom_out_map, 'Pincez l\'ecran a 2 doigts ou utilisez la jauge pour la taille'), Icons.open_with,
_buildInstructionItem(Icons.visibility, 'Appuyez sur TERMINER en haut a droite pour valider'), 'Glissez le centre pour positionner le centre de la cible',
),
_buildInstructionItem(
Icons.zoom_out_map,
'Pincez l\'ecran a 2 doigts ou utilisez la jauge pour la taille',
),
_buildInstructionItem(
Icons.visibility,
'Appuyez sur TERMINER en haut a droite pour valider',
),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
children: [ children: [
const Text('Centre: '), const Text('Centre: '),
Text( Text(
'(${(provider.targetCenterX * 100).toStringAsFixed(1)}%, ${(provider.targetCenterY * 100).toStringAsFixed(1)}%)', '(${(provider.targetCenterX * 100).toStringAsFixed(1)}%, ${(provider.targetCenterY * 100).toStringAsFixed(1)}%)',
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(
fontWeight: FontWeight.bold,
),
), ),
], ],
), ),
@@ -345,7 +411,9 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
const Text('Rayon: '), const Text('Rayon: '),
Text( Text(
'${(provider.targetRadius * 100).toStringAsFixed(1)}%', '${(provider.targetRadius * 100).toStringAsFixed(1)}%',
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(
fontWeight: FontWeight.bold,
),
), ),
], ],
), ),
@@ -364,13 +432,18 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
left: 0, left: 0,
right: 0, right: 0,
child: Align( child: Align(
alignment: _isAtBottom ? Alignment.bottomCenter : Alignment.bottomRight, alignment: _isAtBottom
? Alignment.bottomCenter
: Alignment.bottomRight,
child: Padding( child: Padding(
padding: _isAtBottom ? EdgeInsets.zero : const EdgeInsets.all(16.0), padding: _isAtBottom
? EdgeInsets.zero
: const EdgeInsets.all(16.0),
child: _isCalibrating child: _isCalibrating
? const SizedBox.shrink() ? const SizedBox.shrink()
: FloatingActionButton.extended( : FloatingActionButton.extended(
onPressed: () => _showSaveSessionDialog(context, provider), onPressed: () =>
_showSaveSessionDialog(context, provider),
backgroundColor: AppTheme.primaryColor, backgroundColor: AppTheme.primaryColor,
icon: const Icon(Icons.save), icon: const Icon(Icons.save),
label: const Text('TERMINER LA SESSION'), label: const Text('TERMINER LA SESSION'),
@@ -396,7 +469,10 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
); );
} }
Widget _buildZoomableImageWithOverlay(BuildContext context, AnalysisProvider provider) { Widget _buildZoomableImageWithOverlay(
BuildContext context,
AnalysisProvider provider,
) {
return InteractiveViewer( return InteractiveViewer(
transformationController: _transformationController, transformationController: _transformationController,
minScale: 1.0, minScale: 1.0,
@@ -404,15 +480,19 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
boundaryMargin: const EdgeInsets.all(double.infinity), boundaryMargin: const EdgeInsets.all(double.infinity),
panEnabled: _movingShotId == null, panEnabled: _movingShotId == null,
child: Transform( child: Transform(
// CORRECTION GÉOMÉTRIQUE : On déplace le Transform ICI pour englober le GestureDetector et le Stack d'un coup !
transform: Matrix4.identity() transform: Matrix4.identity()
..setTranslationRaw(widget.cropOffset?.dx ?? 0.0, widget.cropOffset?.dy ?? 0.0, 0.0) ..setTranslationRaw(
widget.cropOffset?.dx ?? 0.0,
widget.cropOffset?.dy ?? 0.0,
0.0,
)
..scale(1.0, 1.0) ..scale(1.0, 1.0)
..rotateZ((provider.cropRotation) * (math.pi / 180)), ..rotateZ((provider.cropRotation) * (math.pi / 180)),
alignment: Alignment.center, alignment: Alignment.center,
child: GestureDetector( child: GestureDetector(
onDoubleTapDown: (TapDownDetails details) { onDoubleTapDown: (TapDownDetails details) {
final RenderBox? box = _imageKey.currentContext?.findRenderObject() as RenderBox?; final RenderBox? box =
_imageKey.currentContext?.findRenderObject() as RenderBox?;
if (box == null) return; if (box == null) return;
final localOffset = box.globalToLocal(details.globalPosition); final localOffset = box.globalToLocal(details.globalPosition);
@@ -441,7 +521,8 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
} }
}, },
onLongPressStart: (LongPressStartDetails details) { onLongPressStart: (LongPressStartDetails details) {
final RenderBox? box = _imageKey.currentContext?.findRenderObject() as RenderBox?; final RenderBox? box =
_imageKey.currentContext?.findRenderObject() as RenderBox?;
if (box == null) return; if (box == null) return;
final localOffset = box.globalToLocal(details.globalPosition); final localOffset = box.globalToLocal(details.globalPosition);
@@ -474,10 +555,12 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) { onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) {
if (_movingShotId == null) return; if (_movingShotId == null) return;
final RenderBox? box = _imageKey.currentContext?.findRenderObject() as RenderBox?; final RenderBox? box =
_imageKey.currentContext?.findRenderObject() as RenderBox?;
if (box == null) return; if (box == null) return;
final adjustedGlobalPosition = details.globalPosition + const Offset(-25, -35); final adjustedGlobalPosition =
details.globalPosition + const Offset(-25, -35);
final localOffset = box.globalToLocal(adjustedGlobalPosition); final localOffset = box.globalToLocal(adjustedGlobalPosition);
final relX = (localOffset.dx / box.size.width).clamp(0.0, 1.0); final relX = (localOffset.dx / box.size.width).clamp(0.0, 1.0);
@@ -507,7 +590,8 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
shots: provider.shots, shots: provider.shots,
showRings: true, showRings: true,
zoomScale: _currentZoomScale, zoomScale: _currentZoomScale,
onShotTapped: (shot) => _showShotDetails(context, provider, shot), onShotTapped: (shot) =>
_showShotDetails(context, provider, shot),
onAddShot: (relX, relY) => provider.addShot(relX, relY), onAddShot: (relX, relY) => provider.addShot(relX, relY),
), ),
], ],
@@ -516,11 +600,16 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
), ),
); );
} }
Widget _buildActionButtons(BuildContext context, AnalysisProvider provider) { Widget _buildActionButtons(BuildContext context, AnalysisProvider provider) {
return const Column(children: [Row(children: [])]); return const Column(children: [Row(children: [])]);
} }
void _showShotDetails(BuildContext context, AnalysisProvider provider, Shot shot) { void _showShotDetails(
BuildContext context,
AnalysisProvider provider,
Shot shot,
) {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (context) => Container( builder: (context) => Container(
@@ -534,7 +623,9 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
), ),
Text( Text(
'ID: ${shot.id}', 'ID: ${shot.id}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.grey, fontSize: 10), style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: Colors.grey, fontSize: 10),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
ListTile( ListTile(
@@ -543,10 +634,18 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
trailing: DropdownButton<int>( trailing: DropdownButton<int>(
value: shot.score.clamp(0, 10), value: shot.score.clamp(0, 10),
items: List.generate(11, (index) => index) items: List.generate(11, (index) => index)
.map((s) => DropdownMenuItem( .map(
(s) => DropdownMenuItem(
value: s, value: s,
child: Text('$s', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)), child: Text(
)) '$s',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
)
.toList(), .toList(),
onChanged: (newScore) { onChanged: (newScore) {
if (newScore != null) { if (newScore != null) {
@@ -566,7 +665,10 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
Navigator.pop(context); Navigator.pop(context);
}, },
icon: const Icon(Icons.delete, color: Colors.red), icon: const Icon(Icons.delete, color: Colors.red),
label: const Text('SUPPRIMER', style: TextStyle(color: Colors.red)), label: const Text(
'SUPPRIMER',
style: TextStyle(color: Colors.red),
),
), ),
), ),
], ],
@@ -631,14 +733,19 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
Navigator.pop(context); Navigator.pop(context);
Navigator.pushAndRemoveUntil( Navigator.pushAndRemoveUntil(
context, context,
MaterialPageRoute(builder: (context) => const CaptureScreen()), MaterialPageRoute(
builder: (context) => const CaptureScreen(),
),
(route) => route.isFirst, (route) => route.isFirst,
); );
} }
} catch (e) { } catch (e) {
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erreur: $e'), backgroundColor: AppTheme.errorColor), SnackBar(
content: Text('Erreur: $e'),
backgroundColor: AppTheme.errorColor,
),
); );
} }
} }
@@ -665,7 +772,10 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
} catch (e) { } catch (e) {
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erreur: $e'), backgroundColor: AppTheme.errorColor), SnackBar(
content: Text('Erreur: $e'),
backgroundColor: AppTheme.errorColor,
),
); );
} }
} }