feat(crop): remplacement de la rotation à deux doigts par une jauge précise centrée à zéro
This commit is contained in:
@@ -36,9 +36,8 @@ 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
|
// CORRECTION : _rotation gère désormais l'angle en degrés pour le Slider (-45.0 à 45.0)
|
||||||
double _rotation = 0.0;
|
double _rotation = 0.0;
|
||||||
double _baseRotation = 0.0;
|
|
||||||
|
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
bool _imageLoaded = false;
|
bool _imageLoaded = false;
|
||||||
@@ -82,11 +81,10 @@ 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: [
|
actions: [
|
||||||
if (_rotation != 0.0 || _scale != 1.0 || _offset != Offset.zero)
|
if (_rotation != 0.0 || _scale != 1.0 || _offset != Offset.zero)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.rotate_left, color: Colors.white70),
|
icon: const Icon(Icons.refresh, color: Colors.white70),
|
||||||
tooltip: 'Réinitialiser l\'orientation',
|
tooltip: 'Réinitialiser l\'orientation',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -118,9 +116,9 @@ class _CropScreenState extends State<CropScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Bouton d'aide / Modifier
|
// TEXTE D'AIDE AJUSTÉ
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
@@ -136,16 +134,72 @@ class _CropScreenState extends State<CropScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'Utilisez deux doigts pour zoomer et tourner',
|
'Glissez à un doigt pour déplacer, pincez pour zoomer',
|
||||||
style: TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 12),
|
style: TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 12),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// NOUVELLE JAUGE DE ROTATION CENTRÉE À ZÉRO
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Rotation de l\'image',
|
||||||
|
style: TextStyle(color: Colors.white70, fontSize: 13, fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${_rotation.round()}°',
|
||||||
|
style: const TextStyle(color: Color(0xFF00FF00), fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.rotate_left, color: Colors.white38, size: 20),
|
||||||
|
Expanded(
|
||||||
|
child: Slider(
|
||||||
|
value: _rotation,
|
||||||
|
min: -45.0, // Pivot max à gauche
|
||||||
|
max: 45.0, // Pivot max à droite
|
||||||
|
divisions: 90, // Un cran précis par degré
|
||||||
|
label: '${_rotation.round()}°',
|
||||||
|
activeColor: const Color(0xFF1A73E8),
|
||||||
|
inactiveColor: Colors.white12,
|
||||||
|
onChanged: (double value) {
|
||||||
|
setState(() {
|
||||||
|
_rotation = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(Icons.rotate_right, color: Colors.white38, size: 20),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
// Petit bouton Reset rapide pour cette jauge
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.restart_alt, color: Colors.white54, size: 20),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_rotation = 0.0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// Boutons du bas
|
// Boutons du bas
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 30),
|
padding: const EdgeInsets.fromLTRB(20, 10, 20, 30),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -184,8 +238,6 @@ class _CropScreenState extends State<CropScreen> {
|
|||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
_viewportSize = Size(constraints.maxWidth, constraints.maxHeight);
|
_viewportSize = Size(constraints.maxWidth, constraints.maxHeight);
|
||||||
|
|
||||||
// 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 && _rotation == 0.0) {
|
if (_scale == 1.0 && _offset == Offset.zero && _rotation == 0.0) {
|
||||||
@@ -200,11 +252,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
|
// TRANSFORMATION GÉOMÉTRIQUE : Conversion des degrés du Slider en Radians pour la matrice
|
||||||
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),
|
..rotateZ(_rotation * (math.pi / 180)),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Image.file(
|
child: Image.file(
|
||||||
File(widget.imagePath),
|
File(widget.imagePath),
|
||||||
@@ -256,8 +308,6 @@ 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) {
|
||||||
@@ -265,11 +315,7 @@ 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;
|
||||||
|
// SÉCURITÉ : On a retiré le calcul de rotation fou à deux doigts d'ici !
|
||||||
// MODIFICATION ROUGE : Suivi de l'angle de rotation à deux doigts en direct
|
|
||||||
if (details.pointerCount == 2) {
|
|
||||||
_rotation = _baseRotation + details.rotation;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +328,6 @@ 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 !
|
|
||||||
Navigator.pushReplacement(
|
Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
|
|||||||
Reference in New Issue
Block a user