import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; import 'package:eagletracker/config.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_osm_plugin/flutter_osm_plugin.dart'; import 'package:eagletracker/class/BLEProvider.dart'; import 'package:eagletracker/class/geoPosition.dart'; import 'package:eagletracker/class/preferenceSaved.dart'; import 'package:eagletracker/function.dart'; import 'package:provider/provider.dart'; /* * Class for the device flow map screen */ class DeviceFlowMap extends StatefulWidget { const DeviceFlowMap( {super.key, required this.deviceName, required this.deviceAddress}); final String deviceName; final String deviceAddress; @override State createState() => _DeviceFlowMapState(); } class _DeviceFlowMapState extends State { final Config config = Config(); PreferenceSaved preferenceSaved = PreferenceSaved.empty(); MapController mapController = MapController( initPosition: GeoPoint(latitude: 46.1723, longitude: 7.1841)); List scanResults = []; List geoPositions = []; List advDataLastReception = []; Timer? timer; double progression = 0.0; int timeTakingPointSecond = 10; int updateInterval = 500; double vitesseMax = 0.0; double altitudeMax = 0.0; double latitude = 0; double longitude = 0; double altitude = 0; double speed = 0; bool targetTracking = false; bool drawRoad = false; GeoPoint targetTrackingPoint = GeoPoint(latitude: 0, longitude: 0); GeoPoint targetTrackingPointHistory = GeoPoint(latitude: 0, longitude: 0); @override void initState() { super.initState(); SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]); restoreLocal().then((value) async { preferenceSaved = value; timeTakingPointSecond = preferenceSaved.timeTakingPointSecond; // set the default time capture if the value is 0 if (timeTakingPointSecond == 0) { timeTakingPointSecond = config.defaultTimeCapture; } int totalUpdates = (timeTakingPointSecond * 1000) ~/ updateInterval; timer = Timer.periodic(Duration(milliseconds: updateInterval), (Timer timer) async { setState(() { progression = (timer.tick % totalUpdates) / totalUpdates * 100.0; }); // Check if the manufacturer data is not empty and the last reception is not empty if (advDataLastReception.isNotEmpty && timer.tick % totalUpdates == 0) { List hexList = advDataLastReception .map((int value) => value.toRadixString(16)) .toList(); if (config.debug) { print(advDataLastReception); print(hexList); } //Get the values from the advertisement data List values = getValueFromAdvData(advDataLastReception); latitude = values[0]; longitude = values[1]; altitude = values[2]; speed = values[3]; //Ajout de la position GeoPosition geoPosition = GeoPosition( latitude: latitude, longitude: longitude, altitude: altitude, ); geoPositions.add(geoPosition); //Vitesse max et altitude max if (speed > vitesseMax) { vitesseMax = speed; } if (altitude > altitudeMax) { altitudeMax = altitude; } targetTrackingPoint = GeoPoint( latitude: latitude, longitude: longitude, ); //Draw the road if the option is enabled mapController.clearAllRoads(); if (drawRoad) { List linedsPoints = []; for (var geoPosition in geoPositions) { GeoPoint geoPoint = GeoPoint( latitude: geoPosition.latitude, longitude: geoPosition.longitude, ); if (!linedsPoints.contains(geoPoint)) { linedsPoints.add(geoPoint); } if (config.debug) { print(linedsPoints.length); } } if (linedsPoints.length > 1) { // Dessiner la route await mapController.drawRoadManually( linedsPoints, const RoadOption(roadColor: Colors.red, zoomInto: false), ); } } mapController.removeMarker(targetTrackingPointHistory); mapController.addMarker(targetTrackingPoint, markerIcon: const MarkerIcon( icon: Icon( Icons.star_outlined, color: Colors.red, size: 48, ), )); targetTrackingPointHistory = targetTrackingPoint; if (targetTracking) { mapController.changeLocation(targetTrackingPoint); } setState(() {}); } }); }); } @override void dispose() { super.dispose(); timer?.cancel(); } @override Widget build(BuildContext context) { final bleProvider = Provider.of(context); scanResults = bleProvider.scanResults; for (var scanResult in scanResults) { if (scanResult.device.remoteId.toString() == widget.deviceAddress) { if (scanResult.advertisementData.manufacturerData[49406] != null) { advDataLastReception = scanResult.advertisementData.manufacturerData[49406]!; break; } } } return Scaffold( appBar: AppBar( title: const Text("Eagle Tr@cker", style: TextStyle(color: Colors.white)), backgroundColor: Colors.blue, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]); mapController.dispose(); Navigator.pop(context); }, ), ), body: Stack(children: [ OSMFlutter( controller: mapController, onMapIsReady: (bool value) async { if (value) { Future.delayed(const Duration(milliseconds: 750), () async { await mapController.currentLocation(); }); } }, osmOption: OSMOption( zoomOption: const ZoomOption( initZoom: 14, minZoomLevel: 3, maxZoomLevel: 19, stepZoom: 1.0, ), userLocationMarker: UserLocationMaker( personMarker: const MarkerIcon( icon: Icon( Icons.place_rounded, color: Colors.red, size: 48, ), ), directionArrowMarker: const MarkerIcon( icon: Icon( Icons.place_rounded, size: 48, ), ), ), )), Positioned( top: 10, right: 10, child: Container( width: 200, padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Nom: ${widget.deviceName}', style: const TextStyle( color: Colors.black, fontSize: 16, ), ), geoPositions.isNotEmpty ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Altitude: ${altitude.toStringAsFixed(2)} m', style: const TextStyle( color: Colors.black, fontSize: 16, ), ), Text( 'Vitesse: ${speed.toStringAsFixed(2)} km/h', style: const TextStyle( color: Colors.black, fontSize: 16, ), ), Text( 'Alt. max: ${altitudeMax.toStringAsFixed(2)} m', style: const TextStyle( color: Colors.black, fontSize: 16, ), ), Text( 'Vit. max: ${vitesseMax.toStringAsFixed(2)} km/h', style: const TextStyle( color: Colors.black, fontSize: 16, ), ), ], ) : const Text( 'Aucune valeur', style: TextStyle( color: Colors.black, fontSize: 16, ), ), const SizedBox(height: 10), SizedBox( height: 10, width: 180, child: LinearProgressIndicator( value: progression / 100, backgroundColor: Colors.grey[300], valueColor: const AlwaysStoppedAnimation(Colors.blue), ), ), ], ), ), ), ]), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, floatingActionButton: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ FloatingActionButton( tooltip: 'Centrer la carte sur ma position actuelle', heroTag: 'actionFab1', backgroundColor: Colors.blue, onPressed: () { mapController.currentLocation(); }, child: const Icon(Icons.gps_not_fixed_sharp, color: Colors.white), ), FloatingActionButton( heroTag: 'actionFab2', tooltip: 'Réinitialiser les points', backgroundColor: Colors.blue, onPressed: () { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Réinitialiser les points'), content: const Text('Voulez-vous supprimer tous les points ?'), actions: [ TextButton( onPressed: () { geoPositions.clear(); Navigator.of(context).pop(); }, child: const Text('Annuler'), ), TextButton( onPressed: () { setState(() { geoPositions.clear(); }); Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Tous les points ont été supprimés'), ), ); }, child: const Text('Oui'), ), ], ); }, ); }, child: const Icon(Icons.delete_sharp, color: Colors.white), ), FloatingActionButton( heroTag: 'actionFab4', tooltip: 'Centrer la carte sur la cible', backgroundColor: targetTracking ? Colors.green : Colors.blue, onPressed: () { setState(() { targetTracking = !targetTracking; }); }, child: const Icon(Icons.gps_fixed, color: Colors.white), ), FloatingActionButton( tooltip: 'Dessiner la route', heroTag: 'actionFab6', backgroundColor: drawRoad ? Colors.green : Colors.blue, onPressed: () { setState(() { drawRoad = !drawRoad; }); }, child: const Icon(Icons.roundabout_left, color: Colors.white), ), FloatingActionButton( tooltip: 'Envoyer les positions par email', heroTag: 'actionFab3', backgroundColor: Colors.blue, onPressed: () async { if (geoPositions.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Aucune position à envoyer'), ), ); return; } File csvFile = await generateCsv(geoPositions); sendEmailWithAttachment(csvFile); }, child: const Icon(Icons.mail, color: Colors.white), ), ]), ); } }