Adatbázis új struktúra és könyvtár, projektrendszer bevezetése, ProjectService
This commit is contained in:
@@ -0,0 +1,367 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:terepi_seged/models/track.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import '../models/project.dart';
|
||||
|
||||
class AppDatabase {
|
||||
AppDatabase._();
|
||||
static final instance = AppDatabase._();
|
||||
static Database? _db;
|
||||
|
||||
Future<Database> get database async {
|
||||
_db ??= await _open();
|
||||
return _db!;
|
||||
}
|
||||
|
||||
Future<Database> _open() async {
|
||||
final oldDb = p.join(await getDatabasesPath(), 'terepi_seged.db');
|
||||
if (await File(oldDb).exists()) {
|
||||
await File(oldDb).delete();
|
||||
}
|
||||
|
||||
final directory = await getExternalStorageDirectory();
|
||||
final path = p.join(directory!.path, 'database', 'terepi_seged.db');
|
||||
|
||||
return openDatabase(path,
|
||||
version: 1, onCreate: _onCreate, onUpgrade: _onUpgrade);
|
||||
}
|
||||
|
||||
Future<void> _onCreate(Database db, int _) async {
|
||||
// ── Projects ────────────────────────────────────────────────────
|
||||
await db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
uuid TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
client TEXT NOT NULL DEFAULT '',
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
crs TEXT NOT NULL DEFAULT 'eov',
|
||||
color TEXT NOT NULL DEFAULT '#185FA5',
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
is_local_only INTEGER NOT NULL DEFAULT 1,
|
||||
last_synced_at TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
''');
|
||||
|
||||
// ── Bemért pontok ────────────────────────────────────────────────
|
||||
await db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS measured_points (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
eov_y REAL,
|
||||
eov_x REAL,
|
||||
eov_z REAL,
|
||||
latitude REAL,
|
||||
longitude REAL,
|
||||
altitude REAL,
|
||||
accuracy REAL,
|
||||
fix_quality INTEGER,
|
||||
timestamp TEXT NOT NULL,
|
||||
note TEXT NOT NULL DEFAULT ''
|
||||
)
|
||||
''');
|
||||
await db
|
||||
.execute('CREATE INDEX idx_mp_project ON measured_points(project_id)');
|
||||
|
||||
// ── Track-ek ─────────────────────────────────────────────────────
|
||||
await db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS tracks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
start_time TEXT NOT NULL,
|
||||
end_time TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'recording',
|
||||
source TEXT NOT NULL DEFAULT 'Telefon GPS',
|
||||
distance_m REAL NOT NULL DEFAULT 0,
|
||||
point_count INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
''');
|
||||
await db.execute('CREATE INDEX idx_tracks_project ON tracks(project_id)');
|
||||
|
||||
// ── Track pontok ─────────────────────────────────────────────────
|
||||
await db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS track_points (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
track_id INTEGER NOT NULL REFERENCES tracks(id) ON DELETE CASCADE,
|
||||
latitude REAL NOT NULL,
|
||||
longitude REAL NOT NULL,
|
||||
altitude REAL,
|
||||
accuracy REAL,
|
||||
speed REAL,
|
||||
heading REAL,
|
||||
timestamp TEXT NOT NULL
|
||||
)
|
||||
''');
|
||||
await db.execute(
|
||||
'CREATE INDEX idx_tp_track ON track_points(track_id, timestamp)');
|
||||
|
||||
// ── Terepbejárás elemek ──────────────────────────────────────────
|
||||
await db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS note_items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
type TEXT NOT NULL,
|
||||
points_json TEXT NOT NULL,
|
||||
color TEXT NOT NULL DEFAULT '#185FA5',
|
||||
opacity REAL NOT NULL DEFAULT 0.5,
|
||||
stroke_width REAL NOT NULL DEFAULT 3.0,
|
||||
stroke_color TEXT NOT NULL DEFAULT '#FFD700',
|
||||
label TEXT NOT NULL DEFAULT '',
|
||||
created_at TEXT NOT NULL
|
||||
)
|
||||
''');
|
||||
await db
|
||||
.execute('CREATE INDEX idx_notes_project ON note_items(project_id)');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS pending_points (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
point_number INTEGER NOT NULL,
|
||||
gnss_number TEXT,
|
||||
latitude REAL NOT NULL,
|
||||
longitude REAL NOT NULL,
|
||||
altitude REAL,
|
||||
height_of_geoid REAL,
|
||||
eov_x REAL,
|
||||
eov_y REAL,
|
||||
pole_height REAL,
|
||||
horizontal_error REAL,
|
||||
vertical_error REAL,
|
||||
description TEXT,
|
||||
is_deleted INTEGER NOT NULL DEFAULT 0,
|
||||
project_id INTEGER NOT NULL DEFAULT 2,
|
||||
created_at TEXT NOT NULL,
|
||||
sync_status TEXT NOT NULL DEFAULT 'pending'
|
||||
)
|
||||
''');
|
||||
await db.execute(
|
||||
'CREATE INDEX IF NOT EXISTS idx_pp_status '
|
||||
'ON pending_points(sync_status)',
|
||||
);
|
||||
|
||||
// Alap projekt létrehozása az első indításhoz
|
||||
final now = DateTime.now().toIso8601String();
|
||||
await db.insert('projects', {
|
||||
'uuid': const Uuid().v4(),
|
||||
'name': 'Alapértelmezett projekt',
|
||||
'created_at': now,
|
||||
'updated_at': now,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {}
|
||||
|
||||
// ── Projects CRUD ─────────────────────────────────────────────────
|
||||
|
||||
Future<int> insertProject(Project p) async =>
|
||||
(await database).insert('projects', p.toMap());
|
||||
|
||||
Future<void> updateProject(Project p) async => (await database)
|
||||
.update('projects', p.toMap(), where: 'id = ?', whereArgs: [p.id]);
|
||||
|
||||
Future<void> archiveProject(int id) async => (await database).update(
|
||||
'projects',
|
||||
{'status': 'archived', 'updated_at': DateTime.now().toIso8601String()},
|
||||
where: 'id = ?',
|
||||
whereArgs: [id]);
|
||||
|
||||
Future<List<Project>> listProjects({bool includeArchived = false}) async {
|
||||
final rows = await (await database).query(
|
||||
'projects',
|
||||
where: includeArchived ? null : "status = 'active'",
|
||||
orderBy: 'updated_at DESC',
|
||||
);
|
||||
return rows.map(Project.fromMap).toList();
|
||||
}
|
||||
|
||||
Future<Project?> getProject(int id) async {
|
||||
final rows = await (await database)
|
||||
.query('projects', where: 'id = ?', whereArgs: [id], limit: 1);
|
||||
return rows.isEmpty ? null : Project.fromMap(rows.first);
|
||||
}
|
||||
|
||||
// Projekt statisztikák — a listázáshoz
|
||||
Future<Map<String, int>> getProjectStats(int projectId) async {
|
||||
final db = await database;
|
||||
final points = Sqflite.firstIntValue(await db.rawQuery(
|
||||
'SELECT COUNT(*) FROM measured_points WHERE project_id = ?',
|
||||
[projectId])) ??
|
||||
0;
|
||||
final tracks = Sqflite.firstIntValue(await db.rawQuery(
|
||||
'SELECT COUNT(*) FROM tracks WHERE project_id = ?', [projectId])) ??
|
||||
0;
|
||||
final notes = Sqflite.firstIntValue(await db.rawQuery(
|
||||
'SELECT COUNT(*) FROM note_items WHERE project_id = ?',
|
||||
[projectId])) ??
|
||||
0;
|
||||
return {'points': points, 'tracks': tracks, 'notes': notes};
|
||||
}
|
||||
|
||||
Future<int> insertTrack(Track track) async {
|
||||
final db = await database;
|
||||
return db.insert('tracks', track.toMap());
|
||||
}
|
||||
|
||||
Future<void> updateTrack(Track track) async {
|
||||
final db = await database;
|
||||
await db.update(
|
||||
'tracks',
|
||||
track.toMap(),
|
||||
where: 'id = ?',
|
||||
whereArgs: [track.id],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> deleteTrack(int id) async {
|
||||
final db = await database;
|
||||
await db.delete('tracks', where: 'id = ?', whereArgs: [id]);
|
||||
}
|
||||
|
||||
Future<List<Track>> listTracks() async {
|
||||
final db = await database;
|
||||
final rows = await db.query('tracks', orderBy: 'start_time DESC');
|
||||
return rows.map(Track.fromMap).toList();
|
||||
}
|
||||
|
||||
Future<Track?> getTrack(int id) async {
|
||||
final db = await database;
|
||||
final rows = await db.query(
|
||||
'tracks',
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
limit: 1,
|
||||
);
|
||||
return rows.isEmpty ? null : Track.fromMap(rows.first);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// TRACK POINTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
Future<void> addPoint(TrackPoint point, double newDistance) async {
|
||||
final db = await database;
|
||||
await db.transaction((txn) async {
|
||||
await txn.insert('track_points', point.toMap());
|
||||
await txn.rawUpdate('''
|
||||
UPDATE tracks
|
||||
SET distance_meters = ?,
|
||||
point_count = point_count + 1
|
||||
WHERE id = ?
|
||||
''', [newDistance, point.trackId]);
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<TrackPoint>> getPoints(int trackId) async {
|
||||
final db = await database;
|
||||
final rows = await db.query(
|
||||
'track_points',
|
||||
where: 'track_id = ?',
|
||||
whereArgs: [trackId],
|
||||
orderBy: 'timestamp ASC',
|
||||
);
|
||||
return rows.map(TrackPoint.fromMap).toList();
|
||||
}
|
||||
|
||||
Future<List<({double lat, double lon})>> getLatLons(int trackId) async {
|
||||
final db = await database;
|
||||
final rows = await db.query(
|
||||
'track_points',
|
||||
columns: ['latitude', 'longitude'],
|
||||
where: 'track_id = ?',
|
||||
whereArgs: [trackId],
|
||||
orderBy: 'timestamp ASC',
|
||||
);
|
||||
return rows
|
||||
.map((r) => (
|
||||
lat: r['latitude'] as double,
|
||||
lon: r['longitude'] as double,
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// PENDING POINTS (szinkron queue)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
Future<int> insertPendingPoint(Map<String, dynamic> point) async {
|
||||
final db = await database;
|
||||
return db.insert('pending_points', {
|
||||
'point_number': point['pointNumber'],
|
||||
'gnss_number': point['gnssNumber'],
|
||||
'latitude': point['latitude'],
|
||||
'longitude': point['longitude'],
|
||||
'altitude': point['altitude'],
|
||||
'height_of_geoid': point['heightOfGeoid'],
|
||||
'eov_x': point['eovX'],
|
||||
'eov_y': point['eovY'],
|
||||
'pole_height': point['poleHeight'],
|
||||
'horizontal_error': point['horizontalError'],
|
||||
'vertical_error': point['verticalError'],
|
||||
'description': point['description'],
|
||||
'is_deleted': (point['isDeleted'] == true) ? 1 : 0,
|
||||
'project_id': point['projectId'] ?? 2,
|
||||
'created_at': DateTime.now().toIso8601String(),
|
||||
'sync_status': 'pending',
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getPendingPoints() async {
|
||||
final db = await database;
|
||||
// JOIN a projects táblával — lokális projektek kiszűrve
|
||||
return db.rawQuery('''
|
||||
SELECT pp.*
|
||||
FROM pending_points pp
|
||||
LEFT JOIN projects pr ON pr.id = pp.project_id
|
||||
WHERE pp.sync_status = 'pending'
|
||||
AND (pr.is_local_only = 0 OR pr.is_local_only IS NULL)
|
||||
ORDER BY pp.id ASC
|
||||
''');
|
||||
}
|
||||
|
||||
Future<int> getPendingCount() async {
|
||||
final db = await database;
|
||||
final result = await db.rawQuery(
|
||||
"SELECT COUNT(*) AS cnt FROM pending_points "
|
||||
"WHERE sync_status = 'pending'",
|
||||
);
|
||||
return (result.first['cnt'] as int?) ?? 0;
|
||||
}
|
||||
|
||||
Future<void> markPointSynced(int id) async {
|
||||
final db = await database;
|
||||
await db.update(
|
||||
'pending_points',
|
||||
{'sync_status': 'synced'},
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> markPointError(int id) async {
|
||||
final db = await database;
|
||||
await db.update(
|
||||
'pending_points',
|
||||
{'sync_status': 'error'},
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
}
|
||||
|
||||
/// Sikeresen szinkronizált pontok törlése (takarítás)
|
||||
Future<void> purgeSyncedPoints() async {
|
||||
final db = await database;
|
||||
await db.delete(
|
||||
'pending_points',
|
||||
where: 'sync_status = ?',
|
||||
whereArgs: ['synced'],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user