fix(analysis): normaliser les coordonnées de la cible pour annuler l'effet du zoom initial
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user