Track online szinkronizáció refraktorálása

This commit is contained in:
2026-07-08 23:29:22 +02:00
parent 1ab656585b
commit d23f736ead
7 changed files with 131 additions and 154 deletions
+6
View File
@@ -61,6 +61,7 @@ enum TrackSyncStatus { pending, synced, error }
class Track { class Track {
final int? id; final int? id;
final String? uuid; // kliens-generált uuid — ez a szerveroldali id is
final int? projectId; final int? projectId;
final String name; final String name;
final DateTime startTime; final DateTime startTime;
@@ -77,6 +78,7 @@ class Track {
const Track( const Track(
{this.id, {this.id,
this.uuid,
this.projectId, this.projectId,
required this.name, required this.name,
required this.startTime, required this.startTime,
@@ -91,6 +93,7 @@ class Track {
Track copyWith( Track copyWith(
{int? id, {int? id,
String? uuid,
int? projectId, int? projectId,
String? name, String? name,
DateTime? startTime, DateTime? startTime,
@@ -104,6 +107,7 @@ class Track {
String? supabaseId}) => String? supabaseId}) =>
Track( Track(
id: id ?? this.id, id: id ?? this.id,
uuid: uuid ?? this.uuid,
projectId: projectId ?? this.projectId, projectId: projectId ?? this.projectId,
name: name ?? this.name, name: name ?? this.name,
startTime: startTime ?? this.startTime, startTime: startTime ?? this.startTime,
@@ -136,6 +140,7 @@ class Track {
Map<String, dynamic> toMap() => { Map<String, dynamic> toMap() => {
if (id != null) 'id': id, if (id != null) 'id': id,
if (uuid != null) 'uuid': uuid,
if (projectId != null) 'project_id': projectId, if (projectId != null) 'project_id': projectId,
'name': name, 'name': name,
'start_time': startTime.toIso8601String(), 'start_time': startTime.toIso8601String(),
@@ -151,6 +156,7 @@ class Track {
factory Track.fromMap(Map<String, dynamic> m) => Track( factory Track.fromMap(Map<String, dynamic> m) => Track(
id: m['id'] as int?, id: m['id'] as int?,
uuid: m['uuid'] as String?,
projectId: m['project_id'] as int?, projectId: m['project_id'] as int?,
name: m['name'] as String, name: m['name'] as String,
startTime: DateTime.parse(m['start_time'] as String), startTime: DateTime.parse(m['start_time'] as String),
@@ -102,6 +102,16 @@ class TrackingController extends GetxController {
super.onInit(); super.onInit();
_initForegroundTask(); _initForegroundTask();
loadSavedTracks(); loadSavedTracks();
// A TrackingController permanent, egyszer indul az app életciklusában —
// enélkül projektváltáskor a lista a réginél ragadna.
ever(ProjectService.to.activeProject, (_) {
loadSavedTracks();
// Ne maradjon a térképen egy MÁSIK projekt track-overlaya.
overlayTrackIds.clear();
_trackCoords.clear();
});
} }
void _initForegroundTask() { void _initForegroundTask() {
@@ -130,11 +140,18 @@ class TrackingController extends GetxController {
/// Elindítja a rögzítést a megadott forrással (alapértelmezett: telefon GPS). /// Elindítja a rögzítést a megadott forrással (alapértelmezett: telefon GPS).
Future<void> startRecording( Future<void> startRecording(
{LocationSource? source, {LocationSource? source, String? name, TrackSyncMode? syncMode}) async {
String? name,
TrackSyncMode syncMode = TrackSyncMode.online}) async {
if (isRecording.value) return; if (isRecording.value) return;
// Alapértelmezés: a track kövesse az AKTÍV PROJEKT online/lokális
// állapotát — eddig ez mindig 'online' volt, függetlenül a projekt
// beállításától. Explicit paraméterrel felülírható.
final activeProject = ProjectService.to.activeProject.value;
final effectiveSyncMode = syncMode ??
(activeProject?.isLocalOnly ?? true
? TrackSyncMode.localOnly
: TrackSyncMode.online);
_source = source ?? PhoneGpsSource(intervalMs: 1000, distanceFilter: 2.0); _source = source ?? PhoneGpsSource(intervalMs: 1000, distanceFilter: 2.0);
// Track létrehozása az adatbázisban // Track létrehozása az adatbázisban
@@ -144,8 +161,8 @@ class TrackingController extends GetxController {
name: trackName, name: trackName,
startTime: now, startTime: now,
source: _source!.displayName, source: _source!.displayName,
projectId: ProjectService.to.activeProjectId, projectId: activeProject?.id,
syncMode: syncMode)); syncMode: effectiveSyncMode));
currentTrack.value = await _db.getTrack(trackId); currentTrack.value = await _db.getTrack(trackId);
// Állapot reset // Állapot reset
@@ -162,20 +179,11 @@ class TrackingController extends GetxController {
callback: startTrackingCallback, callback: startTrackingCallback,
); );
if (!(currentTrack.value?.isLocalOnly ?? true)) { // A TrackSyncService.startSession() maga dönti el (a track
TrackSyncService.to // isLocalOnly-ja alapján), hogy indítja-e a szinkron-időzítőket —
.createRemoteTrack(currentTrack.value!) // nincs többé külön "előbb hozzuk létre szerveroldalon" lépés, mert
.then((supabaseId) async { // az egységes (uuid-alapú) push maga hozza létre a sort a szerveren.
if (supabaseId == null) return; TrackSyncService.to.startSession(currentTrack.value!);
final updated = currentTrack.value!.copyWith(supabaseId: supabaseId);
await _db.updateTrack(updated);
currentTrack.value = updated;
TrackSyncService.to.startSession(updated);
});
} else {
TrackSyncService.to.startSession(currentTrack.value!);
}
// GPS stream feliratkozás // GPS stream feliratkozás
_positionSub = _source!.positionStream.listen( _positionSub = _source!.positionStream.listen(
_onPosition, _onPosition,
@@ -260,7 +268,8 @@ class TrackingController extends GetxController {
} }
Future<void> loadSavedTracks() async { Future<void> loadSavedTracks() async {
savedTracks.value = await _db.listTracks(); savedTracks.value =
await _db.listTracks(projectId: ProjectService.to.activeProjectId);
} }
Future<void> deleteTrack(int id) async { Future<void> deleteTrack(int id) async {
+37 -5
View File
@@ -57,6 +57,14 @@ class AppDatabase {
Future<void> upgradeSchema(Database db, int oldVersion, int newVersion) => Future<void> upgradeSchema(Database db, int oldVersion, int newVersion) =>
_onUpgrade(db, oldVersion, newVersion); _onUpgrade(db, oldVersion, newVersion);
Future<void> _tryExec(Database db, String sql) async {
try {
await db.execute(sql);
} on DatabaseException catch (e) {
if (!e.toString().contains('duplicate column')) rethrow;
}
}
Future<void> _onCreate(Database db, int _) async { Future<void> _onCreate(Database db, int _) async {
// ── Projects ──────────────────────────────────────────────────── // ── Projects ────────────────────────────────────────────────────
// uuid: a projekt globális azonosítója. Közös projekthez csatlakozáskor // uuid: a projekt globális azonosítója. Közös projekthez csatlakozáskor
@@ -261,6 +269,7 @@ class AppDatabase {
await _createStakeoutTable(db); await _createStakeoutTable(db);
await _createContactsOutbox(db); await _createContactsOutbox(db);
await _addAppInstanceIdColumns(db);
// Alap projekt létrehozása az első indításhoz // Alap projekt létrehozása az első indításhoz
final now = DateTime.now().toIso8601String(); final now = DateTime.now().toIso8601String();
@@ -296,6 +305,8 @@ class AppDatabase {
if (oldVersion < 5) { if (oldVersion < 5) {
_createContactsOutbox(db); _createContactsOutbox(db);
} }
await _addAppInstanceIdColumns(db);
} }
Future<void> _migrateToV4(Database db) async { Future<void> _migrateToV4(Database db) async {
@@ -499,6 +510,7 @@ class AppDatabase {
DeviceIdentityService.to.isReady DeviceIdentityService.to.isReady
? DeviceIdentityService.to.appInstanceId ? DeviceIdentityService.to.appInstanceId
: null; : null;
map['app_instance_id'] ??= DeviceIdentityService.to.appInstanceId;
return db.insert('tracks', map); return db.insert('tracks', map);
} }
@@ -524,10 +536,14 @@ class AppDatabase {
); );
} }
Future<List<Track>> listTracks() async { Future<List<Track>> listTracks({int? projectId}) async {
final db = await database; final db = await database;
final rows = await db.query('tracks', final rows = await db.query('tracks',
where: 'deleted_at IS NULL', orderBy: 'start_time DESC'); where: projectId != null
? 'deleted_at IS NULL AND project_id = ?'
: 'deleted_at IS NULL',
whereArgs: projectId != null ? [projectId] : null,
orderBy: 'start_time DESC');
return rows.map(Track.fromMap).toList(); return rows.map(Track.fromMap).toList();
} }
@@ -554,9 +570,11 @@ class AppDatabase {
await txn.rawUpdate(''' await txn.rawUpdate('''
UPDATE tracks UPDATE tracks
SET distance_m = ?, SET distance_m = ?,
point_count = point_count + 1 point_count = point_count + 1,
updated_at = ?,
sync_status = 'pending'
WHERE id = ? WHERE id = ?
''', [newDistance, point.trackId]); ''', [newDistance, DateTime.now().toIso8601String(), point.trackId]);
}); });
} catch (e) { } catch (e) {
print( print(
@@ -879,6 +897,8 @@ class AppDatabase {
DeviceIdentityService.to.isReady DeviceIdentityService.to.isReady
? DeviceIdentityService.to.appInstanceId ? DeviceIdentityService.to.appInstanceId
: null; : null;
map['app_instance_id'] ??= DeviceIdentityService.to.appInstanceId;
return db.insert('measured_points', map); return db.insert('measured_points', map);
} }
@@ -974,7 +994,7 @@ class AppDatabase {
DeviceIdentityService.to.isReady DeviceIdentityService.to.isReady
? DeviceIdentityService.to.appInstanceId ? DeviceIdentityService.to.appInstanceId
: null; : null;
; map['app_instance_id'] ??= DeviceIdentityService.to.appInstanceId;
return db.insert('stakeout_points', map); return db.insert('stakeout_points', map);
} }
@@ -998,6 +1018,8 @@ class AppDatabase {
DeviceIdentityService.to.isReady DeviceIdentityService.to.isReady
? DeviceIdentityService.to.appInstanceId ? DeviceIdentityService.to.appInstanceId
: null; : null;
map['app_instance_id'] ??= DeviceIdentityService.to.appInstanceId;
await txn.insert('stakeout_points', map); await txn.insert('stakeout_points', map);
inserted++; inserted++;
} }
@@ -1353,6 +1375,16 @@ class AppDatabase {
0; 0;
} }
/// A device_id (ANDROID_ID, TEXT) MARAD — helyes, mert az oszlop nem
/// uuid típusú. Az app_instance_id egy KIEGÉSZÍTŐ azonosító: az adott
/// app-telepítéshez tartozó, saját generált uuid — mindkettő tárolva,
/// hogy az "eszköz" és a "konkrét app-példány" is visszakereshető legyen.
Future<void> _addAppInstanceIdColumns(Database db) async {
for (final table in ['tracks', 'measured_points', 'stakeout_points']) {
await _tryExec(db, 'ALTER TABLE $table ADD COLUMN app_instance_id TEXT');
}
}
// Future<void> testOnly() async { // Future<void> testOnly() async {
// final db = await database; // final db = await database;
// await db.execute( // await db.execute(
+2
View File
@@ -94,6 +94,7 @@ class StakeoutSyncService extends GetxService {
'offset_dist': r['offset_dist'], 'offset_dist': r['offset_dist'],
'offset_bearing': r['offset_bearing'], 'offset_bearing': r['offset_bearing'],
'device_id': r['device_id'], 'device_id': r['device_id'],
'app_instance_id': r['app_instance_id'],
'deleted_at': _toUtc(r['deleted_at'] as String?), 'deleted_at': _toUtc(r['deleted_at'] as String?),
}) })
.toList(); .toList();
@@ -164,6 +165,7 @@ class StakeoutSyncService extends GetxService {
'offset_bearing': r['offset_bearing'], 'offset_bearing': r['offset_bearing'],
'created_by': r['created_by'], 'created_by': r['created_by'],
'device_id': r['device_id'], 'device_id': r['device_id'],
'app_instance_id': r['app_instance_id'],
'created_at': r['created_at'] ?? DateTime.now().toIso8601String(), 'created_at': r['created_at'] ?? DateTime.now().toIso8601String(),
'updated_at': r['updated_at'], 'updated_at': r['updated_at'],
'deleted_at': r['deleted_at'], 'deleted_at': r['deleted_at'],
+2 -1
View File
@@ -11,7 +11,8 @@ class TrackDatabase {
Future<int> insertTrack(Track t) => AppDatabase.instance.insertTrack(t); Future<int> insertTrack(Track t) => AppDatabase.instance.insertTrack(t);
Future<void> updateTrack(Track t) => AppDatabase.instance.updateTrack(t); Future<void> updateTrack(Track t) => AppDatabase.instance.updateTrack(t);
Future<void> deleteTrack(int id) => AppDatabase.instance.deleteTrack(id); Future<void> deleteTrack(int id) => AppDatabase.instance.deleteTrack(id);
Future<List<Track>> listTracks() => AppDatabase.instance.listTracks(); Future<List<Track>> listTracks({int? projectId}) =>
AppDatabase.instance.listTracks(projectId: projectId);
Future<Track?> getTrack(int id) => AppDatabase.instance.getTrack(id); Future<Track?> getTrack(int id) => AppDatabase.instance.getTrack(id);
Future<void> addPoint(TrackPoint p, double d) => Future<void> addPoint(TrackPoint p, double d) =>
AppDatabase.instance.addPoint(p, d); AppDatabase.instance.addPoint(p, d);
+47 -124
View File
@@ -1,6 +1,11 @@
// Kétszintű Supabase szinkronizáció: // EGYETLEN track-szinkron útvonal:
// 1. Élő pozíció — minden 3 mp-ben UPSERT → device_positions // 1. Élő pozíció — minden 3 mp-ben UPSERT → terepi_seged_device_positions
// 2. Track pontok — batch INSERT (10 pont vagy 8 mp) → terepi_track_points // (ez a "hol van most ez az eszköz" jelenlét, független a track-adattól)
// 2. Track fejléc + pontok — a TsSyncService KÖZÖS, uuid-alapú, idempotens
// push-függvényeit hívja, csak SŰRŰBBEN (10 pont vagy 8 mp), amíg fut
// a rögzítés. Nincs külön insert/upsert-kód és külön szerveroldali
// azonosító — pontosan ugyanaz fut, mint amit a háttér-szinkron
// (TsSyncService) 3 percenként amúgy is lefuttatna.
import 'dart:async'; import 'dart:async';
@@ -9,7 +14,7 @@ import 'package:get/get.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:supabase_flutter/supabase_flutter.dart';
import 'app_database.dart'; import 'ts_sync_service.dart';
import 'device_identity_service.dart'; import 'device_identity_service.dart';
import '../models/track.dart'; import '../models/track.dart';
@@ -24,8 +29,8 @@ class TrackSyncService extends GetxService {
static const _positionIntervalSec = 3; static const _positionIntervalSec = 3;
// ── Belső állapot ───────────────────────────────────────────────── // ── Belső állapot ─────────────────────────────────────────────────
final _buffer = <TrackPoint>[]; int _pointsSinceLastPush = 0;
Timer? _batchTimer; Timer? _pushTimer;
Timer? _posTimer; Timer? _posTimer;
Track? _track; Track? _track;
@@ -34,7 +39,6 @@ class TrackSyncService extends GetxService {
// ── Publikus állapot ────────────────────────────────────────────── // ── Publikus állapot ──────────────────────────────────────────────
final isSyncing = false.obs; final isSyncing = false.obs;
final pendingCount = 0.obs;
// ── Lifecycle ───────────────────────────────────────────────────── // ── Lifecycle ─────────────────────────────────────────────────────
@@ -55,14 +59,17 @@ class TrackSyncService extends GetxService {
void startSession(Track track) { void startSession(Track track) {
_track = track; _track = track;
_buffer.clear(); _pointsSinceLastPush = 0;
pendingCount.value = 0;
if (track.isLocalOnly) return; if (track.isLocalOnly) return;
_batchTimer = Timer.periodic( // Azonnali első push — ne kelljen az első 8 mp-et kivárni ahhoz, hogy
// a track fejléce megjelenjen a szerveren más eszközök számára.
unawaited(_pushTrackData());
_pushTimer = Timer.periodic(
const Duration(seconds: _batchIntervalSec), const Duration(seconds: _batchIntervalSec),
(_) => _flush(), (_) => _pushTrackData(),
); );
_posTimer = Timer.periodic( _posTimer = Timer.periodic(
const Duration(seconds: _positionIntervalSec), const Duration(seconds: _positionIntervalSec),
@@ -72,20 +79,12 @@ class TrackSyncService extends GetxService {
Future<void> stopSession(Track track) async { Future<void> stopSession(Track track) async {
_stopTimers(); _stopTimers();
if (track.isLocalOnly) return; if (!track.isLocalOnly) {
// Záró push: a lezárt állapotot (vég idő, státusz, végleges táv/
await _flush(); // utolsó batch // pontszám) a TrackingController.stopRecording() már beírta
// helyileg 'pending' státusszal — ez küldi fel + az utolsó pontokat.
// Track fejléc lezárása Supabase-ben await _pushTrackData();
if (track.supabaseId != null) {
await _supabase.from('terepi_seged_tracks').update({
'end_time': track.endTime?.toIso8601String(),
'status': 'finished',
'distance_m': track.distanceMeters,
'point_count': track.pointCount,
}).eq('id', track.supabaseId!);
} }
await _setInactive(); await _setInactive();
_track = null; _track = null;
_lastPos = null; _lastPos = null;
@@ -93,79 +92,42 @@ class TrackSyncService extends GetxService {
// ── Pont pufferelés ─────────────────────────────────────────────── // ── Pont pufferelés ───────────────────────────────────────────────
/// TrackingController._onPosition() hívja minden pontnál /// TrackingController._onPosition() hívja minden pontnál. A pont már
/// ekkor a LOKÁLIS adatbázisban van (a controller írja be) — itt csak
/// azt számoljuk, mikor induljon köztes push a 8 mp-es időzítő előtt.
void onNewPoint(TrackPoint point) { void onNewPoint(TrackPoint point) {
_lastPos = LatLng(point.latitude, point.longitude); _lastPos = LatLng(point.latitude, point.longitude);
if (_track == null || _track!.isLocalOnly) return; if (_track == null || _track!.isLocalOnly) return;
_buffer.add(point); _pointsSinceLastPush++;
pendingCount.value = _buffer.length; if (_pointsSinceLastPush >= _batchSize) _pushTrackData();
if (_buffer.length >= _batchSize) _flush();
} }
// ── Supabase track létrehozása ──────────────────────────────────── // ── Push a KÖZÖS, uuid-alapú TsSyncService-függvényekkel ──────────
// Nincs saját insert/upsert-kód, nincs külön szerveroldali azonosító —
/// startRecording()-ban hívandó, visszaadja a Supabase UUID-t // pontosan az fut, amit a háttér-szinkron is futtatna, csak sűrűbben.
Future<String?> createRemoteTrack(Track track) async {
if (!_online) return null;
try {
final res = await _supabase
.from('terepi_seged_tracks')
.insert({
'device_id': DeviceIdentityService.to.deviceId,
'name': track.name,
'source': track.source,
'start_time': track.startTime.toIso8601String(),
'status': 'recording',
})
.select('id')
.single();
return res['id'] as String?;
} catch (e) {
return null;
}
}
// ── Batch feltöltés ───────────────────────────────────────────────
Future<void> _flush() async {
if (_buffer.isEmpty || !_online) return;
final supabaseId = _track?.supabaseId;
if (supabaseId == null) return;
final batch = List<TrackPoint>.from(_buffer);
_buffer.clear();
pendingCount.value = 0;
Future<void> _pushTrackData() async {
if (!_online) return;
_pointsSinceLastPush = 0;
try { try {
isSyncing.value = true; isSyncing.value = true;
await _supabase.from('terepi_seged_track_points').insert( // Fejléc ELŐBB (FK: track_points.track_id → tracks.id), utána a
batch // pontok — ugyanaz a sorrend, mint a háttér-szinkron ciklusában.
.map((p) => { await TsSyncService.to.pushTracks();
'track_id': supabaseId, await TsSyncService.to.pushTrackPoints();
'latitude': p.latitude,
'longitude': p.longitude,
'altitude': p.altitude,
'accuracy': p.accuracy,
'speed': p.speed,
'heading': p.heading,
'timestamp': p.timestamp.toIso8601String(),
})
.toList(),
);
} catch (_) { } catch (_) {
// Hiba → visszateszi a bufferbe // Hiba esetén a sorok 'pending' állapotban maradnak — a
_buffer.insertAll(0, batch); // következő ciklus (vagy a háttér-szinkron) újrapróbálja.
pendingCount.value = _buffer.length;
} finally { } finally {
isSyncing.value = false; isSyncing.value = false;
} }
} }
// ── Élő pozíció broadcast ───────────────────────────────────────── // ── Élő pozíció broadcast ─────────────────────────────────────────
// Ez a "hol van most ez az eszköz" jelenlét — nem track-adat, ezért
// marad önálló, egyszerű upsert.
Future<void> _broadcastPosition() async { Future<void> _broadcastPosition() async {
final pos = _lastPos; final pos = _lastPos;
@@ -175,10 +137,11 @@ class TrackSyncService extends GetxService {
try { try {
await _supabase.from('terepi_seged_device_positions').upsert({ await _supabase.from('terepi_seged_device_positions').upsert({
'device_id': device.deviceId, 'device_id': device.deviceId,
'app_instance_id': device.appInstanceId,
'user_name': device.deviceLabel.value, 'user_name': device.deviceLabel.value,
'latitude': pos.latitude, 'latitude': pos.latitude,
'longitude': pos.longitude, 'longitude': pos.longitude,
'track_id': _track?.supabaseId, 'track_id': _track?.uuid,
'is_active': true, 'is_active': true,
'updated_at': DateTime.now().toUtc().toIso8601String(), 'updated_at': DateTime.now().toUtc().toIso8601String(),
}, onConflict: 'device_id'); }, onConflict: 'device_id');
@@ -193,53 +156,13 @@ class TrackSyncService extends GetxService {
} catch (_) {} } catch (_) {}
} }
// ── Offline → Online szinkron ─────────────────────────────────────
Future<void> syncTrack(Track track) async {
if (!_online) return;
String? supabaseId = track.supabaseId;
if (supabaseId == null) {
supabaseId = await createRemoteTrack(track);
if (supabaseId == null) return;
final updated = track.copyWith(supabaseId: supabaseId);
await AppDatabase.instance.updateTrack(updated);
}
final points = await AppDatabase.instance.getPoints(track.id!);
if (points.isEmpty) return;
const chunk = 100;
for (int i = 0; i < points.length; i += chunk) {
final slice = points.sublist(i, (i + chunk).clamp(0, points.length));
await _supabase.from('terepi_seged_track_points').insert(
slice
.map((p) => {
'track_id': supabaseId,
'latitude': p.latitude,
'longitude': p.longitude,
'altitude': p.altitude,
'accuracy': p.accuracy,
'speed': p.speed,
'heading': p.heading,
'timestamp': p.timestamp.toIso8601String(),
})
.toList(),
);
}
await AppDatabase.instance.updateTrack(
track.copyWith(supabaseId: supabaseId),
);
}
// ── Kapcsolat figyelés ──────────────────────────────────────────── // ── Kapcsolat figyelés ────────────────────────────────────────────
void _listenConnectivity() { void _listenConnectivity() {
Connectivity().onConnectivityChanged.listen((results) async { Connectivity().onConnectivityChanged.listen((results) async {
final wasOffline = !_online; final wasOffline = !_online;
_online = results.any((r) => r != ConnectivityResult.none); _online = results.any((r) => r != ConnectivityResult.none);
if (wasOffline && _online) await _flush(); if (wasOffline && _online) await _pushTrackData();
}); });
} }
@@ -249,9 +172,9 @@ class TrackSyncService extends GetxService {
} }
void _stopTimers() { void _stopTimers() {
_batchTimer?.cancel(); _pushTimer?.cancel();
_posTimer?.cancel(); _posTimer?.cancel();
_batchTimer = null; _pushTimer = null;
_posTimer = null; _posTimer = null;
} }
} }
+8 -4
View File
@@ -141,8 +141,8 @@ class TsSyncService extends GetxService {
Future<void> _push() async { Future<void> _push() async {
await _pushProjects(); await _pushProjects();
await _pushMeasuredPoints(); await _pushMeasuredPoints();
await _pushTracks(); await pushTracks();
await _pushTrackPoints(); await pushTrackPoints();
await _pushNoteItems(); await _pushNoteItems();
} }
@@ -188,6 +188,7 @@ class TsSyncService extends GetxService {
'measured_at': _toUtc(r['timestamp'] as String?), 'measured_at': _toUtc(r['timestamp'] as String?),
'note': r['note'], 'note': r['note'],
'device_id': r['device_id'], 'device_id': r['device_id'],
'app_instance_id': r['app_instance_id'],
'deleted_at': _toUtc(r['deleted_at'] as String?), 'deleted_at': _toUtc(r['deleted_at'] as String?),
}) })
.toList(); .toList();
@@ -197,7 +198,7 @@ class TsSyncService extends GetxService {
'measured_points', rows.map((r) => r['uuid'] as String).toList()); 'measured_points', rows.map((r) => r['uuid'] as String).toList());
} }
Future<void> _pushTracks() async { Future<void> pushTracks() async {
final rows = await _db.pendingTracks(); final rows = await _db.pendingTracks();
if (rows.isEmpty) return; if (rows.isEmpty) return;
@@ -213,6 +214,7 @@ class TsSyncService extends GetxService {
'distance_m': r['distance_m'], 'distance_m': r['distance_m'],
'point_count': r['point_count'], 'point_count': r['point_count'],
'device_id': r['device_id'], 'device_id': r['device_id'],
'app_instance_id': r['app_instance_id'],
'deleted_at': _toUtc(r['deleted_at'] as String?), 'deleted_at': _toUtc(r['deleted_at'] as String?),
}) })
.toList(); .toList();
@@ -225,7 +227,7 @@ class TsSyncService extends GetxService {
/// Track-pontok: nagy mennyiség lehet, ezért 500-as batch-ekben, ciklusban, /// Track-pontok: nagy mennyiség lehet, ezért 500-as batch-ekben, ciklusban,
/// amíg el nem fogy. A szerveren nincs UPDATE policy (append-only), ezért /// amíg el nem fogy. A szerveren nincs UPDATE policy (append-only), ezért
/// ignoreDuplicates — az ismételt feltöltés csendben kimarad. /// ignoreDuplicates — az ismételt feltöltés csendben kimarad.
Future<void> _pushTrackPoints() async { Future<void> pushTrackPoints() async {
while (true) { while (true) {
final rows = await _db.pendingTrackPoints(limit: 500); final rows = await _db.pendingTrackPoints(limit: 500);
if (rows.isEmpty) break; if (rows.isEmpty) break;
@@ -328,6 +330,7 @@ class TsSyncService extends GetxService {
'note': r['note'] ?? '', 'note': r['note'] ?? '',
'created_by': r['created_by'], 'created_by': r['created_by'],
'device_id': r['device_id'], 'device_id': r['device_id'],
'app_instance_id': r['app_instance_id'],
'updated_at': r['updated_at'], 'updated_at': r['updated_at'],
'deleted_at': r['deleted_at'], 'deleted_at': r['deleted_at'],
'sync_status': 'synced', 'sync_status': 'synced',
@@ -358,6 +361,7 @@ class TsSyncService extends GetxService {
'is_local_only': 0, 'is_local_only': 0,
'created_by': r['created_by'], 'created_by': r['created_by'],
'device_id': r['device_id'], 'device_id': r['device_id'],
'app_instance_id': r['app_instance_id'],
'updated_at': r['updated_at'], 'updated_at': r['updated_at'],
'deleted_at': r['deleted_at'], 'deleted_at': r['deleted_at'],
'sync_status': 'synced', 'sync_status': 'synced',