import 'dart:io'; import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; import '../models/palm_record.dart'; import '../models/palm_correction.dart'; class DatabaseHelper { static final DatabaseHelper _instance = DatabaseHelper._internal(); factory DatabaseHelper() => _instance; DatabaseHelper._internal(); static Database? _database; Future get database async { if (_database != null) return _database!; _database = await _initDatabase(); return _database!; } Future _initDatabase() async { String path = join(await getDatabasesPath(), 'palm_oil_history.db'); return await openDatabase( path, version: 4, onCreate: _onCreate, onUpgrade: _onUpgrade, ); } Future _onCreate(Database db, int version) async { await db.execute(''' CREATE TABLE history ( id INTEGER PRIMARY KEY AUTOINCREMENT, image_path TEXT, ripeness_class TEXT, confidence REAL, timestamp TEXT, x1 REAL, y1 REAL, x2 REAL, y2 REAL, detections TEXT, batch_id TEXT, inference_ms INTEGER ) '''); await db.execute('CREATE INDEX idx_history_batch_id ON history(batch_id)'); await _createCorrectionsTable(db); } Future _createCorrectionsTable(Database db) async { await db.execute(''' CREATE TABLE corrections ( id INTEGER PRIMARY KEY AUTOINCREMENT, original_record_id INTEGER, corrected_class TEXT, corrected_detections TEXT, timestamp TEXT ) '''); await db.execute('CREATE INDEX idx_corrections_original_record_id ON corrections(original_record_id)'); } Future _onUpgrade(Database db, int oldVersion, int newVersion) async { if (oldVersion < 2) { await db.execute('ALTER TABLE history ADD COLUMN detections TEXT'); } if (oldVersion < 3) { await db.execute('ALTER TABLE history ADD COLUMN batch_id TEXT'); await db.execute('ALTER TABLE history ADD COLUMN inference_ms INTEGER'); await db.execute('CREATE INDEX idx_history_batch_id ON history(batch_id)'); } if (oldVersion < 4) { await _createCorrectionsTable(db); } } Future insertRecord(PalmRecord record) async { Database db = await database; return await db.insert('history', record.toMap()); } Future> getAllRecords() async { Database db = await database; List> maps = await db.query('history', orderBy: 'timestamp DESC'); return List.generate(maps.length, (i) { return PalmRecord.fromMap(maps[i]); }); } Future> getRecordsByBatch(String batchId) async { Database db = await database; List> maps = await db.query( 'history', where: 'batch_id = ?', whereArgs: [batchId], orderBy: 'timestamp ASC', ); return List.generate(maps.length, (i) { return PalmRecord.fromMap(maps[i]); }); } Future deleteRecord(PalmRecord record) async { Database db = await database; await _deleteImageFile(record.imagePath); return await db.delete('history', where: 'id = ?', whereArgs: [record.id]); } Future deleteBatch(String batchId) async { Database db = await database; final records = await getRecordsByBatch(batchId); for (final record in records) { await _deleteImageFile(record.imagePath); } return await db.delete('history', where: 'batch_id = ?', whereArgs: [batchId]); } Future clearAllRecords() async { Database db = await database; final records = await getAllRecords(); for (final record in records) { await _deleteImageFile(record.imagePath); } return await db.delete('history'); } Future insertCorrection(PalmCorrection correction) async { Database db = await database; return await db.insert('corrections', correction.toMap()); } Future deleteCorrectionsForRecord(int recordId) async { Database db = await database; return await db.delete('corrections', where: 'original_record_id = ?', whereArgs: [recordId]); } Future getLatestCorrection(int recordId) async { Database db = await database; List> maps = await db.query( 'corrections', where: 'original_record_id = ?', whereArgs: [recordId], orderBy: 'timestamp DESC', limit: 1, ); if (maps.isEmpty) return null; return PalmCorrection.fromMap(maps.first); } Future _deleteImageFile(String imagePath) async { try { final file = File(imagePath); if (await file.exists()) { await file.delete(); } } catch (_) { // A missing/locked file shouldn't block the DB delete. } } }