Navigáció javítása, fejlesztése

This commit is contained in:
2026-07-15 03:13:52 +02:00
parent 0810c528ec
commit 204d848ba4
4 changed files with 143 additions and 62 deletions
@@ -1,7 +1,9 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter_map/flutter_map.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:terepi_seged/models/source_point.dart';
import 'package:terepi_seged/models/vechicle_position_log.dart';
import 'package:terepi_seged/services/app_database.dart';
@@ -50,12 +52,17 @@ class VibroNavController extends GetxController {
// ── Periodikus napló ───────────────────────────────────────────────
final isLogging = false.obs;
final logIntervalSec = 60.obs; // felhasználó által állítható
final logs = <VehiclePositionLog>[].obs;
Timer? _logTimer;
// ── Térkép — a MapController itt él, nem a View State-jében ────────
final mapController = MapController();
@override
void onInit() {
super.onInit();
load();
refreshLogs();
if (Get.isRegistered<GnssService>()) {
ever(GnssService.to.lastGgaLine, (_) => _onPosition());
}
@@ -67,6 +74,10 @@ class VibroNavController extends GetxController {
super.onClose();
}
Future<void> refreshLogs() async {
logs.value = await loadLogs();
}
Future<void> load() async {
final projectId = ProjectService.to.activeProjectId;
if (projectId == null) {
@@ -212,5 +223,6 @@ class VibroNavController extends GetxController {
: null,
);
await _db.insertVehiclePositionLog(entry);
logs.add(entry);
}
}
@@ -13,6 +13,7 @@ import 'package:terepi_seged/services/gnss/gnss_service.dart';
import 'package:terepi_seged/services/project_service.dart';
import 'package:terepi_seged/services/sps_import_service.dart';
import 'package:terepi_seged/services/vechicle_identity_service.dart';
import 'package:terepi_seged/widgets/map/animated_map_follow.dart';
import 'package:terepi_seged/widgets/map/imported_layer_overlay.dart';
import '../controllers/vibro_nav_controller.dart';
@@ -20,59 +21,9 @@ import '../controllers/vibro_nav_controller.dart';
/// Egyszerű navigációs oldal a vibrátor-járműhöz. ÖNÁLLÓ oldal, nem
/// MapSurveyMode — saját MapController kell a forgó (track-up)
/// térképhez, hogy ez semmilyen más módot ne érintsen.
class VibroNavView extends StatefulWidget {
class VibroNavView extends GetView<VibroNavController> {
const VibroNavView({super.key});
@override
State<VibroNavView> createState() => _VibroNavViewState();
}
class _VibroNavViewState extends State<VibroNavView> {
late final VibroNavController controller;
final _mapController = MapController();
List<VehiclePositionLog> _logs = [];
@override
void initState() {
super.initState();
controller = Get.put(VibroNavController());
_refreshLogs();
// Forgatás + a naplók frissítése a pozíció-változásokra.
ever(controller.travelHeading, (_) => _applyRotation());
ever(controller.hasPosition, (_) => _followPosition());
}
@override
void dispose() {
Get.delete<VibroNavController>();
super.dispose();
}
Future<void> _refreshLogs() async {
_logs = await controller.loadLogs();
if (mounted) setState(() {});
}
void _applyRotation() {
final h = controller.travelHeading.value;
if (h == null) return;
try {
_mapController.rotate(-h);
} catch (_) {}
}
void _followPosition() {
if (!controller.hasPosition.value ||
!Get.isRegistered<CoordConverterService>()) {
return;
}
final gnss = GnssService.to;
try {
_mapController.move(LatLng(gnss.latitude.value, gnss.longitude.value),
_mapController.camera.zoom);
} catch (_) {}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -94,6 +45,17 @@ class _VibroNavViewState extends State<VibroNavView> {
body: Stack(
children: [
_buildMap(),
Obx(() {
final target =
controller.hasPosition.value && Get.isRegistered<GnssService>()
? LatLng(GnssService.to.latitude.value,
GnssService.to.longitude.value)
: null;
return AnimatedMapFollow(
mapController: controller.mapController,
target: target,
heading: controller.travelHeading.value);
}),
Positioned(
left: 8,
right: 8,
@@ -104,8 +66,7 @@ class _VibroNavViewState extends State<VibroNavView> {
left: 8,
right: 8,
bottom: 8,
child: _NavPanel(
controller: controller, onLoggingToggled: _refreshLogs),
child: _NavPanel(controller: controller),
),
],
),
@@ -118,16 +79,22 @@ class _VibroNavViewState extends State<VibroNavView> {
final nearest = controller.nearestPoint.value;
return FlutterMap(
mapController: _mapController,
mapController: controller.mapController,
options: const MapOptions(
initialCenter: LatLng(47.5, 19.05),
initialZoom: 15,
),
children: [
// TileLayer(
// urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
// userAgentPackageName: 'hu.app_dev.terepi_seged',
// ),
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'hu.app_dev.terepi_seged',
urlTemplate: 'http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}',
subdomains: const ['mt0', 'mt1', 'mt2', 'mt3'],
maxNativeZoom: 18,
),
// Tervezett útvonal + minden más importált háttérréteg — a
// MEGLÉVŐ rétegimport-mechanizmus, nincs hozzá új kód.
const ImportedLayerOverlay(),
@@ -140,7 +107,7 @@ class _VibroNavViewState extends State<VibroNavView> {
child: _SourcePointMarker(
point: sp,
isNearest: sp.uuid == nearest?.uuid,
isVerified: controller.isVerified(sp, _logs),
isVerified: controller.isVerified(sp, controller.logs),
),
),
if (controller.hasPosition.value)
@@ -237,6 +204,7 @@ class _VibroNavViewState extends State<VibroNavView> {
}
final inserted = await AppDatabase.instance.insertSourcePoints(saved);
await controller.load();
await controller.refreshLogs();
Get.snackbar('Import kész', '$inserted forráspont importálva',
snackPosition: SnackPosition.BOTTOM);
} catch (e) {
@@ -252,8 +220,7 @@ class _VibroNavViewState extends State<VibroNavView> {
class _NavPanel extends StatelessWidget {
final VibroNavController controller;
final VoidCallback onLoggingToggled;
const _NavPanel({required this.controller, required this.onLoggingToggled});
const _NavPanel({required this.controller});
@override
Widget build(BuildContext context) {
@@ -298,7 +265,6 @@ class _NavPanel extends StatelessWidget {
'(${controller.logIntervalSec.value} mp)'),
onPressed: () {
controller.stopLogging();
onLoggingToggled();
},
)
: FilledButton.icon(
@@ -308,7 +274,6 @@ class _NavPanel extends StatelessWidget {
'(${controller.logIntervalSec.value} mp-enként)'),
onPressed: () {
controller.startLogging();
onLoggingToggled();
},
),
),
+7 -1
View File
@@ -27,6 +27,7 @@ import 'package:terepi_seged/pages/start/bindings/start_page_bindings.dart';
import 'package:terepi_seged/pages/start/presentation/views/start_page.dart';
import 'package:terepi_seged/pages/tracking/bindings/tracking_bindings.dart';
import 'package:terepi_seged/pages/tracking/presentation/views/tracking_view.dart';
import 'package:terepi_seged/pages/vibro_nav/presentation/controllers/vibro_nav_controller.dart';
import 'package:terepi_seged/pages/vibro_nav/presentation/views/vibro_nav_view.dart';
import '../pages/map_test/bindings/map_test_bindings.dart';
@@ -108,6 +109,11 @@ class AppPages {
GetPage(
name: Routes.STAKEOUT_IMPORT, page: () => const StakeoutImportView()),
GetPage(name: Routes.CONTACTS, page: () => const ContactsView()),
GetPage(name: Routes.VIBRONAV, page: () => const VibroNavView())
GetPage(
name: Routes.VIBRONAV,
page: () => const VibroNavView(),
binding: BindingsBuilder(() {
Get.put(VibroNavController());
}))
];
}
+98
View File
@@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
/// Egyenletes, ANIMÁLT térkép-követés — a `mapController.move()`/
/// `.rotate()` közvetlen hívása helyett, amik AZONNAL ugranak az új
/// pozícióra/irányba.
///
/// Ez a widget semmit nem jelenít meg (0 méretű) — kizárólag arra való,
/// hogy a [target] (és opcionálisan [heading]) VÁLTOZÁSAKOR sima
/// animációval, ne ugrással vigye át a térképet a régi pontból az újba.
///
/// Belül a Flutter beépített `TweenAnimationBuilder`-ét használja — ez
/// IMPLICIT módon animál (a cél-érték változásakor magától, a jelenlegi
/// állásból indulva animál az újig). A ticker a Flutter-widget
/// belsejében él, nekünk nem kell saját State/AnimationController-t
/// írnunk hozzá — ez az EGYETLEN pont, ahol animáció miatt egyáltalán
/// szóba kerül egy ticker, de azt is a keretrendszer adja.
class AnimatedMapFollow extends StatelessWidget {
final MapController mapController;
final LatLng? target;
final double? heading; // fok — ha null, nincs forgatás
final Duration duration;
const AnimatedMapFollow({
super.key,
required this.mapController,
required this.target,
this.heading,
this.duration = const Duration(milliseconds: 400),
});
@override
Widget build(BuildContext context) {
final t = target;
if (t == null) return const SizedBox.shrink();
return Stack(
children: [
TweenAnimationBuilder<LatLng>(
tween: _LatLngTween(begin: t, end: t),
duration: duration,
curve: Curves.easeOut,
builder: (context, animatedPos, _) {
try {
mapController.move(animatedPos, mapController.camera.zoom);
} catch (_) {}
return const SizedBox.shrink();
},
),
if (heading != null)
TweenAnimationBuilder<double>(
// A térkép a haladási irány ELLENTETTJÉVEL forog (track-up).
tween: _DegreesTween(begin: -heading!, end: -heading!),
duration: duration,
curve: Curves.easeOut,
builder: (context, animatedRotation, _) {
try {
mapController.rotate(animatedRotation);
} catch (_) {}
return const SizedBox.shrink();
},
),
],
);
}
}
class _LatLngTween extends Tween<LatLng> {
_LatLngTween({required LatLng begin, required LatLng end})
: super(begin: begin, end: end);
@override
LatLng lerp(double t) {
final b = begin!, e = end!;
return LatLng(
b.latitude + (e.latitude - b.latitude) * t,
b.longitude + (e.longitude - b.longitude) * t,
);
}
}
/// Fok-interpoláció a "legrövidebb úton" — enélkül egy 350°→10° váltás
/// hosszan, "visszafelé" animálna 340°-ot ahelyett hogy egyenesen 20°-ot
/// lépne előre.
class _DegreesTween extends Tween<double> {
_DegreesTween({required double begin, required double end})
: super(begin: begin, end: end);
@override
double lerp(double t) {
final b = begin!, e = end!;
var diff = (e - b) % 360;
if (diff > 180) diff -= 360;
if (diff < -180) diff += 360;
return b + diff * t;
}
}