From 750732eafe4e1dd1b371ddfa00d7fc47f1d5c84f Mon Sep 17 00:00:00 2001 From: qguillaume Date: Tue, 26 May 2026 17:07:16 +0200 Subject: [PATCH] =?UTF-8?q?Feat:=20Ajout=20de=20la=20rotation=20=C3=A0=20d?= =?UTF-8?q?eux=20doigts=20de=20l'image=20sur=20l'=C3=A9cran=20de=20Crop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/features/crop/crop_screen.dart | 41 ++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/lib/features/crop/crop_screen.dart b/lib/features/crop/crop_screen.dart index b9453469..9e130fc2 100644 --- a/lib/features/crop/crop_screen.dart +++ b/lib/features/crop/crop_screen.dart @@ -36,6 +36,10 @@ class _CropScreenState extends State { Offset _startOffset = Offset.zero; Offset _startFocalPoint = Offset.zero; + // AJOUT ROUGE : États pour la gestion de la rotation + double _rotation = 0.0; + double _baseRotation = 0.0; + bool _isLoading = false; bool _imageLoaded = false; Size? _imageSize; @@ -78,6 +82,22 @@ class _CropScreenState extends State { icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => Navigator.pop(context), ), + // AJOUT : Bouton de réinitialisation rapide si on s'est trompé dans la rotation + actions: [ + if (_rotation != 0.0 || _scale != 1.0 || _offset != Offset.zero) + IconButton( + icon: const Icon(Icons.rotate_left, color: Colors.white70), + tooltip: 'Réinitialiser l\'orientation', + onPressed: () { + setState(() { + _rotation = 0.0; + _scale = 1.0; + _offset = Offset.zero; + _initializeImagePosition(); + }); + }, + ), + ], ), body: _isLoading ? const Center(child: CircularProgressIndicator(color: Color(0xFF1A73E8))) @@ -106,18 +126,17 @@ class _CropScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - // CORRECTION : Icône passée en vert fluo (0xFF00FF00) const Icon(Icons.center_focus_strong, color: Color(0xFF00FF00), size: 20), const SizedBox(width: 8), Text( - 'Alignez le centre de la cible sur la croix', + 'Alignez et pivotez la cible sur la croix', style: TextStyle(color: Colors.white.withOpacity(0.8), fontSize: 14, fontWeight: FontWeight.w500), ), ], ), const SizedBox(height: 4), Text( - 'Zoomez pour plus de précision', + 'Utilisez deux doigts pour zoomer et tourner', style: TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 12), ), ], @@ -150,7 +169,6 @@ class _CropScreenState extends State { padding: const EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), - // CORRECTION : Texte modifié pour correspondre au nouveau flux child: const Text('Suivant', style: TextStyle(fontWeight: FontWeight.bold)), ), ), @@ -170,7 +188,7 @@ class _CropScreenState extends State { // Le carré de crop occupe presque tout l'espace disponible _cropSize = math.min(constraints.maxWidth, constraints.maxHeight) * 0.95; - if (_scale == 1.0 && _offset == Offset.zero) { + if (_scale == 1.0 && _offset == Offset.zero && _rotation == 0.0) { _initializeImagePosition(); } @@ -182,9 +200,11 @@ class _CropScreenState extends State { Positioned.fill( child: Center( child: Transform( + // SÉCURITÉ GÉOMÉTRIQUE : Ajout de la rotation dans la matrice d'affichage transform: Matrix4.identity() ..setTranslationRaw(_offset.dx, _offset.dy, 0) - ..scale(_scale, _scale), + ..scale(_scale, _scale) + ..rotateZ(_rotation), alignment: Alignment.center, child: Image.file( File(widget.imagePath), @@ -236,6 +256,8 @@ class _CropScreenState extends State { _baseScale = _scale; _startFocalPoint = details.focalPoint; _startOffset = _offset; + // MODIFICATION ROUGE : Enregistrement de l'angle de départ + _baseRotation = _rotation; } void _onScaleUpdate(ScaleUpdateDetails details) { @@ -243,6 +265,11 @@ class _CropScreenState extends State { _scale = (_baseScale * details.scale).clamp(0.8, 8.0); final delta = details.focalPoint - _startFocalPoint; _offset = _startOffset + delta; + + // MODIFICATION ROUGE : Suivi de l'angle de rotation à deux doigts en direct + if (details.pointerCount == 2) { + _rotation = _baseRotation + details.rotation; + } }); } @@ -256,14 +283,12 @@ class _CropScreenState extends State { if (!mounted) return; // CHANGEMENT DE FLUX : On saute l'analyse et on va DIRECTEMENT calibrer la cible ! - // On passe à l'écran d'analyse (qui va être renommé PlottingScreen) toutes les infos requises. Navigator.pushReplacement( context, MaterialPageRoute( builder: (_) => AnalysisScreen( imagePath: widget.imagePath, targetType: widget.targetType, - // On initialise le centre avec le point que l'utilisateur vient de cibler initialCenterX: targetCenterX, initialCenterY: targetCenterY, cropScale: _scale,