import 'package:path/path.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:sqflite/sqflite.dart'; // --------------------------------------------------------------------------- // Phone-only storage for attendance (no backend yet). // // - SharedPreferences keeps ONE value: the currently open session id // (so UI, background service and reboot recovery all agree). // - sqflite keeps every session + every GPS dot. // // NOTE: the database handle is opened per-isolate (statics are per-isolate), // so the background service gets its own safe handle. // --------------------------------------------------------------------------- class AttendanceStorage { static const String _dbName = 'attendance.db'; static const String _prefsActiveSessionKey = 'attendance_active_session'; static Database? _db; static Future get _database async { if (_db != null) return _db!; final String path = join(await getDatabasesPath(), _dbName); _db = await openDatabase( path, version: 1, onCreate: (Database db, int version) async { await db.execute(''' CREATE TABLE attendance_sessions( session_id TEXT PRIMARY KEY, punch_in TEXT NOT NULL, punch_out TEXT, status TEXT NOT NULL, total_km_haversine REAL DEFAULT 0 ) '''); await db.execute(''' CREATE TABLE attendance_points( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, lat REAL NOT NULL, lng REAL NOT NULL, accuracy REAL NOT NULL, is_mocked INTEGER NOT NULL DEFAULT 0, timestamp TEXT NOT NULL ) '''); await db.execute(''' CREATE INDEX idx_points_session ON attendance_points(session_id) '''); }, ); return _db!; } // ---------------- active session (SharedPreferences) ---------------- static Future setActiveSession(String sessionId) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_prefsActiveSessionKey, sessionId); } static Future getActiveSession() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString(_prefsActiveSessionKey); } static Future clearActiveSession() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove(_prefsActiveSessionKey); } // ---------------- sessions ---------------- static Future createSession(String sessionId, DateTime punchIn) async { final db = await _database; await db.insert('attendance_sessions', { 'session_id': sessionId, 'punch_in': punchIn.toIso8601String(), 'punch_out': null, 'status': 'OPEN', 'total_km_haversine': 0, }); } static Future?> getSession(String sessionId) async { final db = await _database; final rows = await db.query( 'attendance_sessions', where: 'session_id = ?', whereArgs: [sessionId], limit: 1, ); if (rows.isEmpty) return null; return rows.first; } static Future closeSession( String sessionId, DateTime punchOut, double totalKmHaversine, ) async { final db = await _database; await db.update( 'attendance_sessions', { 'punch_out': punchOut.toIso8601String(), 'status': 'CLOSED', 'total_km_haversine': totalKmHaversine, }, where: 'session_id = ?', whereArgs: [sessionId], ); } static Future>> getRecentSessions( {int limit = 20}) async { final db = await _database; return db.query( 'attendance_sessions', orderBy: 'punch_in DESC', limit: limit, ); } // ---------------- points ---------------- static Future insertPoint({ required String sessionId, required double lat, required double lng, required double accuracy, required bool isMocked, required DateTime timestamp, }) async { final db = await _database; await db.insert('attendance_points', { 'session_id': sessionId, 'lat': lat, 'lng': lng, 'accuracy': accuracy, 'is_mocked': isMocked ? 1 : 0, 'timestamp': timestamp.toIso8601String(), }); } static Future>> getPoints( String sessionId) async { final db = await _database; return db.query( 'attendance_points', where: 'session_id = ?', whereArgs: [sessionId], orderBy: 'timestamp ASC', ); } static Future countPoints(String sessionId) async { final db = await _database; final rows = await db.rawQuery( 'SELECT COUNT(*) AS c FROM attendance_points WHERE session_id = ?', [sessionId], ); return (rows.first['c'] as int?) ?? 0; } }