Navigációs képernyőn útvonal megjelenítése supabase táblából és navigációs képernyővel történő indítás beállítások alapján
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 6s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 6s
This commit is contained in:
@@ -8,6 +8,7 @@ import 'package:get/get.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:terepi_seged/models/measure_source_point.dart';
|
||||
import 'package:terepi_seged/models/vibro_nav_route.dart';
|
||||
import 'package:terepi_seged/services/app_logger.dart';
|
||||
import 'package:terepi_seged/services/coord_converter_service.dart';
|
||||
import 'package:terepi_seged/services/gnss/gnss_connection.dart';
|
||||
@@ -16,6 +17,7 @@ import 'package:terepi_seged/services/gnss/phone_gps_connection.dart';
|
||||
import 'package:terepi_seged/services/measure_source_point_service.dart';
|
||||
import 'package:terepi_seged/services/project_service.dart';
|
||||
import 'package:terepi_seged/services/vechicle_identity_service.dart';
|
||||
import 'package:terepi_seged/services/vibro_nav_route_service.dart';
|
||||
|
||||
/// Forráspont + egyszer kiszámolt WGS84 pozíció. A térkép ebből rajzol,
|
||||
/// így GNSS-frissítéskor nincs újraszámolás.
|
||||
@@ -66,6 +68,9 @@ class VibroNavController extends GetxController {
|
||||
final Map<String, LatLng?> _geoCache = {};
|
||||
final isLoadingSourcePoints = false.obs;
|
||||
|
||||
final routes = <VibroNavRoute>[].obs;
|
||||
StreamSubscription<List<VibroNavRoute>>? _routesSub;
|
||||
|
||||
/// Ha false, a tartalék (plan_state = 0) pontok nem jelennek meg a
|
||||
/// térképen — csak a jelgerjesztés helyei (plan_state = 1) látszanak.
|
||||
final showReservePoints = false.obs;
|
||||
@@ -93,7 +98,11 @@ class VibroNavController extends GetxController {
|
||||
super.onInit();
|
||||
|
||||
watchSourcePoints();
|
||||
ever(ProjectService.to.activeProject, (_) => watchSourcePoints());
|
||||
watchRoutes();
|
||||
ever(ProjectService.to.activeProject, (_) {
|
||||
watchSourcePoints();
|
||||
watchRoutes();
|
||||
});
|
||||
|
||||
if (Get.isRegistered<GnssService>()) {
|
||||
_gnssUpdateSub =
|
||||
@@ -130,6 +139,7 @@ class VibroNavController extends GetxController {
|
||||
void onClose() {
|
||||
_gnssUpdateSub?.cancel();
|
||||
_sourcePointsSub?.cancel();
|
||||
_routesSub?.cancel();
|
||||
_externalUploadTimer?.cancel();
|
||||
_internalUploadTimer?.cancel();
|
||||
_internalPositionSub?.cancel();
|
||||
@@ -357,4 +367,26 @@ class VibroNavController extends GetxController {
|
||||
List<GeoSourcePoint> get visibleGeoSourcePoints => showReservePoints.value
|
||||
? geoSourcePoints
|
||||
: geoSourcePoints.where((g) => g.point.isShotLocation).toList();
|
||||
|
||||
void watchRoutes() {
|
||||
_routesSub?.cancel();
|
||||
final projectId = ProjectService.to.activeProject.value?.uuid;
|
||||
if (projectId == null) {
|
||||
routes.clear();
|
||||
return;
|
||||
}
|
||||
_routesSub = VibroNavRouteService.to.watchForToday(projectId).listen(
|
||||
(list) => routes.value = list,
|
||||
onError: (e) =>
|
||||
AppLogger.e('VibroNavController', 'Útvonal-stream hiba: $e'),
|
||||
);
|
||||
}
|
||||
|
||||
/// A térkép ezt rajzolja: a mai napra érvényes útvonalak közül azok,
|
||||
/// amelyek minden járműre szólnak, vagy erre a járműre. A járműváltásra
|
||||
/// azonnal reagál.
|
||||
List<VibroNavRoute> get visibleRoutes {
|
||||
final v = VehicleIdentityService.to.selectedVehicle.value;
|
||||
return routes.where((r) => r.vehicle == null || r.vehicle == v).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:terepi_seged/models/vibro_nav_route.dart';
|
||||
import 'package:terepi_seged/services/app_logger.dart';
|
||||
import 'package:terepi_seged/services/project_service.dart';
|
||||
import 'package:terepi_seged/services/vibro_nav_route_service.dart';
|
||||
|
||||
/// Az útvonalak kezelő (admin) oldalának controllere: lista, GeoJSON-import,
|
||||
/// szerkesztés (név, dátum, jármű, fajta, aktív), törlés.
|
||||
class VibroNavRoutesController extends GetxController {
|
||||
static VibroNavRoutesController get to => Get.find();
|
||||
|
||||
final routes = <VibroNavRoute>[].obs;
|
||||
final isLoading = true.obs;
|
||||
final isBusy = false.obs;
|
||||
|
||||
// Közös űrlap-állapot (import ÉS szerkesztés).
|
||||
final nameCtrl = TextEditingController();
|
||||
final formDate = Rxn<DateTime>(); // null = minden nap
|
||||
final formVehicle = Rxn<String>(); // null = minden jármű
|
||||
final formKind = 'detour'.obs;
|
||||
final formActive = true.obs;
|
||||
|
||||
VibroNavRoute? _editing;
|
||||
List<RouteDraft> _drafts = [];
|
||||
|
||||
bool get isEditing => _editing != null;
|
||||
VibroNavRoute? get editingRoute => _editing;
|
||||
int get draftCount => _drafts.length;
|
||||
|
||||
StreamSubscription<List<VibroNavRoute>>? _sub;
|
||||
Worker? _projectWorker;
|
||||
|
||||
String? get projectId => ProjectService.to.activeProject.value?.uuid;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_subscribe();
|
||||
_projectWorker = ever(ProjectService.to.activeProject, (_) => _subscribe());
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_sub?.cancel();
|
||||
_projectWorker?.dispose();
|
||||
nameCtrl.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
void _subscribe() {
|
||||
_sub?.cancel();
|
||||
final pid = projectId;
|
||||
if (pid == null) {
|
||||
routes.clear();
|
||||
isLoading.value = false;
|
||||
return;
|
||||
}
|
||||
isLoading.value = true;
|
||||
_sub = VibroNavRouteService.to.watchAll(pid).listen(
|
||||
(list) {
|
||||
routes.value = list;
|
||||
isLoading.value = false;
|
||||
},
|
||||
onError: (e) {
|
||||
isLoading.value = false;
|
||||
AppLogger.e('VibroNavRoutesController', 'Útvonal-lista hiba: $e');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// Űrlap-segédek
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
|
||||
static DateTime _dateOnly(DateTime d) => DateTime(d.year, d.month, d.day);
|
||||
|
||||
static bool sameDay(DateTime? a, DateTime? b) =>
|
||||
a != null && b != null && _dateOnly(a) == _dateOnly(b);
|
||||
|
||||
void setDateNone() => formDate.value = null;
|
||||
|
||||
void setDateOffset(int days) =>
|
||||
formDate.value = _dateOnly(DateTime.now().add(Duration(days: days)));
|
||||
|
||||
Future<void> pickDate(BuildContext context) async {
|
||||
final now = DateTime.now();
|
||||
final picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: formDate.value ?? now,
|
||||
firstDate: DateTime(now.year - 1),
|
||||
lastDate: DateTime(now.year + 2),
|
||||
);
|
||||
if (picked != null) formDate.value = _dateOnly(picked);
|
||||
}
|
||||
|
||||
String? _dateStr(DateTime? d) => d == null
|
||||
? null
|
||||
: '${d.year}-${d.month.toString().padLeft(2, '0')}-'
|
||||
'${d.day.toString().padLeft(2, '0')}';
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// Szerkesztés
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
|
||||
void beginEdit(VibroNavRoute r) {
|
||||
_editing = r;
|
||||
_drafts = [];
|
||||
nameCtrl.text = r.name;
|
||||
formDate.value = r.validDate;
|
||||
formVehicle.value = r.vehicle;
|
||||
formKind.value = r.routeKind;
|
||||
formActive.value = r.isActive;
|
||||
}
|
||||
|
||||
Future<void> toggleActive(VibroNavRoute r, bool value) async {
|
||||
try {
|
||||
await VibroNavRouteService.to.updateRoute(r.id, {'is_active': value});
|
||||
} catch (e) {
|
||||
AppLogger.e('VibroNavRoutesController', 'Állapotváltás hiba: $e');
|
||||
Get.snackbar('Hiba', 'Az állapot módosítása nem sikerült: $e',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteRoute(VibroNavRoute r) async {
|
||||
try {
|
||||
await VibroNavRouteService.to.deleteRoute(r.id);
|
||||
} catch (e) {
|
||||
AppLogger.e('VibroNavRoutesController', 'Törlés hiba: $e');
|
||||
Get.snackbar('Hiba', 'A törlés nem sikerült: $e',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// GeoJSON-import
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Fájlválasztás + értelmezés. Visszatérés: a talált útvonalak száma,
|
||||
/// vagy null, ha megszakadt / hibás a fájl (a hibát már kiírta).
|
||||
Future<int?> pickAndParse() async {
|
||||
if (projectId == null) {
|
||||
Get.snackbar('Nincs aktív projekt', 'Előbb válassz ki egy projektet.',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Az Android a .geojson kiterjesztést nem ismeri (Drive-ról sem
|
||||
// választható FileType.custom-mal), ezért FileType.any + kézi ellenőrzés.
|
||||
final result = await FilePicker.platform.pickFiles(type: FileType.any);
|
||||
if (result == null || result.files.isEmpty) return null;
|
||||
|
||||
final file = result.files.single;
|
||||
final lower = file.name.toLowerCase();
|
||||
if (!lower.endsWith('.geojson') && !lower.endsWith('.json')) {
|
||||
Get.snackbar('Nem GeoJSON', 'Válassz .geojson vagy .json fájlt.',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
String content;
|
||||
if (file.bytes != null) {
|
||||
content = utf8.decode(file.bytes!);
|
||||
} else if (file.path != null) {
|
||||
content = await File(file.path!).readAsString();
|
||||
} else {
|
||||
throw const FormatException('A fájl nem olvasható.');
|
||||
}
|
||||
|
||||
final base = file.name
|
||||
.replaceAll(RegExp(r'\.(geo)?json$', caseSensitive: false), '');
|
||||
final parsed = VibroNavRouteService.parseGeoJson(content, base);
|
||||
|
||||
_editing = null;
|
||||
_drafts = parsed.drafts;
|
||||
nameCtrl.clear();
|
||||
setDateOffset(0); // alapból a MAI napra érvényes
|
||||
formVehicle.value = null;
|
||||
formKind.value = 'detour';
|
||||
formActive.value = true;
|
||||
|
||||
if (parsed.skipped > 0) {
|
||||
Get.snackbar('Figyelem',
|
||||
'${parsed.skipped} nem vonal geometriájú elem kimaradt.',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
return parsed.drafts.length;
|
||||
} on FormatException catch (e) {
|
||||
Get.snackbar('Import hiba', e.message,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
duration: const Duration(seconds: 6));
|
||||
return null;
|
||||
} catch (e) {
|
||||
AppLogger.e('VibroNavRoutesController', 'Import hiba: $e');
|
||||
Get.snackbar('Import hiba', '$e', snackPosition: SnackPosition.BOTTOM);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// Mentés (szerkesztés VAGY import)
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
|
||||
Future<bool> saveForm() async {
|
||||
final pid = projectId;
|
||||
if (pid == null) return false;
|
||||
|
||||
if (_editing != null && nameCtrl.text.trim().isEmpty) {
|
||||
Get.snackbar('Hiányzó név', 'Adj nevet az útvonalnak.',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
return false;
|
||||
}
|
||||
|
||||
isBusy.value = true;
|
||||
try {
|
||||
final common = {
|
||||
'valid_date': _dateStr(formDate.value),
|
||||
'vehicle': formVehicle.value,
|
||||
'route_kind': formKind.value,
|
||||
'is_active': formActive.value,
|
||||
};
|
||||
|
||||
if (_editing != null) {
|
||||
await VibroNavRouteService.to.updateRoute(
|
||||
_editing!.id, {'name': nameCtrl.text.trim(), ...common});
|
||||
} else {
|
||||
await VibroNavRouteService.to.insertRoutes([
|
||||
for (final d in _drafts)
|
||||
{
|
||||
'project_id': pid,
|
||||
'name': d.name,
|
||||
'geometry': d.geometry,
|
||||
'crs': d.crs,
|
||||
...common,
|
||||
}
|
||||
]);
|
||||
}
|
||||
_editing = null;
|
||||
_drafts = [];
|
||||
return true;
|
||||
} catch (e) {
|
||||
AppLogger.e('VibroNavRoutesController', 'Mentési hiba: $e');
|
||||
Get.snackbar(
|
||||
'Mentési hiba',
|
||||
'Az útvonal nem menthető. Cloud-projekt kell hozzá, és be kell '
|
||||
'lenned jelentkezve.\n$e',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
duration: const Duration(seconds: 6),
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
isBusy.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user