feat: implement base architecture and core repositories for weapon tracking and target analysis functionality

This commit is contained in:
streaper2
2026-05-08 20:29:18 +02:00
parent 5dd58da51c
commit 774dbfcf40
37 changed files with 3687 additions and 2713 deletions

View File

@@ -7,9 +7,9 @@ library;
import 'dart:io';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
import '../../data/models/session.dart';
import '../../data/models/target_analysis.dart';
import '../../data/models/shot.dart';
import '../../data/models/target_type.dart';
import '../../data/repositories/session_repository.dart';
@@ -120,6 +120,7 @@ class AnalysisProvider extends ChangeNotifier {
String imagePath,
TargetType targetType, {
bool autoAnalyze = true,
Offset? manualCenter,
}) async {
_state = AnalysisState.loading;
_imagePath = imagePath;
@@ -138,8 +139,8 @@ class AnalysisProvider extends ChangeNotifier {
if (!autoAnalyze) {
// Just setup default values without running detection
_targetCenterX = 0.5;
_targetCenterY = 0.5;
_targetCenterX = manualCenter?.dx ?? 0.5;
_targetCenterY = manualCenter?.dy ?? 0.5;
_targetRadius = 0.4;
_targetInnerRadius = 0.04;
@@ -173,7 +174,7 @@ class AnalysisProvider extends ChangeNotifier {
x: impact.x,
y: impact.y,
score: impact.suggestedScore,
sessionId: '', // Will be set when saving
analysisId: '', // Will be set when saving
);
}).toList();
@@ -195,7 +196,7 @@ class AnalysisProvider extends ChangeNotifier {
/// Add a manual shot
void addShot(double x, double y) {
final score = _calculateShotScore(x, y);
final shot = Shot(id: _uuid.v4(), x: x, y: y, score: score, sessionId: '');
final shot = Shot(id: _uuid.v4(), x: x, y: y, score: score, analysisId: '');
_shots.add(shot);
_recalculateScores();
@@ -275,7 +276,7 @@ class AnalysisProvider extends ChangeNotifier {
x: impact.x,
y: impact.y,
score: score,
sessionId: '',
analysisId: '',
);
_shots.add(shot);
}
@@ -288,14 +289,6 @@ class AnalysisProvider extends ChangeNotifier {
}
/// Auto-detect impacts using OpenCV (Hough Circles + Contours)
///
/// NOTE: OpenCV est actuellement désactivé sur Windows en raison de problèmes
/// de compilation. Cette méthode retourne 0 (aucun impact détecté).
/// Utiliser autoDetectImpacts() à la place.
///
/// Utilise les algorithmes OpenCV pour une détection plus robuste:
/// - Transformation de Hough pour détecter les cercles
/// - Analyse de contours avec filtrage par circularité
Future<int> autoDetectImpactsWithOpenCV({
double cannyThreshold1 = 50,
double cannyThreshold2 = 150,
@@ -352,7 +345,7 @@ class AnalysisProvider extends ChangeNotifier {
x: impact.x,
y: impact.y,
score: score,
sessionId: '',
analysisId: '',
);
_shots.add(shot);
}
@@ -404,7 +397,7 @@ class AnalysisProvider extends ChangeNotifier {
x: impact.x,
y: impact.y,
score: score,
sessionId: '',
analysisId: '',
);
_shots.add(shot);
}
@@ -419,7 +412,7 @@ class AnalysisProvider extends ChangeNotifier {
/// Add a reference impact for calibrated detection
void addReferenceImpact(double x, double y) {
final score = _calculateShotScore(x, y);
final shot = Shot(id: _uuid.v4(), x: x, y: y, score: score, sessionId: '');
final shot = Shot(id: _uuid.v4(), x: x, y: y, score: score, analysisId: '');
_referenceImpacts.add(shot);
notifyListeners();
}
@@ -489,7 +482,7 @@ class AnalysisProvider extends ChangeNotifier {
x: impact.x,
y: impact.y,
score: score,
sessionId: '',
analysisId: '',
);
_shots.add(shot);
}
@@ -563,7 +556,7 @@ class AnalysisProvider extends ChangeNotifier {
}
return false;
} catch (e) {
print('Auto-calibration error: $e');
debugPrint('Auto-calibration error: $e');
return false;
}
}
@@ -608,7 +601,6 @@ class AnalysisProvider extends ChangeNotifier {
}
}
/* version deux a tester*/
/// Calcule ET applique la correction pour un feedback immédiat
Future<void> calculateAndApplyDistortion() async {
// 1. Calcul des paramètres (votre code actuel)
@@ -644,7 +636,6 @@ class AnalysisProvider extends ChangeNotifier {
notifyListeners();
}
}
/* fin section deux a tester*/
int _calculateShotScore(double x, double y) {
if (_targetType == TargetType.concentric) {
@@ -697,10 +688,10 @@ class AnalysisProvider extends ChangeNotifier {
return false;
}
// Identifiant de session temporaire si non sauvegardée
final sessionId = _shots.isNotEmpty && _shots.first.sessionId.isNotEmpty
? _shots.first.sessionId
: 'session_${DateTime.now().millisecondsSinceEpoch}';
// Identifiant d'analyse temporaire
final analysisId = _shots.isNotEmpty && _shots.first.analysisId.isNotEmpty
? _shots.first.analysisId
: 'analysis_${DateTime.now().millisecondsSinceEpoch}';
final service = AiExportService(); // Local instanciation for simplicity
@@ -709,7 +700,7 @@ class AnalysisProvider extends ChangeNotifier {
final success = await service.exportData(
imagePath: _imagePath!,
sessionId: sessionId,
sessionId: 'export',
targetType: _targetType!,
targetCenterX: _targetCenterX,
targetCenterY: _targetCenterY,
@@ -725,16 +716,23 @@ class AnalysisProvider extends ChangeNotifier {
return success;
}
/// Save the session
Future<Session> saveSession({String? notes}) async {
/// Save the session (legacy standalone flow)
Future<TargetAnalysis> saveSession({
String? notes,
String? sessionId,
String? weaponName,
String? weaponId,
int? distance,
}) async {
if (_imagePath == null || _targetType == null) {
throw Exception('Cannot save: missing image or target type');
}
final session = await _sessionRepository.createSession(
targetType: _targetType!,
final targetAnalysis = await _sessionRepository.prepareTargetAnalysis(
imagePath: _imagePath!,
shots: _shots.map((s) => s.copyWith(sessionId: '')).toList(),
sessionId: sessionId ?? 'standalone',
targetType: _targetType!,
shots: _shots,
totalScore: totalScore,
groupingDiameter: _groupingResult?.diameter,
groupingCenterX: _groupingResult?.centerX,
@@ -745,11 +743,34 @@ class AnalysisProvider extends ChangeNotifier {
targetRadius: _targetRadius,
);
// Update shots with session ID
_shots = session.shots;
notifyListeners();
if (sessionId != null && sessionId != 'standalone') {
final existingSession = await _sessionRepository.getSession(sessionId);
if (existingSession != null) {
await _sessionRepository.addAnalysisToSession(sessionId, targetAnalysis);
} else {
await _sessionRepository.createSession(
id: sessionId,
weapon: weaponName ?? 'Inconnue',
weaponId: weaponId,
maxShotsPerTarget: shotCount,
analyses: [targetAnalysis],
notes: notes,
distance: distance ?? 25,
);
}
} else {
await _sessionRepository.createSession(
weapon: weaponName ?? 'Inconnue',
weaponId: weaponId,
maxShotsPerTarget: shotCount,
analyses: [targetAnalysis],
notes: notes,
distance: distance ?? 25,
);
}
return session;
notifyListeners();
return targetAnalysis;
}
/// Reset the provider