168 lines
4.8 KiB
Dart
168 lines
4.8 KiB
Dart
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<Database> 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<void> setActiveSession(String sessionId) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(_prefsActiveSessionKey, sessionId);
|
|
}
|
|
|
|
static Future<String?> getActiveSession() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
return prefs.getString(_prefsActiveSessionKey);
|
|
}
|
|
|
|
static Future<void> clearActiveSession() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.remove(_prefsActiveSessionKey);
|
|
}
|
|
|
|
// ---------------- sessions ----------------
|
|
|
|
static Future<void> 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<Map<String, dynamic>?> 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<void> 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<List<Map<String, dynamic>>> getRecentSessions(
|
|
{int limit = 20}) async {
|
|
final db = await _database;
|
|
return db.query(
|
|
'attendance_sessions',
|
|
orderBy: 'punch_in DESC',
|
|
limit: limit,
|
|
);
|
|
}
|
|
|
|
// ---------------- points ----------------
|
|
|
|
static Future<void> 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<List<Map<String, dynamic>>> getPoints(
|
|
String sessionId) async {
|
|
final db = await _database;
|
|
return db.query(
|
|
'attendance_points',
|
|
where: 'session_id = ?',
|
|
whereArgs: [sessionId],
|
|
orderBy: 'timestamp ASC',
|
|
);
|
|
}
|
|
|
|
static Future<int> 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;
|
|
}
|
|
}
|