From 5d2e612b9cb47d1951f9921132259d7803720505 Mon Sep 17 00:00:00 2001 From: qguillaume Date: Fri, 12 Jun 2026 11:17:16 +0200 Subject: [PATCH] fix: reeduction temps prise photo et enleve confusion photo en cours de traitement --- lib/features/capture/capture_screen.dart | 140 +++++++++++++++++++---- 1 file changed, 118 insertions(+), 22 deletions(-) diff --git a/lib/features/capture/capture_screen.dart b/lib/features/capture/capture_screen.dart index 5516c674..39d42ea7 100644 --- a/lib/features/capture/capture_screen.dart +++ b/lib/features/capture/capture_screen.dart @@ -47,6 +47,11 @@ class _CaptureScreenState extends State String? _selectedImagePath; bool _isLoading = false; bool _isCapturing = false; // garde anti-double-capture pendant le traitement + // Chemin de la photo tout juste capturée, affichée FIGÉE pendant le + // traitement (downscale). Tant qu'il est null, la capture physique est + // encore en cours → l'utilisateur doit rester immobile. Une fois rempli, + // la photo est prise et l'utilisateur peut bouger. + String? _capturedPreviewPath; // Caméra live CameraController? _cameraController; @@ -190,9 +195,15 @@ class _CaptureScreenState extends State _cameras = await availableCameras(); if (_cameras != null && _cameras!.isNotEmpty) { + // PERF : on capture en `veryHigh` (2160p) et non en `max`. Le pipeline + // redimensionne ensuite tout à 1080 px max → la résolution `max` + // (12–48 MP) ne servait à rien et rallongeait fortement le décodage + // post-capture (« délai de quelques secondes »). `veryHigh` reste très + // au-dessus du 1080 px final : aucune perte de qualité, mais décodage + // bien plus rapide. _cameraController = CameraController( _cameras![0], - ResolutionPreset.max, + ResolutionPreset.veryHigh, enableAudio: false, imageFormatGroup: ImageFormatGroup.jpeg, ); @@ -614,30 +625,19 @@ class _CaptureScreenState extends State // 6. Overlay bloquant pendant la capture/traitement : empêche de // bouger l'UI ou de relancer une capture tant que la photo n'est pas - // chargée (absorbe tous les gestes + voile + indicateur). + // chargée (absorbe tous les gestes). Deux phases distinctes pour + // lever la confusion « puis-je bouger ? » : + // • _capturedPreviewPath == null → capture physique en cours : + // voile sur l'aperçu vivant + « Ne bougez pas ». + // • _capturedPreviewPath != null → photo prise, traitement en + // cours : on affiche l'image FIGÉE (le flux vivant disparaît) + // + « Photo prise ✓ — vous pouvez bouger ». if (_isCapturing) Positioned.fill( child: AbsorbPointer( - child: Container( - color: Colors.black.withValues(alpha: 0.45), - child: const Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - CircularProgressIndicator(color: Colors.white), - SizedBox(height: 16), - Text( - 'Photo en cours…', - style: TextStyle( - color: Colors.white, - fontSize: 14, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - ), + child: _capturedPreviewPath == null + ? _buildCapturingOverlay() + : _buildProcessingOverlay(_capturedPreviewPath!), ), ), ], @@ -645,6 +645,92 @@ class _CaptureScreenState extends State ); } + // ───────────────────────────────────────────────────────────────────────── + // Overlay PHASE 1 : capture physique en cours → l'utilisateur doit rester + // immobile. Voile semi-transparent par-dessus l'aperçu vivant. + // ───────────────────────────────────────────────────────────────────────── + Widget _buildCapturingOverlay() { + return Container( + color: Colors.black.withValues(alpha: 0.45), + child: const Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CircularProgressIndicator(color: Colors.white), + SizedBox(height: 16), + Text( + 'NE BOUGEZ PAS — CAPTURE…', + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w700, + letterSpacing: 1.2, + ), + ), + ], + ), + ), + ); + } + + // ───────────────────────────────────────────────────────────────────────── + // Overlay PHASE 2 : photo capturée, traitement en cours. On affiche l'image + // FIGÉE (l'aperçu vivant disparaît, plus d'ambiguïté) et on indique + // clairement que l'utilisateur peut maintenant bouger. + // ───────────────────────────────────────────────────────────────────────── + Widget _buildProcessingOverlay(String imagePath) { + return Stack( + fit: StackFit.expand, + children: [ + // Image figée de la photo prise → recouvre totalement le flux caméra + Image.file(File(imagePath), fit: BoxFit.cover), + Container( + color: Colors.black.withValues(alpha: 0.55), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.check_circle, + color: Color(0xFF00FF00), + size: 52, + ), + const SizedBox(height: 14), + const Text( + 'PHOTO PRISE', + style: TextStyle( + color: Color(0xFF00FF00), + fontSize: 16, + fontWeight: FontWeight.w700, + letterSpacing: 1.2, + ), + ), + const SizedBox(height: 20), + const SizedBox( + width: 22, + height: 22, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2.5, + ), + ), + const SizedBox(height: 14), + Text( + 'Traitement… vous pouvez bouger', + style: TextStyle( + color: Colors.white.withValues(alpha: 0.85), + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ], + ); + } + // ───────────────────────────────────────────────────────────────────────── // NOUVEAU : Widget affichant les angles pitch/roll en temps réel // ───────────────────────────────────────────────────────────────────────── @@ -761,6 +847,7 @@ class _CaptureScreenState extends State setState(() { _isCapturing = true; _isLoading = true; + _capturedPreviewPath = null; // phase « capture en cours » }); try { // On stoppe le flux d'analyse (obligatoire avant takePicture) puis on @@ -772,6 +859,14 @@ class _CaptureScreenState extends State final XFile photo = await _cameraController!.takePicture(); + // La photo est PHYSIQUEMENT capturée : on gèle l'aperçu en affichant + // l'image prise (le flux caméra « vivant » disparaît) et on bascule le + // message sur « vous pouvez bouger ». Tout ce qui suit n'est que du + // traitement et n'exige plus l'immobilité de l'utilisateur. + if (mounted) { + setState(() => _capturedPreviewPath = photo.path); + } + // Pas de redressement perspective automatique ici : il déformait l'image // (écrasement en carré + rotation de l'axe de l'ellipse) et gelait le // thread UI. Le recentrage ET la rotation se font manuellement à l'écran @@ -801,6 +896,7 @@ class _CaptureScreenState extends State setState(() { _isLoading = false; _isCapturing = false; + _capturedPreviewPath = null; }); } }