Navigációs képernyőn forráspontok megjelenítése
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 9s

This commit is contained in:
2026-10-04 20:18:08 +02:00
parent e801090e90
commit e3ff7d070c
10 changed files with 1100 additions and 626 deletions
+86 -9
View File
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:get/get.dart';
@@ -17,16 +18,92 @@ class MeasuredDataService extends GetxService {
/// TODO: erősítsd meg a valós bucket-nevet, ha eltér ettől.
static const _bucket = 'terepi_seged-measure-images';
/// Élő, folyamatosan frissülő stream — a Supabase Realtime-on keresztül,
/// pontosan úgy, ahogy korábban a Firestore .snapshots() működött. Új
/// felvétel a PC-szoftverből AZONNAL megjelenik, frissítés nélkül.
static const _pageSize = 1000;
/// A legfrissebb ennyi felvételt töltjük be (a PostgREST 1000-es
/// max-rows miatt lapozva).
static const _maxRecords = 3000;
/// SZÁNDÉKOSAN nem `.stream()`: az rendezetlen, 1000 sorra vágott
/// lekérdezést futtat, ezért sorok tűnnek el. Helyette szerveroldalon
/// szűrt, lapozott lekérdezés + Realtime csatorna, ami csak jelzés
/// (debounce-olt újratöltés).
Stream<List<MeasuredRecord>> watchRecords(String projectId) {
return _client
.from('terepi_seged_measure_records')
.stream(primaryKey: ['id'])
.eq('project_id', projectId)
.order('measured_at', ascending: false)
.map((rows) => rows.map((r) => MeasuredRecord.fromMap(r)).toList());
late StreamController<List<MeasuredRecord>> controller;
RealtimeChannel? channel;
Timer? debounce;
var disposed = false;
var fetching = false;
var again = false;
Future<void> refetch() async {
if (disposed) return;
if (fetching) {
again = true;
return;
}
fetching = true;
try {
do {
again = false;
final list = await _fetchAll(projectId);
if (!disposed) controller.add(list);
} while (again && !disposed);
} catch (e, s) {
if (!disposed) controller.addError(e, s);
} finally {
fetching = false;
}
}
controller = StreamController<List<MeasuredRecord>>(
onListen: () {
refetch();
channel = _client
.channel('measure_records_$projectId')
.onPostgresChanges(
event: PostgresChangeEvent.all,
schema: 'public',
table: 'terepi_seged_measure_records',
filter: PostgresChangeFilter(
type: PostgresChangeFilterType.eq,
column: 'project_id',
value: projectId,
),
callback: (_) {
debounce?.cancel();
debounce = Timer(const Duration(milliseconds: 800), refetch);
},
)
.subscribe();
},
onCancel: () async {
disposed = true;
debounce?.cancel();
if (channel != null) await _client.removeChannel(channel!);
},
);
return controller.stream;
}
Future<List<MeasuredRecord>> _fetchAll(String projectId) async {
final out = <MeasuredRecord>[];
var from = 0;
while (out.length < _maxRecords) {
final rows = await _client
.from('terepi_seged_measure_records')
.select()
.eq('project_id', projectId)
.order('measured_at', ascending: false)
.order('id', ascending: false)
.range(from, from + _pageSize - 1);
for (final r in rows) {
out.add(MeasuredRecord.fromMap(r));
}
if (rows.length < _pageSize) break;
from += _pageSize;
}
return out;
}
/// A felvételhez tartozó kép betöltése — elsőbbséget adva az image_url-nek