Feat: Ajout de la rotation à deux doigts de l'image sur l'écran de Crop

This commit is contained in:
qguillaume
2026-05-26 17:07:16 +02:00
parent f40bfc0ba7
commit 750732eafe

View File

@@ -36,6 +36,10 @@ class _CropScreenState extends State<CropScreen> {
Offset _startOffset = Offset.zero; Offset _startOffset = Offset.zero;
Offset _startFocalPoint = 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 _isLoading = false;
bool _imageLoaded = false; bool _imageLoaded = false;
Size? _imageSize; Size? _imageSize;
@@ -78,6 +82,22 @@ class _CropScreenState extends State<CropScreen> {
icon: const Icon(Icons.arrow_back, color: Colors.white), icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context), 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 body: _isLoading
? const Center(child: CircularProgressIndicator(color: Color(0xFF1A73E8))) ? const Center(child: CircularProgressIndicator(color: Color(0xFF1A73E8)))
@@ -106,18 +126,17 @@ class _CropScreenState extends State<CropScreen> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
// CORRECTION : Icône passée en vert fluo (0xFF00FF00)
const Icon(Icons.center_focus_strong, color: Color(0xFF00FF00), size: 20), const Icon(Icons.center_focus_strong, color: Color(0xFF00FF00), size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( 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), style: TextStyle(color: Colors.white.withOpacity(0.8), fontSize: 14, fontWeight: FontWeight.w500),
), ),
], ],
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'Zoomez pour plus de précision', 'Utilisez deux doigts pour zoomer et tourner',
style: TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 12), style: TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 12),
), ),
], ],
@@ -150,7 +169,6 @@ class _CropScreenState extends State<CropScreen> {
padding: const EdgeInsets.symmetric(vertical: 15), padding: const EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
), ),
// CORRECTION : Texte modifié pour correspondre au nouveau flux
child: const Text('Suivant', style: TextStyle(fontWeight: FontWeight.bold)), child: const Text('Suivant', style: TextStyle(fontWeight: FontWeight.bold)),
), ),
), ),
@@ -170,7 +188,7 @@ class _CropScreenState extends State<CropScreen> {
// Le carré de crop occupe presque tout l'espace disponible // Le carré de crop occupe presque tout l'espace disponible
_cropSize = math.min(constraints.maxWidth, constraints.maxHeight) * 0.95; _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(); _initializeImagePosition();
} }
@@ -182,9 +200,11 @@ class _CropScreenState extends State<CropScreen> {
Positioned.fill( Positioned.fill(
child: Center( child: Center(
child: Transform( child: Transform(
// SÉCURITÉ GÉOMÉTRIQUE : Ajout de la rotation dans la matrice d'affichage
transform: Matrix4.identity() transform: Matrix4.identity()
..setTranslationRaw(_offset.dx, _offset.dy, 0) ..setTranslationRaw(_offset.dx, _offset.dy, 0)
..scale(_scale, _scale), ..scale(_scale, _scale)
..rotateZ(_rotation),
alignment: Alignment.center, alignment: Alignment.center,
child: Image.file( child: Image.file(
File(widget.imagePath), File(widget.imagePath),
@@ -236,6 +256,8 @@ class _CropScreenState extends State<CropScreen> {
_baseScale = _scale; _baseScale = _scale;
_startFocalPoint = details.focalPoint; _startFocalPoint = details.focalPoint;
_startOffset = _offset; _startOffset = _offset;
// MODIFICATION ROUGE : Enregistrement de l'angle de départ
_baseRotation = _rotation;
} }
void _onScaleUpdate(ScaleUpdateDetails details) { void _onScaleUpdate(ScaleUpdateDetails details) {
@@ -243,6 +265,11 @@ class _CropScreenState extends State<CropScreen> {
_scale = (_baseScale * details.scale).clamp(0.8, 8.0); _scale = (_baseScale * details.scale).clamp(0.8, 8.0);
final delta = details.focalPoint - _startFocalPoint; final delta = details.focalPoint - _startFocalPoint;
_offset = _startOffset + delta; _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<CropScreen> {
if (!mounted) return; if (!mounted) return;
// CHANGEMENT DE FLUX : On saute l'analyse et on va DIRECTEMENT calibrer la cible ! // 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( Navigator.pushReplacement(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => AnalysisScreen( builder: (_) => AnalysisScreen(
imagePath: widget.imagePath, imagePath: widget.imagePath,
targetType: widget.targetType, targetType: widget.targetType,
// On initialise le centre avec le point que l'utilisateur vient de cibler
initialCenterX: targetCenterX, initialCenterX: targetCenterX,
initialCenterY: targetCenterY, initialCenterY: targetCenterY,
cropScale: _scale, cropScale: _scale,