import 'dart:async'; import 'package:get/get.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import '../models/measure_source_point.dart'; class SourcePointFetchResult { final List points; final bool capped; const SourcePointFetchResult({required this.points, required this.capped}); } /// Forráspontok (jelgerjesztés/tartalék) lekérdezése a navigációs /// térképhez. SZÁNDÉKOSAN tisztán online — nincs helyi tükör. class MeasureSourcePointService extends GetxService { static MeasureSourcePointService get to => Get.find(); SupabaseClient get _client => Supabase.instance.client; /// Ha egy pontnak VAN megadott tervezett dátuma, csak a mait mutatjuk; /// aminek NINCS dátuma, azt mindig — plusz egy kemény felső korlát, /// nehogy egy dátum nélküli projektnél ezerszámra terhelje a térképet. static const _safetyLimit = 6000; /// Élő frissítés a stream() helyett: a tényleges adatot SZERVER-oldali /// szűréssel, lapozva kérjük le (a PostgREST 1000 soros felső /// korlátja miatt a .stream() csonkított, rendezetlen részhalmazt /// adott), a realtime csatorna pedig csak jelez, hogy változott /// valami — ilyenkor újra lekérünk. Stream watchTodayOrUndated(String projectId) { late StreamController controller; RealtimeChannel? channel; Timer? debounce; var disposed = false; Future refetch() async { if (disposed) return; try { final result = await _fetchAll(projectId); if (!disposed) controller.add(result); } catch (e, s) { if (!disposed) controller.addError(e, s); } } controller = StreamController( onListen: () { refetch(); channel = _client .channel('source_points_$projectId') .onPostgresChanges( event: PostgresChangeEvent.all, schema: 'public', table: 'terepi_seged_measure_source_points', filter: PostgresChangeFilter( type: PostgresChangeFilterType.eq, column: 'project_id', value: projectId, ), callback: (_) { // Sok egyidejű változásnál (pl. tömeges import) ne // kérjünk le minden egyes sorra újra. debounce?.cancel(); debounce = Timer(const Duration(milliseconds: 600), refetch); }, ) .subscribe(); }, onCancel: () async { disposed = true; debounce?.cancel(); if (channel != null) await _client.removeChannel(channel!); }, ); return controller.stream; } Future _fetchAll(String projectId) async { final now = DateTime.now(); final todayStr = '${now.year.toString().padLeft(4, '0')}-' '${now.month.toString().padLeft(2, '0')}-' '${now.day.toString().padLeft(2, '0')}'; const pageSize = 1000; final all = []; var from = 0; while (all.length <= _safetyLimit) { final rows = await _client .from('terepi_seged_measure_source_points') .select() .eq('project_id', projectId) .eq('is_active', true) .or('planned_acquisition_date.eq.$todayStr,' 'planned_acquisition_date.is.null') .order('line_number', ascending: true) .order('point_number', ascending: true) .range(from, from + pageSize - 1); all.addAll(rows.map((r) => MeasureSourcePoint.fromMap(r))); if (rows.length < pageSize) break; from += pageSize; } final capped = all.length > _safetyLimit; if (capped) all.removeRange(_safetyLimit, all.length); return SourcePointFetchResult(points: all, capped: capped); } }