Navigációs képernyőn forráspontok megjelenítése
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 9s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 9s
This commit is contained in:
@@ -85,7 +85,9 @@ class PhoneGpsConnection implements GnssConnection {
|
||||
Future<void> disconnect() async {
|
||||
_retryTimer?.cancel();
|
||||
await _positionSub?.cancel();
|
||||
_stateController.add(GnssConnectionState.disconnected);
|
||||
if (!_stateController.isClosed) {
|
||||
_stateController.add(GnssConnectionState.disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
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<MeasureSourcePoint> 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<SourcePointFetchResult> watchTodayOrUndated(String projectId) {
|
||||
late StreamController<SourcePointFetchResult> controller;
|
||||
RealtimeChannel? channel;
|
||||
Timer? debounce;
|
||||
var disposed = false;
|
||||
|
||||
Future<void> 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<SourcePointFetchResult>(
|
||||
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<SourcePointFetchResult> _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 = <MeasureSourcePoint>[];
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -10,7 +10,7 @@ class VehicleIdentityService extends GetxService {
|
||||
static const _key = 'vehicle_id';
|
||||
|
||||
/// Az alapértelmezett választható lista — igény szerint bővíthető.
|
||||
static const availableVehicles = ['V1', 'V2', 'V3'];
|
||||
static const availableVehicles = ['V1', 'V2', 'V3', 'V4', 'Teszt'];
|
||||
|
||||
final selectedVehicle = Rxn<String>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user