feat: implement base architecture and core repositories for weapon tracking and target analysis functionality
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path/path.dart';
|
||||
import '../models/session.dart';
|
||||
import '../models/target_analysis.dart';
|
||||
import '../models/shot.dart';
|
||||
import '../models/weapon.dart';
|
||||
import '../models/maintenance.dart';
|
||||
import '../../core/constants/app_constants.dart';
|
||||
|
||||
class DatabaseHelper {
|
||||
@@ -36,6 +39,19 @@ class DatabaseHelper {
|
||||
await db.execute('''
|
||||
CREATE TABLE ${AppConstants.sessionsTable} (
|
||||
id TEXT PRIMARY KEY,
|
||||
weapon TEXT NOT NULL,
|
||||
weapon_id TEXT,
|
||||
max_shots_per_target INTEGER NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
notes TEXT,
|
||||
distance INTEGER DEFAULT 25
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE ${AppConstants.targetAnalysesTable} (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_id TEXT NOT NULL,
|
||||
target_type TEXT NOT NULL,
|
||||
image_path TEXT NOT NULL,
|
||||
total_score INTEGER NOT NULL,
|
||||
@@ -46,7 +62,8 @@ class DatabaseHelper {
|
||||
notes TEXT,
|
||||
target_center_x REAL,
|
||||
target_center_y REAL,
|
||||
target_radius REAL
|
||||
target_radius REAL,
|
||||
FOREIGN KEY (session_id) REFERENCES ${AppConstants.sessionsTable}(id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
|
||||
@@ -56,22 +73,212 @@ class DatabaseHelper {
|
||||
x REAL NOT NULL,
|
||||
y REAL NOT NULL,
|
||||
score INTEGER NOT NULL,
|
||||
session_id TEXT NOT NULL,
|
||||
FOREIGN KEY (session_id) REFERENCES ${AppConstants.sessionsTable}(id) ON DELETE CASCADE
|
||||
analysis_id TEXT NOT NULL,
|
||||
FOREIGN KEY (analysis_id) REFERENCES ${AppConstants.targetAnalysesTable}(id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE INDEX idx_shots_session_id ON ${AppConstants.shotsTable}(session_id)
|
||||
CREATE INDEX idx_target_analyses_session_id ON ${AppConstants.targetAnalysesTable}(session_id)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE INDEX idx_shots_analysis_id ON ${AppConstants.shotsTable}(analysis_id)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE INDEX idx_sessions_created_at ON ${AppConstants.sessionsTable}(created_at DESC)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE ${AppConstants.weaponsTable} (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
caliber TEXT NOT NULL,
|
||||
magazine_count INTEGER NOT NULL,
|
||||
magazine_capacity INTEGER NOT NULL,
|
||||
notes TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
optic TEXT,
|
||||
silencer TEXT,
|
||||
trigger TEXT,
|
||||
custom_name TEXT
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE ${AppConstants.maintenanceTable} (
|
||||
id TEXT PRIMARY KEY,
|
||||
weapon_id TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
rounds_since_last INTEGER,
|
||||
FOREIGN KEY (weapon_id) REFERENCES ${AppConstants.weaponsTable}(id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
|
||||
// Handle future database migrations here
|
||||
if (oldVersion < 2) {
|
||||
// Migration from single-target sessions to multi-target sessions
|
||||
|
||||
// 1. Rename existing sessions table to a temporary name
|
||||
await db.execute('ALTER TABLE ${AppConstants.sessionsTable} RENAME TO sessions_old');
|
||||
|
||||
// 2. Create the new sessions table
|
||||
await db.execute('''
|
||||
CREATE TABLE ${AppConstants.sessionsTable} (
|
||||
id TEXT PRIMARY KEY,
|
||||
weapon TEXT NOT NULL,
|
||||
max_shots_per_target INTEGER NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
notes TEXT
|
||||
)
|
||||
''');
|
||||
|
||||
// 3. Create the new target_analyses table
|
||||
await db.execute('''
|
||||
CREATE TABLE ${AppConstants.targetAnalysesTable} (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_id TEXT NOT NULL,
|
||||
target_type TEXT NOT NULL,
|
||||
image_path TEXT NOT NULL,
|
||||
total_score INTEGER NOT NULL,
|
||||
grouping_diameter REAL,
|
||||
grouping_center_x REAL,
|
||||
grouping_center_y REAL,
|
||||
created_at TEXT NOT NULL,
|
||||
notes TEXT,
|
||||
target_center_x REAL,
|
||||
target_center_y REAL,
|
||||
target_radius REAL,
|
||||
FOREIGN KEY (session_id) REFERENCES ${AppConstants.sessionsTable}(id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
|
||||
// 4. Update shots table (rename session_id to analysis_id)
|
||||
await db.execute('ALTER TABLE ${AppConstants.shotsTable} RENAME TO shots_old');
|
||||
await db.execute('''
|
||||
CREATE TABLE ${AppConstants.shotsTable} (
|
||||
id TEXT PRIMARY KEY,
|
||||
x REAL NOT NULL,
|
||||
y REAL NOT NULL,
|
||||
score INTEGER NOT NULL,
|
||||
analysis_id TEXT NOT NULL,
|
||||
FOREIGN KEY (analysis_id) REFERENCES ${AppConstants.targetAnalysesTable}(id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
|
||||
// 5. Migrate data
|
||||
// For each old session, create one session and one target_analysis
|
||||
final oldSessions = await db.query('sessions_old');
|
||||
for (final old in oldSessions) {
|
||||
final sessionId = old['id'] as String;
|
||||
final analysisId = 'analysis_$sessionId'; // Create a unique ID for the analysis
|
||||
|
||||
// Insert into new sessions
|
||||
await db.insert(AppConstants.sessionsTable, {
|
||||
'id': sessionId,
|
||||
'weapon': 'Inconnue',
|
||||
'max_shots_per_target': 5,
|
||||
'created_at': old['created_at'],
|
||||
'notes': old['notes'],
|
||||
});
|
||||
|
||||
// Insert into target_analyses
|
||||
await db.insert(AppConstants.targetAnalysesTable, {
|
||||
'id': analysisId,
|
||||
'session_id': sessionId,
|
||||
'target_type': old['target_type'],
|
||||
'image_path': old['image_path'],
|
||||
'total_score': old['total_score'],
|
||||
'grouping_diameter': old['grouping_diameter'],
|
||||
'grouping_center_x': old['grouping_center_x'],
|
||||
'grouping_center_y': old['grouping_center_y'],
|
||||
'created_at': old['created_at'],
|
||||
'notes': null,
|
||||
'target_center_x': old['target_center_x'],
|
||||
'target_center_y': old['target_center_y'],
|
||||
'target_radius': old['target_radius'],
|
||||
});
|
||||
|
||||
// Migrate shots from shots_old to shots
|
||||
final oldShots = await db.query('shots_old', where: 'session_id = ?', whereArgs: [sessionId]);
|
||||
for (final shot in oldShots) {
|
||||
await db.insert(AppConstants.shotsTable, {
|
||||
'id': shot['id'],
|
||||
'x': shot['x'],
|
||||
'y': shot['y'],
|
||||
'score': shot['score'],
|
||||
'analysis_id': analysisId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Cleanup
|
||||
await db.execute('DROP TABLE sessions_old');
|
||||
await db.execute('DROP TABLE shots_old');
|
||||
|
||||
// 7. Re-create indexes
|
||||
await db.execute('''
|
||||
CREATE INDEX idx_target_analyses_session_id ON ${AppConstants.targetAnalysesTable}(session_id)
|
||||
''');
|
||||
await db.execute('''
|
||||
CREATE INDEX idx_shots_analysis_id ON ${AppConstants.shotsTable}(analysis_id)
|
||||
''');
|
||||
await db.execute('''
|
||||
CREATE INDEX idx_sessions_created_at ON ${AppConstants.sessionsTable}(created_at DESC)
|
||||
''');
|
||||
}
|
||||
|
||||
if (oldVersion < 3) {
|
||||
// Add weapons table
|
||||
await db.execute('''
|
||||
CREATE TABLE ${AppConstants.weaponsTable} (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
caliber TEXT NOT NULL,
|
||||
magazine_count INTEGER NOT NULL,
|
||||
magazine_capacity INTEGER NOT NULL,
|
||||
notes TEXT,
|
||||
created_at TEXT NOT NULL
|
||||
)
|
||||
''');
|
||||
|
||||
// Add weapon_id to sessions table
|
||||
await db.execute('ALTER TABLE ${AppConstants.sessionsTable} ADD COLUMN weapon_id TEXT');
|
||||
}
|
||||
|
||||
if (oldVersion < 4) {
|
||||
// Add maintenance table
|
||||
await db.execute('''
|
||||
CREATE TABLE ${AppConstants.maintenanceTable} (
|
||||
id TEXT PRIMARY KEY,
|
||||
weapon_id TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
rounds_since_last INTEGER,
|
||||
FOREIGN KEY (weapon_id) REFERENCES ${AppConstants.weaponsTable}(id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
||||
if (oldVersion < 5) {
|
||||
// Add new weapon options columns
|
||||
await db.execute('ALTER TABLE ${AppConstants.weaponsTable} ADD COLUMN optic TEXT');
|
||||
await db.execute('ALTER TABLE ${AppConstants.weaponsTable} ADD COLUMN silencer TEXT');
|
||||
await db.execute('ALTER TABLE ${AppConstants.weaponsTable} ADD COLUMN "trigger" TEXT');
|
||||
await db.execute('ALTER TABLE ${AppConstants.weaponsTable} ADD COLUMN custom_name TEXT');
|
||||
}
|
||||
|
||||
if (oldVersion < 6) {
|
||||
// Add distance column to sessions
|
||||
await db.execute('ALTER TABLE ${AppConstants.sessionsTable} ADD COLUMN distance INTEGER DEFAULT 25');
|
||||
}
|
||||
}
|
||||
|
||||
// Session operations
|
||||
@@ -84,20 +291,39 @@ class DatabaseHelper {
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
|
||||
for (final shot in session.shots) {
|
||||
// Ensure shot has correct session_id
|
||||
final shotWithSessionId = shot.copyWith(sessionId: session.id);
|
||||
await txn.insert(
|
||||
AppConstants.shotsTable,
|
||||
shotWithSessionId.toMap(),
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
for (final analysis in session.analyses) {
|
||||
await insertTargetAnalysisTx(txn, analysis, session.id);
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> insertTargetAnalysis(TargetAnalysis analysis, String sessionId) async {
|
||||
final db = await database;
|
||||
await db.transaction((txn) async {
|
||||
await insertTargetAnalysisTx(txn, analysis, sessionId);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> insertTargetAnalysisTx(Transaction txn, TargetAnalysis analysis, String sessionId) async {
|
||||
final analysisWithSessionId = analysis.copyWith(sessionId: sessionId);
|
||||
await txn.insert(
|
||||
AppConstants.targetAnalysesTable,
|
||||
analysisWithSessionId.toMap(),
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
|
||||
for (final shot in analysis.shots) {
|
||||
final shotWithAnalysisId = shot.copyWith(analysisId: analysis.id);
|
||||
await txn.insert(
|
||||
AppConstants.shotsTable,
|
||||
shotWithAnalysisId.toMap(),
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Session?> getSession(String id) async {
|
||||
final db = await database;
|
||||
final sessionMaps = await db.query(
|
||||
@@ -108,106 +334,52 @@ class DatabaseHelper {
|
||||
|
||||
if (sessionMaps.isEmpty) return null;
|
||||
|
||||
final shotMaps = await db.query(
|
||||
AppConstants.shotsTable,
|
||||
final analysisMaps = await db.query(
|
||||
AppConstants.targetAnalysesTable,
|
||||
where: 'session_id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
|
||||
final shots = shotMaps.map((map) => Shot.fromMap(map)).toList();
|
||||
return Session.fromMap(sessionMaps.first, shots);
|
||||
final analyses = <TargetAnalysis>[];
|
||||
for (final analysisMap in analysisMaps) {
|
||||
final analysisId = analysisMap['id'] as String;
|
||||
final shotMaps = await db.query(
|
||||
AppConstants.shotsTable,
|
||||
where: 'analysis_id = ?',
|
||||
whereArgs: [analysisId],
|
||||
);
|
||||
final shots = shotMaps.map((map) => Shot.fromMap(map)).toList();
|
||||
analyses.add(TargetAnalysis.fromMap(analysisMap, shots));
|
||||
}
|
||||
|
||||
return Session.fromMap(sessionMaps.first, analyses);
|
||||
}
|
||||
|
||||
Future<List<Session>> getAllSessions({
|
||||
String? targetType,
|
||||
int? limit,
|
||||
int? offset,
|
||||
}) async {
|
||||
final db = await database;
|
||||
|
||||
String? whereClause;
|
||||
List<dynamic>? whereArgs;
|
||||
|
||||
if (targetType != null) {
|
||||
whereClause = 'target_type = ?';
|
||||
whereArgs = [targetType];
|
||||
}
|
||||
|
||||
final sessionMaps = await db.query(
|
||||
AppConstants.sessionsTable,
|
||||
where: whereClause,
|
||||
whereArgs: whereArgs,
|
||||
orderBy: 'created_at DESC',
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
);
|
||||
|
||||
// First, check if there are orphaned shots (with empty session_id)
|
||||
// and only one session - if so, assign them to that session
|
||||
if (sessionMaps.length == 1) {
|
||||
final orphanedShots = await db.query(
|
||||
AppConstants.shotsTable,
|
||||
where: 'session_id = ?',
|
||||
whereArgs: [''],
|
||||
);
|
||||
if (orphanedShots.isNotEmpty) {
|
||||
await db.update(
|
||||
AppConstants.shotsTable,
|
||||
{'session_id': sessionMaps.first['id']},
|
||||
where: 'session_id = ?',
|
||||
whereArgs: [''],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final sessions = <Session>[];
|
||||
for (final sessionMap in sessionMaps) {
|
||||
final sessionId = sessionMap['id'] as String;
|
||||
|
||||
final shotMaps = await db.query(
|
||||
AppConstants.shotsTable,
|
||||
where: 'session_id = ?',
|
||||
whereArgs: [sessionId],
|
||||
);
|
||||
|
||||
final shots = shotMaps.map((map) => Shot.fromMap(map)).toList();
|
||||
sessions.add(Session.fromMap(sessionMap, shots));
|
||||
final session = await getSession(sessionId);
|
||||
if (session != null) {
|
||||
sessions.add(session);
|
||||
}
|
||||
}
|
||||
|
||||
return sessions;
|
||||
}
|
||||
|
||||
Future<int> updateSession(Session session) async {
|
||||
final db = await database;
|
||||
return await db.transaction((txn) async {
|
||||
await txn.update(
|
||||
AppConstants.sessionsTable,
|
||||
session.toMap(),
|
||||
where: 'id = ?',
|
||||
whereArgs: [session.id],
|
||||
);
|
||||
|
||||
// Delete existing shots and insert new ones
|
||||
await txn.delete(
|
||||
AppConstants.shotsTable,
|
||||
where: 'session_id = ?',
|
||||
whereArgs: [session.id],
|
||||
);
|
||||
|
||||
for (final shot in session.shots) {
|
||||
// Ensure shot has correct session_id
|
||||
final shotWithSessionId = shot.copyWith(sessionId: session.id);
|
||||
await txn.insert(
|
||||
AppConstants.shotsTable,
|
||||
shotWithSessionId.toMap(),
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> deleteSession(String id) async {
|
||||
final db = await database;
|
||||
return await db.delete(
|
||||
@@ -229,12 +401,12 @@ class DatabaseHelper {
|
||||
) ?? 0;
|
||||
|
||||
final avgScore = (await db.rawQuery(
|
||||
'SELECT AVG(total_score) as avg FROM ${AppConstants.sessionsTable}',
|
||||
'SELECT AVG(total_score) as avg FROM ${AppConstants.targetAnalysesTable}',
|
||||
)).first['avg'] as double? ?? 0.0;
|
||||
|
||||
final bestScore = Sqflite.firstIntValue(
|
||||
await db.rawQuery(
|
||||
'SELECT MAX(total_score) FROM ${AppConstants.sessionsTable}',
|
||||
'SELECT MAX(total_score) FROM ${AppConstants.targetAnalysesTable}',
|
||||
),
|
||||
) ?? 0;
|
||||
|
||||
@@ -246,6 +418,121 @@ class DatabaseHelper {
|
||||
};
|
||||
}
|
||||
|
||||
// Weapon operations
|
||||
Future<int> insertWeapon(Weapon weapon) async {
|
||||
final db = await database;
|
||||
return await db.insert(
|
||||
AppConstants.weaponsTable,
|
||||
weapon.toMap(),
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<Weapon>> getAllWeapons() async {
|
||||
final db = await database;
|
||||
final List<Map<String, dynamic>> maps = await db.query(AppConstants.weaponsTable, orderBy: 'name ASC');
|
||||
return List.generate(maps.length, (i) => Weapon.fromMap(maps[i]));
|
||||
}
|
||||
|
||||
Future<Weapon?> getWeapon(String id) async {
|
||||
final db = await database;
|
||||
final List<Map<String, dynamic>> maps = await db.query(
|
||||
AppConstants.weaponsTable,
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
if (maps.isEmpty) return null;
|
||||
return Weapon.fromMap(maps.first);
|
||||
}
|
||||
|
||||
Future<int> updateWeapon(Weapon weapon) async {
|
||||
final db = await database;
|
||||
|
||||
// Start a transaction to ensure both updates succeed
|
||||
return await db.transaction((txn) async {
|
||||
// 1. Update weapon in armory
|
||||
final count = await txn.update(
|
||||
AppConstants.weaponsTable,
|
||||
weapon.toMap(),
|
||||
where: 'id = ?',
|
||||
whereArgs: [weapon.id],
|
||||
);
|
||||
|
||||
// 2. Propagate name change to all sessions linked to this weapon
|
||||
await txn.update(
|
||||
AppConstants.sessionsTable,
|
||||
{'weapon': weapon.displayName},
|
||||
where: 'weapon_id = ?',
|
||||
whereArgs: [weapon.id],
|
||||
);
|
||||
|
||||
return count;
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> deleteWeapon(String id) async {
|
||||
final db = await database;
|
||||
|
||||
return await db.transaction((txn) async {
|
||||
// 1. Unlink sessions from this weapon but KEEP the name (gravé à jamais)
|
||||
await txn.update(
|
||||
AppConstants.sessionsTable,
|
||||
{'weapon_id': null},
|
||||
where: 'weapon_id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
|
||||
// 2. Delete the weapon record
|
||||
return await txn.delete(
|
||||
AppConstants.weaponsTable,
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Weapon statistics & Maintenance
|
||||
Future<int> getRoundsFiredForWeapon(String weaponId) async {
|
||||
final db = await database;
|
||||
final result = await db.rawQuery('''
|
||||
SELECT COUNT(s.id) as count
|
||||
FROM ${AppConstants.shotsTable} s
|
||||
JOIN ${AppConstants.targetAnalysesTable} ta ON s.analysis_id = ta.id
|
||||
JOIN ${AppConstants.sessionsTable} sess ON ta.session_id = sess.id
|
||||
WHERE sess.weapon_id = ?
|
||||
''', [weaponId]);
|
||||
return Sqflite.firstIntValue(result) ?? 0;
|
||||
}
|
||||
|
||||
Future<int> insertMaintenance(MaintenanceEntry entry) async {
|
||||
final db = await database;
|
||||
return await db.insert(
|
||||
AppConstants.maintenanceTable,
|
||||
entry.toMap(),
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<MaintenanceEntry>> getMaintenanceForWeapon(String weaponId) async {
|
||||
final db = await database;
|
||||
final maps = await db.query(
|
||||
AppConstants.maintenanceTable,
|
||||
where: 'weapon_id = ?',
|
||||
orderBy: 'date DESC',
|
||||
whereArgs: [weaponId],
|
||||
);
|
||||
return List.generate(maps.length, (i) => MaintenanceEntry.fromMap(maps[i]));
|
||||
}
|
||||
|
||||
Future<int> deleteMaintenance(String id) async {
|
||||
final db = await database;
|
||||
return await db.delete(
|
||||
AppConstants.maintenanceTable,
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
final db = await database;
|
||||
await db.close();
|
||||
|
||||
57
lib/data/models/maintenance.dart
Normal file
57
lib/data/models/maintenance.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
enum MaintenanceType {
|
||||
cleaning('Nettoyage'),
|
||||
repair('Réparation'),
|
||||
inspection('Inspection'),
|
||||
upgrade('Amélioration'),
|
||||
other('Autre');
|
||||
|
||||
final String displayName;
|
||||
const MaintenanceType(this.displayName);
|
||||
|
||||
static MaintenanceType fromString(String value) {
|
||||
return MaintenanceType.values.firstWhere(
|
||||
(type) => type.name == value,
|
||||
orElse: () => MaintenanceType.other,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MaintenanceEntry {
|
||||
final String id;
|
||||
final String weaponId;
|
||||
final MaintenanceType type;
|
||||
final String description;
|
||||
final DateTime date;
|
||||
final int? roundsSinceLastMaintenance;
|
||||
|
||||
MaintenanceEntry({
|
||||
required this.id,
|
||||
required this.weaponId,
|
||||
required this.type,
|
||||
required this.description,
|
||||
required this.date,
|
||||
this.roundsSinceLastMaintenance,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'weapon_id': weaponId,
|
||||
'type': type.name,
|
||||
'description': description,
|
||||
'date': date.toIso8601String(),
|
||||
'rounds_since_last': roundsSinceLastMaintenance,
|
||||
};
|
||||
}
|
||||
|
||||
factory MaintenanceEntry.fromMap(Map<String, dynamic> map) {
|
||||
return MaintenanceEntry(
|
||||
id: map['id'] as String,
|
||||
weaponId: map['weapon_id'] as String,
|
||||
type: MaintenanceType.fromString(map['type'] as String),
|
||||
description: map['description'] as String,
|
||||
date: DateTime.parse(map['date'] as String),
|
||||
roundsSinceLastMaintenance: map['rounds_since_last'] as int?,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,112 +1,83 @@
|
||||
import 'shot.dart';
|
||||
import 'target_type.dart';
|
||||
import 'target_analysis.dart';
|
||||
|
||||
class Session {
|
||||
final String id;
|
||||
final TargetType targetType;
|
||||
final String imagePath;
|
||||
final List<Shot> shots;
|
||||
final int totalScore;
|
||||
final double? groupingDiameter;
|
||||
final double? groupingCenterX;
|
||||
final double? groupingCenterY;
|
||||
final String weapon;
|
||||
final String? weaponId;
|
||||
final int maxShotsPerTarget;
|
||||
final List<TargetAnalysis> analyses;
|
||||
final DateTime createdAt;
|
||||
final String? notes;
|
||||
|
||||
// Target detection data
|
||||
final double? targetCenterX;
|
||||
final double? targetCenterY;
|
||||
final double? targetRadius;
|
||||
final int distance; // Shooting distance in meters
|
||||
|
||||
Session({
|
||||
required this.id,
|
||||
required this.targetType,
|
||||
required this.imagePath,
|
||||
required this.shots,
|
||||
required this.totalScore,
|
||||
this.groupingDiameter,
|
||||
this.groupingCenterX,
|
||||
this.groupingCenterY,
|
||||
required this.weapon,
|
||||
this.weaponId,
|
||||
required this.maxShotsPerTarget,
|
||||
required this.analyses,
|
||||
required this.createdAt,
|
||||
this.notes,
|
||||
this.targetCenterX,
|
||||
this.targetCenterY,
|
||||
this.targetRadius,
|
||||
this.distance = 25, // Default distance
|
||||
});
|
||||
|
||||
int get shotCount => shots.length;
|
||||
int get targetCount => analyses.length;
|
||||
|
||||
int get totalShots => analyses.fold(0, (sum, a) => sum + a.shotCount);
|
||||
|
||||
int get totalScore => analyses.fold(0, (sum, a) => sum + a.totalScore);
|
||||
|
||||
double get averageScore => shots.isEmpty ? 0.0 : totalScore / shots.length;
|
||||
double get averageScore => analyses.isEmpty ? 0.0 : totalScore / analyses.length;
|
||||
|
||||
Session copyWith({
|
||||
String? id,
|
||||
TargetType? targetType,
|
||||
String? imagePath,
|
||||
List<Shot>? shots,
|
||||
int? totalScore,
|
||||
double? groupingDiameter,
|
||||
double? groupingCenterX,
|
||||
double? groupingCenterY,
|
||||
String? weapon,
|
||||
String? weaponId,
|
||||
int? maxShotsPerTarget,
|
||||
List<TargetAnalysis>? analyses,
|
||||
DateTime? createdAt,
|
||||
String? notes,
|
||||
double? targetCenterX,
|
||||
double? targetCenterY,
|
||||
double? targetRadius,
|
||||
int? distance,
|
||||
}) {
|
||||
return Session(
|
||||
id: id ?? this.id,
|
||||
targetType: targetType ?? this.targetType,
|
||||
imagePath: imagePath ?? this.imagePath,
|
||||
shots: shots ?? this.shots,
|
||||
totalScore: totalScore ?? this.totalScore,
|
||||
groupingDiameter: groupingDiameter ?? this.groupingDiameter,
|
||||
groupingCenterX: groupingCenterX ?? this.groupingCenterX,
|
||||
groupingCenterY: groupingCenterY ?? this.groupingCenterY,
|
||||
weapon: weapon ?? this.weapon,
|
||||
weaponId: weaponId ?? this.weaponId,
|
||||
maxShotsPerTarget: maxShotsPerTarget ?? this.maxShotsPerTarget,
|
||||
analyses: analyses ?? this.analyses,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
notes: notes ?? this.notes,
|
||||
targetCenterX: targetCenterX ?? this.targetCenterX,
|
||||
targetCenterY: targetCenterY ?? this.targetCenterY,
|
||||
targetRadius: targetRadius ?? this.targetRadius,
|
||||
distance: distance ?? this.distance,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'target_type': targetType.name,
|
||||
'image_path': imagePath,
|
||||
'total_score': totalScore,
|
||||
'grouping_diameter': groupingDiameter,
|
||||
'grouping_center_x': groupingCenterX,
|
||||
'grouping_center_y': groupingCenterY,
|
||||
'weapon': weapon,
|
||||
'weapon_id': weaponId,
|
||||
'max_shots_per_target': maxShotsPerTarget,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'notes': notes,
|
||||
'target_center_x': targetCenterX,
|
||||
'target_center_y': targetCenterY,
|
||||
'target_radius': targetRadius,
|
||||
'distance': distance,
|
||||
};
|
||||
}
|
||||
|
||||
factory Session.fromMap(Map<String, dynamic> map, List<Shot> shots) {
|
||||
factory Session.fromMap(Map<String, dynamic> map, List<TargetAnalysis> analyses) {
|
||||
return Session(
|
||||
id: map['id'] as String,
|
||||
targetType: TargetType.fromString(map['target_type'] as String),
|
||||
imagePath: map['image_path'] as String,
|
||||
shots: shots,
|
||||
totalScore: map['total_score'] as int,
|
||||
groupingDiameter: map['grouping_diameter'] as double?,
|
||||
groupingCenterX: map['grouping_center_x'] as double?,
|
||||
groupingCenterY: map['grouping_center_y'] as double?,
|
||||
weapon: map['weapon'] as String? ?? 'Inconnue',
|
||||
weaponId: map['weapon_id'] as String?,
|
||||
maxShotsPerTarget: map['max_shots_per_target'] as int? ?? 5,
|
||||
analyses: analyses,
|
||||
createdAt: DateTime.parse(map['created_at'] as String),
|
||||
notes: map['notes'] as String?,
|
||||
targetCenterX: map['target_center_x'] as double?,
|
||||
targetCenterY: map['target_center_y'] as double?,
|
||||
targetRadius: map['target_radius'] as double?,
|
||||
distance: map['distance'] as int? ?? 25,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Session(id: $id, targetType: $targetType, shots: ${shots.length}, totalScore: $totalScore, createdAt: $createdAt)';
|
||||
return 'Session(id: $id, weapon: $weapon, distance: ${distance}m, targets: ${analyses.length}, createdAt: $createdAt)';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ class Shot {
|
||||
final double x; // Relative position (0.0 - 1.0)
|
||||
final double y; // Relative position (0.0 - 1.0)
|
||||
final int score;
|
||||
final String sessionId;
|
||||
final String analysisId;
|
||||
|
||||
Shot({
|
||||
required this.id,
|
||||
required this.x,
|
||||
required this.y,
|
||||
required this.score,
|
||||
required this.sessionId,
|
||||
required this.analysisId,
|
||||
});
|
||||
|
||||
Shot copyWith({
|
||||
@@ -18,14 +18,14 @@ class Shot {
|
||||
double? x,
|
||||
double? y,
|
||||
int? score,
|
||||
String? sessionId,
|
||||
String? analysisId,
|
||||
}) {
|
||||
return Shot(
|
||||
id: id ?? this.id,
|
||||
x: x ?? this.x,
|
||||
y: y ?? this.y,
|
||||
score: score ?? this.score,
|
||||
sessionId: sessionId ?? this.sessionId,
|
||||
analysisId: analysisId ?? this.analysisId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class Shot {
|
||||
'x': x,
|
||||
'y': y,
|
||||
'score': score,
|
||||
'session_id': sessionId,
|
||||
'analysis_id': analysisId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -45,13 +45,13 @@ class Shot {
|
||||
x: (map['x'] as num).toDouble(),
|
||||
y: (map['y'] as num).toDouble(),
|
||||
score: map['score'] as int,
|
||||
sessionId: map['session_id'] as String,
|
||||
analysisId: map['analysis_id'] as String? ?? map['session_id'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Shot(id: $id, x: $x, y: $y, score: $score, sessionId: $sessionId)';
|
||||
return 'Shot(id: $id, x: $x, y: $y, score: $score, analysisId: $analysisId)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -62,11 +62,11 @@ class Shot {
|
||||
other.x == x &&
|
||||
other.y == y &&
|
||||
other.score == score &&
|
||||
other.sessionId == sessionId;
|
||||
other.analysisId == analysisId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return id.hashCode ^ x.hashCode ^ y.hashCode ^ score.hashCode ^ sessionId.hashCode;
|
||||
return id.hashCode ^ x.hashCode ^ y.hashCode ^ score.hashCode ^ analysisId.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
113
lib/data/models/target_analysis.dart
Normal file
113
lib/data/models/target_analysis.dart
Normal file
@@ -0,0 +1,113 @@
|
||||
import 'shot.dart';
|
||||
import 'target_type.dart';
|
||||
|
||||
class TargetAnalysis {
|
||||
final String id;
|
||||
final String sessionId;
|
||||
final TargetType targetType;
|
||||
final String imagePath;
|
||||
final List<Shot> shots;
|
||||
final int totalScore;
|
||||
final double? groupingDiameter;
|
||||
final double? groupingCenterX;
|
||||
final double? groupingCenterY;
|
||||
final DateTime createdAt;
|
||||
final String? notes;
|
||||
|
||||
// Target detection data
|
||||
final double? targetCenterX;
|
||||
final double? targetCenterY;
|
||||
final double? targetRadius;
|
||||
|
||||
TargetAnalysis({
|
||||
required this.id,
|
||||
required this.sessionId,
|
||||
required this.targetType,
|
||||
required this.imagePath,
|
||||
required this.shots,
|
||||
required this.totalScore,
|
||||
this.groupingDiameter,
|
||||
this.groupingCenterX,
|
||||
this.groupingCenterY,
|
||||
required this.createdAt,
|
||||
this.notes,
|
||||
this.targetCenterX,
|
||||
this.targetCenterY,
|
||||
this.targetRadius,
|
||||
});
|
||||
|
||||
int get shotCount => shots.length;
|
||||
|
||||
double get averageScore => shots.isEmpty ? 0.0 : totalScore / shots.length;
|
||||
|
||||
TargetAnalysis copyWith({
|
||||
String? id,
|
||||
String? sessionId,
|
||||
TargetType? targetType,
|
||||
String? imagePath,
|
||||
List<Shot>? shots,
|
||||
int? totalScore,
|
||||
double? groupingDiameter,
|
||||
double? groupingCenterX,
|
||||
double? groupingCenterY,
|
||||
DateTime? createdAt,
|
||||
String? notes,
|
||||
double? targetCenterX,
|
||||
double? targetCenterY,
|
||||
double? targetRadius,
|
||||
}) {
|
||||
return TargetAnalysis(
|
||||
id: id ?? this.id,
|
||||
sessionId: sessionId ?? this.sessionId,
|
||||
targetType: targetType ?? this.targetType,
|
||||
imagePath: imagePath ?? this.imagePath,
|
||||
shots: shots ?? this.shots,
|
||||
totalScore: totalScore ?? this.totalScore,
|
||||
groupingDiameter: groupingDiameter ?? this.groupingDiameter,
|
||||
groupingCenterX: groupingCenterX ?? this.groupingCenterX,
|
||||
groupingCenterY: groupingCenterY ?? this.groupingCenterY,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
notes: notes ?? this.notes,
|
||||
targetCenterX: targetCenterX ?? this.targetCenterX,
|
||||
targetCenterY: targetCenterY ?? this.targetCenterY,
|
||||
targetRadius: targetRadius ?? this.targetRadius,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'session_id': sessionId,
|
||||
'target_type': targetType.name,
|
||||
'image_path': imagePath,
|
||||
'total_score': totalScore,
|
||||
'grouping_diameter': groupingDiameter,
|
||||
'grouping_center_x': groupingCenterX,
|
||||
'grouping_center_y': groupingCenterY,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'notes': notes,
|
||||
'target_center_x': targetCenterX,
|
||||
'target_center_y': targetCenterY,
|
||||
'target_radius': targetRadius,
|
||||
};
|
||||
}
|
||||
|
||||
factory TargetAnalysis.fromMap(Map<String, dynamic> map, List<Shot> shots) {
|
||||
return TargetAnalysis(
|
||||
id: map['id'] as String,
|
||||
sessionId: map['session_id'] as String,
|
||||
targetType: TargetType.fromString(map['target_type'] as String),
|
||||
imagePath: map['image_path'] as String,
|
||||
shots: shots,
|
||||
totalScore: map['total_score'] as int,
|
||||
groupingDiameter: map['grouping_diameter'] as double?,
|
||||
groupingCenterX: map['grouping_center_x'] as double?,
|
||||
groupingCenterY: map['grouping_center_y'] as double?,
|
||||
createdAt: DateTime.parse(map['created_at'] as String),
|
||||
notes: map['notes'] as String?,
|
||||
targetCenterX: map['target_center_x'] as double?,
|
||||
targetCenterY: map['target_center_y'] as double?,
|
||||
targetRadius: map['target_radius'] as double?,
|
||||
);
|
||||
}
|
||||
}
|
||||
114
lib/data/models/weapon.dart
Normal file
114
lib/data/models/weapon.dart
Normal file
@@ -0,0 +1,114 @@
|
||||
enum WeaponType {
|
||||
handgun('Arme de Poing'),
|
||||
rifle('Arme d\'Épaule'),
|
||||
shotgun('Fusil à Pompe'),
|
||||
airgun('Airsoft / Airgun');
|
||||
|
||||
final String displayName;
|
||||
const WeaponType(this.displayName);
|
||||
|
||||
static WeaponType fromString(String value) {
|
||||
return WeaponType.values.firstWhere(
|
||||
(type) => type.name == value,
|
||||
orElse: () => WeaponType.handgun,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Weapon {
|
||||
final String id;
|
||||
final String name;
|
||||
final WeaponType type;
|
||||
final String caliber;
|
||||
final int magazineCount;
|
||||
final int magazineCapacity;
|
||||
final String? notes;
|
||||
final DateTime createdAt;
|
||||
|
||||
// New options
|
||||
final String? optic;
|
||||
final String? silencer;
|
||||
final String? trigger;
|
||||
final String? customName;
|
||||
|
||||
String get displayName => (customName != null && customName!.isNotEmpty) ? customName! : name;
|
||||
|
||||
Weapon({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.type,
|
||||
required this.caliber,
|
||||
required this.magazineCount,
|
||||
required this.magazineCapacity,
|
||||
this.notes,
|
||||
required this.createdAt,
|
||||
this.optic,
|
||||
this.silencer,
|
||||
this.trigger,
|
||||
this.customName,
|
||||
});
|
||||
|
||||
Weapon copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
WeaponType? type,
|
||||
String? caliber,
|
||||
int? magazineCount,
|
||||
int? magazineCapacity,
|
||||
String? notes,
|
||||
DateTime? createdAt,
|
||||
String? optic,
|
||||
String? silencer,
|
||||
String? trigger,
|
||||
String? customName,
|
||||
}) {
|
||||
return Weapon(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
type: type ?? this.type,
|
||||
caliber: caliber ?? this.caliber,
|
||||
magazineCount: magazineCount ?? this.magazineCount,
|
||||
magazineCapacity: magazineCapacity ?? this.magazineCapacity,
|
||||
notes: notes ?? this.notes,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
optic: optic ?? this.optic,
|
||||
silencer: silencer ?? this.silencer,
|
||||
trigger: trigger ?? this.trigger,
|
||||
customName: customName ?? this.customName,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'type': type.name,
|
||||
'caliber': caliber,
|
||||
'magazine_count': magazineCount,
|
||||
'magazine_capacity': magazineCapacity,
|
||||
'notes': notes,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'optic': optic,
|
||||
'silencer': silencer,
|
||||
'trigger': trigger,
|
||||
'custom_name': customName,
|
||||
};
|
||||
}
|
||||
|
||||
factory Weapon.fromMap(Map<String, dynamic> map) {
|
||||
return Weapon(
|
||||
id: map['id'] as String,
|
||||
name: map['name'] as String,
|
||||
type: WeaponType.fromString(map['type'] as String),
|
||||
caliber: map['caliber'] as String,
|
||||
magazineCount: map['magazine_count'] as int,
|
||||
magazineCapacity: map['magazine_capacity'] as int,
|
||||
notes: map['notes'] as String?,
|
||||
createdAt: DateTime.parse(map['created_at'] as String),
|
||||
optic: map['optic'] as String?,
|
||||
silencer: map['silencer'] as String?,
|
||||
trigger: map['trigger'] as String?,
|
||||
customName: map['custom_name'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,10 @@ import 'package:path/path.dart' as path;
|
||||
import 'package:uuid/uuid.dart';
|
||||
import '../database/database_helper.dart';
|
||||
import '../models/session.dart';
|
||||
import '../models/target_analysis.dart';
|
||||
import '../models/shot.dart';
|
||||
import '../models/target_type.dart';
|
||||
import '../models/weapon.dart';
|
||||
import '../models/maintenance.dart';
|
||||
|
||||
class SessionRepository {
|
||||
final DatabaseHelper _databaseHelper;
|
||||
@@ -18,8 +20,37 @@ class SessionRepository {
|
||||
_uuid = uuid ?? const Uuid();
|
||||
|
||||
Future<Session> createSession({
|
||||
required TargetType targetType,
|
||||
String? id,
|
||||
required String weapon,
|
||||
String? weaponId,
|
||||
required int maxShotsPerTarget,
|
||||
required List<TargetAnalysis> analyses,
|
||||
String? notes,
|
||||
int distance = 25,
|
||||
}) async {
|
||||
final session = Session(
|
||||
id: id ?? _uuid.v4(),
|
||||
weapon: weapon,
|
||||
weaponId: weaponId,
|
||||
maxShotsPerTarget: maxShotsPerTarget,
|
||||
analyses: analyses,
|
||||
createdAt: DateTime.now(),
|
||||
notes: notes,
|
||||
distance: distance,
|
||||
);
|
||||
|
||||
await _databaseHelper.insertSession(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
Future<void> addAnalysisToSession(String sessionId, TargetAnalysis analysis) async {
|
||||
await _databaseHelper.insertTargetAnalysis(analysis, sessionId);
|
||||
}
|
||||
|
||||
Future<TargetAnalysis> prepareTargetAnalysis({
|
||||
required String imagePath,
|
||||
required String sessionId,
|
||||
required dynamic targetType, // TargetType
|
||||
required List<Shot> shots,
|
||||
required int totalScore,
|
||||
double? groupingDiameter,
|
||||
@@ -33,8 +64,9 @@ class SessionRepository {
|
||||
// Copy image to app documents directory
|
||||
final savedImagePath = await _saveImage(imagePath);
|
||||
|
||||
final session = Session(
|
||||
return TargetAnalysis(
|
||||
id: _uuid.v4(),
|
||||
sessionId: sessionId,
|
||||
targetType: targetType,
|
||||
imagePath: savedImagePath,
|
||||
shots: shots,
|
||||
@@ -48,9 +80,6 @@ class SessionRepository {
|
||||
targetCenterY: targetCenterY,
|
||||
targetRadius: targetRadius,
|
||||
);
|
||||
|
||||
await _databaseHelper.insertSession(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
Future<String> _saveImage(String sourcePath) async {
|
||||
@@ -75,28 +104,23 @@ class SessionRepository {
|
||||
}
|
||||
|
||||
Future<List<Session>> getAllSessions({
|
||||
TargetType? targetType,
|
||||
int? limit,
|
||||
int? offset,
|
||||
}) async {
|
||||
return await _databaseHelper.getAllSessions(
|
||||
targetType: targetType?.name,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateSession(Session session) async {
|
||||
await _databaseHelper.updateSession(session);
|
||||
}
|
||||
|
||||
Future<void> deleteSession(String id) async {
|
||||
final session = await getSession(id);
|
||||
if (session != null) {
|
||||
// Delete the image file
|
||||
final imageFile = File(session.imagePath);
|
||||
if (await imageFile.exists()) {
|
||||
await imageFile.delete();
|
||||
for (final analysis in session.analyses) {
|
||||
final imageFile = File(analysis.imagePath);
|
||||
if (await imageFile.exists()) {
|
||||
await imageFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
await _databaseHelper.deleteSession(id);
|
||||
@@ -106,7 +130,79 @@ class SessionRepository {
|
||||
return await _databaseHelper.getStatistics();
|
||||
}
|
||||
|
||||
String generateShotId() {
|
||||
String generateId() {
|
||||
return _uuid.v4();
|
||||
}
|
||||
|
||||
// Weapon Repository methods
|
||||
Future<Weapon> addWeapon({
|
||||
required String name,
|
||||
required WeaponType type,
|
||||
required String caliber,
|
||||
required int magazineCount,
|
||||
required int magazineCapacity,
|
||||
String? notes,
|
||||
String? optic,
|
||||
String? silencer,
|
||||
String? trigger,
|
||||
String? customName,
|
||||
}) async {
|
||||
final weapon = Weapon(
|
||||
id: _uuid.v4(),
|
||||
name: name,
|
||||
type: type,
|
||||
caliber: caliber,
|
||||
magazineCount: magazineCount,
|
||||
magazineCapacity: magazineCapacity,
|
||||
notes: notes,
|
||||
createdAt: DateTime.now(),
|
||||
optic: optic,
|
||||
silencer: silencer,
|
||||
trigger: trigger,
|
||||
customName: customName,
|
||||
);
|
||||
await _databaseHelper.insertWeapon(weapon);
|
||||
return weapon;
|
||||
}
|
||||
|
||||
Future<List<Weapon>> getWeapons() async {
|
||||
return await _databaseHelper.getAllWeapons();
|
||||
}
|
||||
|
||||
Future<void> updateWeapon(Weapon weapon) async {
|
||||
await _databaseHelper.updateWeapon(weapon);
|
||||
}
|
||||
|
||||
Future<void> deleteWeapon(String id) async {
|
||||
await _databaseHelper.deleteWeapon(id);
|
||||
}
|
||||
|
||||
Future<int> getRoundsFiredForWeapon(String weaponId) async {
|
||||
return await _databaseHelper.getRoundsFiredForWeapon(weaponId);
|
||||
}
|
||||
|
||||
Future<void> addMaintenanceEntry({
|
||||
required String weaponId,
|
||||
required MaintenanceType type,
|
||||
required String description,
|
||||
int? roundsSinceLast,
|
||||
}) async {
|
||||
final entry = MaintenanceEntry(
|
||||
id: _uuid.v4(),
|
||||
weaponId: weaponId,
|
||||
type: type,
|
||||
description: description,
|
||||
date: DateTime.now(),
|
||||
roundsSinceLastMaintenance: roundsSinceLast,
|
||||
);
|
||||
await _databaseHelper.insertMaintenance(entry);
|
||||
}
|
||||
|
||||
Future<List<MaintenanceEntry>> getMaintenanceHistory(String weaponId) async {
|
||||
return await _databaseHelper.getMaintenanceForWeapon(weaponId);
|
||||
}
|
||||
|
||||
Future<void> deleteMaintenanceEntry(String id) async {
|
||||
await _databaseHelper.deleteMaintenance(id);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user