Navigáció javítása, fejlesztése
This commit is contained in:
@@ -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();
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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());
|
||||
}))
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user