premier app version beta
This commit is contained in:
254
lib/data/database/database_helper.dart
Normal file
254
lib/data/database/database_helper.dart
Normal file
@@ -0,0 +1,254 @@
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path/path.dart';
|
||||
import '../models/session.dart';
|
||||
import '../models/shot.dart';
|
||||
import '../../core/constants/app_constants.dart';
|
||||
|
||||
class DatabaseHelper {
|
||||
static DatabaseHelper? _instance;
|
||||
static Database? _database;
|
||||
|
||||
DatabaseHelper._internal();
|
||||
|
||||
factory DatabaseHelper() {
|
||||
_instance ??= DatabaseHelper._internal();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
Future<Database> get database async {
|
||||
_database ??= await _initDatabase();
|
||||
return _database!;
|
||||
}
|
||||
|
||||
Future<Database> _initDatabase() async {
|
||||
final databasesPath = await getDatabasesPath();
|
||||
final path = join(databasesPath, AppConstants.databaseName);
|
||||
|
||||
return await openDatabase(
|
||||
path,
|
||||
version: AppConstants.databaseVersion,
|
||||
onCreate: _onCreate,
|
||||
onUpgrade: _onUpgrade,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onCreate(Database db, int version) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE ${AppConstants.sessionsTable} (
|
||||
id TEXT PRIMARY KEY,
|
||||
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
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE ${AppConstants.shotsTable} (
|
||||
id TEXT PRIMARY KEY,
|
||||
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
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE INDEX idx_shots_session_id ON ${AppConstants.shotsTable}(session_id)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE INDEX idx_sessions_created_at ON ${AppConstants.sessionsTable}(created_at DESC)
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
|
||||
// Handle future database migrations here
|
||||
}
|
||||
|
||||
// Session operations
|
||||
Future<int> insertSession(Session session) async {
|
||||
final db = await database;
|
||||
return await db.transaction((txn) async {
|
||||
await txn.insert(
|
||||
AppConstants.sessionsTable,
|
||||
session.toMap(),
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
}
|
||||
|
||||
Future<Session?> getSession(String id) async {
|
||||
final db = await database;
|
||||
final sessionMaps = await db.query(
|
||||
AppConstants.sessionsTable,
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
|
||||
if (sessionMaps.isEmpty) return null;
|
||||
|
||||
final shotMaps = await db.query(
|
||||
AppConstants.shotsTable,
|
||||
where: 'session_id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
|
||||
final shots = shotMaps.map((map) => Shot.fromMap(map)).toList();
|
||||
return Session.fromMap(sessionMaps.first, shots);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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(
|
||||
AppConstants.sessionsTable,
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getStatistics() async {
|
||||
final db = await database;
|
||||
|
||||
final totalSessions = Sqflite.firstIntValue(
|
||||
await db.rawQuery('SELECT COUNT(*) FROM ${AppConstants.sessionsTable}'),
|
||||
) ?? 0;
|
||||
|
||||
final totalShots = Sqflite.firstIntValue(
|
||||
await db.rawQuery('SELECT COUNT(*) FROM ${AppConstants.shotsTable}'),
|
||||
) ?? 0;
|
||||
|
||||
final avgScore = (await db.rawQuery(
|
||||
'SELECT AVG(total_score) as avg FROM ${AppConstants.sessionsTable}',
|
||||
)).first['avg'] as double? ?? 0.0;
|
||||
|
||||
final bestScore = Sqflite.firstIntValue(
|
||||
await db.rawQuery(
|
||||
'SELECT MAX(total_score) FROM ${AppConstants.sessionsTable}',
|
||||
),
|
||||
) ?? 0;
|
||||
|
||||
return {
|
||||
'totalSessions': totalSessions,
|
||||
'totalShots': totalShots,
|
||||
'averageScore': avgScore,
|
||||
'bestScore': bestScore,
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
final db = await database;
|
||||
await db.close();
|
||||
_database = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user