2026-07-08 23:29:22 +02:00
|
|
|
// EGYETLEN track-szinkron útvonal:
|
|
|
|
|
// 1. Élő pozíció — minden 3 mp-ben UPSERT → terepi_seged_device_positions
|
|
|
|
|
// (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.
|
2026-06-23 15:21:20 +02:00
|
|
|
|
|
|
|
|
import 'dart:async';
|
|
|
|
|
|
|
|
|
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
|
|
|
|
import 'package:get/get.dart';
|
|
|
|
|
import 'package:latlong2/latlong.dart';
|
|
|
|
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
|
|
|
|
2026-07-08 23:29:22 +02:00
|
|
|
import 'ts_sync_service.dart';
|
2026-06-23 15:21:20 +02:00
|
|
|
import 'device_identity_service.dart';
|
|
|
|
|
import '../models/track.dart';
|
|
|
|
|
|
|
|
|
|
class TrackSyncService extends GetxService {
|
|
|
|
|
static TrackSyncService get to => Get.find();
|
|
|
|
|
|
|
|
|
|
final _supabase = Supabase.instance.client;
|
|
|
|
|
|
|
|
|
|
// ── Konfiguráció ──────────────────────────────────────────────────
|
|
|
|
|
static const _batchSize = 10;
|
|
|
|
|
static const _batchIntervalSec = 8;
|
|
|
|
|
static const _positionIntervalSec = 3;
|
|
|
|
|
|
|
|
|
|
// ── Belső állapot ─────────────────────────────────────────────────
|
2026-07-08 23:29:22 +02:00
|
|
|
int _pointsSinceLastPush = 0;
|
|
|
|
|
Timer? _pushTimer;
|
2026-06-23 15:21:20 +02:00
|
|
|
Timer? _posTimer;
|
|
|
|
|
|
|
|
|
|
Track? _track;
|
|
|
|
|
LatLng? _lastPos; // utoljára kapott pozíció (broadcasthoz)
|
|
|
|
|
bool _online = false;
|
|
|
|
|
|
|
|
|
|
// ── Publikus állapot ──────────────────────────────────────────────
|
|
|
|
|
final isSyncing = false.obs;
|
|
|
|
|
|
|
|
|
|
// ── Lifecycle ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<void> onInit() async {
|
|
|
|
|
super.onInit();
|
|
|
|
|
_online = await _checkOnline();
|
|
|
|
|
_listenConnectivity();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void onClose() {
|
|
|
|
|
_stopTimers();
|
|
|
|
|
super.onClose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Session vezérlés (TrackingController hívja) ───────────────────
|
|
|
|
|
|
|
|
|
|
void startSession(Track track) {
|
|
|
|
|
_track = track;
|
2026-07-08 23:29:22 +02:00
|
|
|
_pointsSinceLastPush = 0;
|
2026-06-23 15:21:20 +02:00
|
|
|
|
|
|
|
|
if (track.isLocalOnly) return;
|
|
|
|
|
|
2026-07-08 23:29:22 +02:00
|
|
|
// 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(
|
2026-06-23 15:21:20 +02:00
|
|
|
const Duration(seconds: _batchIntervalSec),
|
2026-07-08 23:29:22 +02:00
|
|
|
(_) => _pushTrackData(),
|
2026-06-23 15:21:20 +02:00
|
|
|
);
|
|
|
|
|
_posTimer = Timer.periodic(
|
|
|
|
|
const Duration(seconds: _positionIntervalSec),
|
|
|
|
|
(_) => _broadcastPosition(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> stopSession(Track track) async {
|
|
|
|
|
_stopTimers();
|
2026-07-08 23:29:22 +02:00
|
|
|
if (!track.isLocalOnly) {
|
|
|
|
|
// Záró push: a lezárt állapotot (vég idő, státusz, végleges táv/
|
|
|
|
|
// pontszám) a TrackingController.stopRecording() már beírta
|
|
|
|
|
// helyileg 'pending' státusszal — ez küldi fel + az utolsó pontokat.
|
|
|
|
|
await _pushTrackData();
|
2026-06-23 15:21:20 +02:00
|
|
|
}
|
|
|
|
|
await _setInactive();
|
|
|
|
|
_track = null;
|
|
|
|
|
_lastPos = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Pont pufferelés ───────────────────────────────────────────────
|
|
|
|
|
|
2026-07-08 23:29:22 +02:00
|
|
|
/// 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.
|
2026-06-23 15:21:20 +02:00
|
|
|
void onNewPoint(TrackPoint point) {
|
|
|
|
|
_lastPos = LatLng(point.latitude, point.longitude);
|
|
|
|
|
|
|
|
|
|
if (_track == null || _track!.isLocalOnly) return;
|
|
|
|
|
|
2026-07-08 23:29:22 +02:00
|
|
|
_pointsSinceLastPush++;
|
|
|
|
|
if (_pointsSinceLastPush >= _batchSize) _pushTrackData();
|
2026-06-23 15:21:20 +02:00
|
|
|
}
|
|
|
|
|
|
2026-07-08 23:29:22 +02:00
|
|
|
// ── 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ó —
|
|
|
|
|
// pontosan az fut, amit a háttér-szinkron is futtatna, csak sűrűbben.
|
2026-06-23 15:21:20 +02:00
|
|
|
|
2026-07-08 23:29:22 +02:00
|
|
|
Future<void> _pushTrackData() async {
|
|
|
|
|
if (!_online) return;
|
|
|
|
|
_pointsSinceLastPush = 0;
|
2026-06-23 15:21:20 +02:00
|
|
|
try {
|
|
|
|
|
isSyncing.value = true;
|
2026-07-08 23:29:22 +02:00
|
|
|
// Fejléc ELŐBB (FK: track_points.track_id → tracks.id), utána a
|
|
|
|
|
// pontok — ugyanaz a sorrend, mint a háttér-szinkron ciklusában.
|
|
|
|
|
await TsSyncService.to.pushTracks();
|
|
|
|
|
await TsSyncService.to.pushTrackPoints();
|
2026-06-23 15:21:20 +02:00
|
|
|
} catch (_) {
|
2026-07-08 23:29:22 +02:00
|
|
|
// Hiba esetén a sorok 'pending' állapotban maradnak — a
|
|
|
|
|
// következő ciklus (vagy a háttér-szinkron) újrapróbálja.
|
2026-06-23 15:21:20 +02:00
|
|
|
} finally {
|
|
|
|
|
isSyncing.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Élő pozíció broadcast ─────────────────────────────────────────
|
2026-07-08 23:29:22 +02:00
|
|
|
// Ez a "hol van most ez az eszköz" jelenlét — nem track-adat, ezért
|
|
|
|
|
// marad önálló, egyszerű upsert.
|
2026-06-23 15:21:20 +02:00
|
|
|
|
|
|
|
|
Future<void> _broadcastPosition() async {
|
|
|
|
|
final pos = _lastPos;
|
|
|
|
|
if (pos == null || !_online) return;
|
|
|
|
|
|
|
|
|
|
final device = DeviceIdentityService.to;
|
|
|
|
|
try {
|
|
|
|
|
await _supabase.from('terepi_seged_device_positions').upsert({
|
|
|
|
|
'device_id': device.deviceId,
|
2026-07-08 23:29:22 +02:00
|
|
|
'app_instance_id': device.appInstanceId,
|
2026-06-23 15:21:20 +02:00
|
|
|
'user_name': device.deviceLabel.value,
|
|
|
|
|
'latitude': pos.latitude,
|
|
|
|
|
'longitude': pos.longitude,
|
2026-07-08 23:29:22 +02:00
|
|
|
'track_id': _track?.uuid,
|
2026-06-23 15:21:20 +02:00
|
|
|
'is_active': true,
|
|
|
|
|
'updated_at': DateTime.now().toUtc().toIso8601String(),
|
|
|
|
|
}, onConflict: 'device_id');
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> _setInactive() async {
|
|
|
|
|
final deviceId = DeviceIdentityService.to.deviceId;
|
|
|
|
|
try {
|
|
|
|
|
await _supabase.from('terepi_seged_device_positions').update(
|
|
|
|
|
{'is_active': false, 'track_id': null}).eq('device_id', deviceId);
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Kapcsolat figyelés ────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
void _listenConnectivity() {
|
|
|
|
|
Connectivity().onConnectivityChanged.listen((results) async {
|
|
|
|
|
final wasOffline = !_online;
|
|
|
|
|
_online = results.any((r) => r != ConnectivityResult.none);
|
2026-07-08 23:29:22 +02:00
|
|
|
if (wasOffline && _online) await _pushTrackData();
|
2026-06-23 15:21:20 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<bool> _checkOnline() async {
|
|
|
|
|
final r = await Connectivity().checkConnectivity();
|
|
|
|
|
return r.any((r) => r != ConnectivityResult.none);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _stopTimers() {
|
2026-07-08 23:29:22 +02:00
|
|
|
_pushTimer?.cancel();
|
2026-06-23 15:21:20 +02:00
|
|
|
_posTimer?.cancel();
|
2026-07-08 23:29:22 +02:00
|
|
|
_pushTimer = null;
|
2026-06-23 15:21:20 +02:00
|
|
|
_posTimer = null;
|
|
|
|
|
}
|
|
|
|
|
}
|