Kitűzés: kényelmi funkciók, pontok listája, export
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:terepi_seged/services/app_database.dart';
|
||||
|
||||
import 'stakeout_service.dart';
|
||||
|
||||
/// Kitűzési pontok Supabase-szinkronja — a meglévő outbox-mintára.
|
||||
///
|
||||
/// A TsSyncService hívja minden ciklus végén (mint a LayerSyncService-t),
|
||||
/// de kézzel (syncNow) is futtatható. PUSH: a 'pending' sorok uuid-ra
|
||||
/// upserttel (idempotens); PULL: projektenként inkrementálisan
|
||||
/// (updated_at > kurzor). A csak-lokális projektek kizárását az
|
||||
/// AppDatabase.pendingStakeoutPoints() végzi SQL-szinten, a pull pedig
|
||||
/// csak a nem-lokális projekteken iterál.
|
||||
///
|
||||
/// SAJÁT kurzort használ (SharedPreferences, projektenként), mert a
|
||||
/// TsSyncService a projects.last_pulled_at kurzort a saját pullja végén
|
||||
/// már előretolta — közös kurzorral a kitűzés-sorok kimaradhatnának.
|
||||
///
|
||||
/// Csapat-láthatóság: a letöltött sorok a lokális DB-be kerülnek, a
|
||||
/// StakeoutService.load() frissíti a listát, így a MÁS ESZKÖZÖN kitűzött
|
||||
/// pont a térképen magától zöldre vált.
|
||||
class StakeoutSyncService extends GetxService {
|
||||
static StakeoutSyncService get to => Get.find();
|
||||
|
||||
final isSyncing = false.obs;
|
||||
final lastError = ''.obs;
|
||||
|
||||
SupabaseClient get _client => Supabase.instance.client;
|
||||
AppDatabase get _db => AppDatabase.instance;
|
||||
|
||||
Future<void> sync() async {
|
||||
if (_client.auth.currentUser == null) return;
|
||||
if (isSyncing.value) return;
|
||||
isSyncing.value = true;
|
||||
lastError.value = '';
|
||||
try {
|
||||
final pulledAny = await _pushThenPull();
|
||||
// Ha aktív a kitűzés mód és jött új adat, frissítjük a nézetet —
|
||||
// itt válik láthatóvá a többiek munkája (zöld markerek).
|
||||
if (pulledAny &&
|
||||
Get.isRegistered<StakeoutService>() &&
|
||||
StakeoutService.to.active.value) {
|
||||
await StakeoutService.to.load();
|
||||
}
|
||||
} catch (e) {
|
||||
lastError.value = e.toString();
|
||||
} finally {
|
||||
isSyncing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _pushThenPull() async {
|
||||
await _push();
|
||||
return _pull();
|
||||
}
|
||||
|
||||
// ── PUSH ─────────────────────────────────────────────────────────
|
||||
|
||||
Future<void> _push() async {
|
||||
final rows = await _db.pendingStakeoutPoints();
|
||||
if (rows.isEmpty) return;
|
||||
|
||||
final payload = rows
|
||||
.map((r) => {
|
||||
'id': r['uuid'],
|
||||
'project_id': r['project_uuid'],
|
||||
'line_id': r['line_id'],
|
||||
'station': r['station'],
|
||||
'name': r['name'],
|
||||
'point_type': r['point_type'],
|
||||
'source': r['source'],
|
||||
'plan_eov_y': r['plan_eov_y'],
|
||||
'plan_eov_x': r['plan_eov_x'],
|
||||
'plan_eov_z': r['plan_eov_z'],
|
||||
'plan_lat': r['plan_lat'],
|
||||
'plan_lon': r['plan_lon'],
|
||||
'status': r['status'],
|
||||
'measured_eov_y': r['measured_eov_y'],
|
||||
'measured_eov_x': r['measured_eov_x'],
|
||||
'measured_eov_z': r['measured_eov_z'],
|
||||
'measured_lat': r['measured_lat'],
|
||||
'measured_lon': r['measured_lon'],
|
||||
'dev_inline': r['dev_inline'],
|
||||
'dev_crossline': r['dev_crossline'],
|
||||
'dev_dz': r['dev_dz'],
|
||||
'fix_quality': r['fix_quality'],
|
||||
'accuracy': r['accuracy'],
|
||||
'tilt_deg': r['tilt_deg'],
|
||||
'staked_at': _toUtc(r['staked_at'] as String?),
|
||||
'is_offset': (r['is_offset'] as int? ?? 0) == 1,
|
||||
'parent_uuid': r['parent_uuid'],
|
||||
'offset_dist': r['offset_dist'],
|
||||
'offset_bearing': r['offset_bearing'],
|
||||
'device_id': r['device_id'],
|
||||
'deleted_at': _toUtc(r['deleted_at'] as String?),
|
||||
})
|
||||
.toList();
|
||||
|
||||
await _client.from('ts_stakeout_points').upsert(payload);
|
||||
await _db.markSyncedByUuid(
|
||||
'stakeout_points', rows.map((r) => r['uuid'] as String).toList());
|
||||
}
|
||||
|
||||
// ── PULL ─────────────────────────────────────────────────────────
|
||||
|
||||
Future<bool> _pull() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
var pulledAny = false;
|
||||
|
||||
final projects = await _db.listProjects();
|
||||
for (final project in projects.where((p) => !p.isLocalOnly)) {
|
||||
final cursorKey = 'stakeout_cursor_${project.uuid}';
|
||||
final cursor = prefs.getString(cursorKey) ?? '1970-01-01T00:00:00Z';
|
||||
var maxSeen = cursor;
|
||||
|
||||
final rows = await _client
|
||||
.from('ts_stakeout_points')
|
||||
.select()
|
||||
.eq('project_id', project.uuid)
|
||||
.gt('updated_at', cursor)
|
||||
.order('updated_at', ascending: true)
|
||||
.limit(1000);
|
||||
|
||||
for (final r in rows) {
|
||||
final ts = r['updated_at'] as String?;
|
||||
if (ts != null && DateTime.parse(ts).isAfter(DateTime.parse(maxSeen))) {
|
||||
maxSeen = ts;
|
||||
}
|
||||
|
||||
// Az AppDatabase generikus applyRemoteRow-ja: uuid alapján
|
||||
// beszúr/frissít, a lokális 'pending' sort nem írja felül
|
||||
// (a push előbb futott).
|
||||
await _db.applyRemoteRow('stakeout_points', {
|
||||
'uuid': r['id'],
|
||||
'project_id': project.id,
|
||||
'line_id': r['line_id'] ?? '',
|
||||
'station': r['station'],
|
||||
'name': r['name'] ?? '${r['station']}',
|
||||
'point_type': r['point_type'] ?? 'geofon',
|
||||
'source': r['source'] ?? 'supabase',
|
||||
'plan_eov_y': r['plan_eov_y'],
|
||||
'plan_eov_x': r['plan_eov_x'],
|
||||
'plan_eov_z': r['plan_eov_z'],
|
||||
'plan_lat': r['plan_lat'],
|
||||
'plan_lon': r['plan_lon'],
|
||||
'status': r['status'] ?? 'pending',
|
||||
'measured_eov_y': r['measured_eov_y'],
|
||||
'measured_eov_x': r['measured_eov_x'],
|
||||
'measured_eov_z': r['measured_eov_z'],
|
||||
'measured_lat': r['measured_lat'],
|
||||
'measured_lon': r['measured_lon'],
|
||||
'dev_inline': r['dev_inline'],
|
||||
'dev_crossline': r['dev_crossline'],
|
||||
'dev_dz': r['dev_dz'],
|
||||
'fix_quality': r['fix_quality'],
|
||||
'accuracy': r['accuracy'],
|
||||
'tilt_deg': r['tilt_deg'],
|
||||
'staked_at': r['staked_at'],
|
||||
'is_offset': (r['is_offset'] == true) ? 1 : 0,
|
||||
'parent_uuid': r['parent_uuid'],
|
||||
'offset_dist': r['offset_dist'],
|
||||
'offset_bearing': r['offset_bearing'],
|
||||
'created_by': r['created_by'],
|
||||
'device_id': r['device_id'],
|
||||
'created_at': r['created_at'] ?? DateTime.now().toIso8601String(),
|
||||
'updated_at': r['updated_at'],
|
||||
'deleted_at': r['deleted_at'],
|
||||
'sync_status': 'synced',
|
||||
});
|
||||
pulledAny = true;
|
||||
}
|
||||
|
||||
if (maxSeen != cursor) {
|
||||
await prefs.setString(cursorKey, maxSeen);
|
||||
}
|
||||
}
|
||||
return pulledAny;
|
||||
}
|
||||
|
||||
/// A lokális, zóna nélküli időbélyeg a Postgres-ben UTC-nek számítana
|
||||
/// → küldés előtt explicit UTC-re konvertálunk.
|
||||
String? _toUtc(String? localIso) {
|
||||
if (localIso == null || localIso.isEmpty) return null;
|
||||
return DateTime.parse(localIso).toUtc().toIso8601String();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user