diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 843d521..a87a60e 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3,6 +3,8 @@ PODS: - Flutter (1.0.0) - flutter_blue_plus (0.0.1): - Flutter + - flutter_email_sender (0.0.1): + - Flutter - flutter_osm_plugin (0.0.1): - Alamofire - Flutter @@ -11,6 +13,9 @@ PODS: - Yams - geolocator_apple (1.2.0): - Flutter + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS - permission_handler_apple (9.3.0): - Flutter - Polyline (5.1.0) @@ -25,8 +30,10 @@ PODS: DEPENDENCIES: - Flutter (from `Flutter`) - flutter_blue_plus (from `.symlinks/plugins/flutter_blue_plus/ios`) + - flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`) - flutter_osm_plugin (from `.symlinks/plugins/flutter_osm_plugin/ios`) - geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -43,10 +50,14 @@ EXTERNAL SOURCES: :path: Flutter flutter_blue_plus: :path: ".symlinks/plugins/flutter_blue_plus/ios" + flutter_email_sender: + :path: ".symlinks/plugins/flutter_email_sender/ios" flutter_osm_plugin: :path: ".symlinks/plugins/flutter_osm_plugin/ios" geolocator_apple: :path: ".symlinks/plugins/geolocator_apple/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" shared_preferences_foundation: @@ -58,8 +69,10 @@ SPEC CHECKSUMS: Alamofire: f36a35757af4587d8e4f4bfa223ad10be2422b8c Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_blue_plus: 4837da7d00cf5d441fdd6635b3a57f936778ea96 + flutter_email_sender: 10a22605f92809a11ef52b2f412db806c6082d40 flutter_osm_plugin: a661df71d2a3d1698ee410dd2272c8536f9e46f4 geolocator_apple: 6cbaf322953988e009e5ecb481f07efece75c450 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 Polyline: 2a1f29f87f8d9b7de868940f4f76deb8c678a5b1 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 diff --git a/lib/class/geoPosition.dart b/lib/class/geoPosition.dart index 81632a1..0fa63ac 100644 --- a/lib/class/geoPosition.dart +++ b/lib/class/geoPosition.dart @@ -2,11 +2,13 @@ class GeoPosition { final double latitude; final double longitude; final double altitude; + final double speed; GeoPosition({ required this.latitude, required this.longitude, required this.altitude, + this.speed = 0.0, }); factory GeoPosition.fromJson(Map json) { @@ -14,6 +16,16 @@ class GeoPosition { latitude: json['latitude'], longitude: json['longitude'], altitude: json['altitude'], + speed: json['speed'], ); } + + Map toJson() { + return { + 'latitude': latitude, + 'longitude': longitude, + 'altitude': altitude, + 'speed': speed, + }; + } } diff --git a/lib/class/preferenceSaved.dart b/lib/class/preferenceSaved.dart new file mode 100644 index 0000000..6c0e5bb --- /dev/null +++ b/lib/class/preferenceSaved.dart @@ -0,0 +1,37 @@ +import 'dart:convert'; + +import 'package:shared_preferences/shared_preferences.dart'; + +class PreferenceSaved { + int timeTakingPointSecond = 0; + + PreferenceSaved({required this.timeTakingPointSecond}); + + PreferenceSaved.empty(); + + PreferenceSaved.fromJson(Map json) { + timeTakingPointSecond = json['timeTakingPointSecond']; + } + + Map toJson() { + final Map data = {}; + data['timeTakingPointSecond'] = timeTakingPointSecond; + return data; + } +} + +Future restoreLocal() async { + final prefs = await SharedPreferences.getInstance(); + final String? encodedData = prefs.getString('preferenceSaved'); + if (encodedData != null) { + final Map decodedData = jsonDecode(encodedData); + return PreferenceSaved.fromJson(decodedData); + } + return PreferenceSaved(timeTakingPointSecond: 0); +} + +Future saveLocal(PreferenceSaved preferenceSaved) async { + final prefs = await SharedPreferences.getInstance(); + final String encodedData = jsonEncode(preferenceSaved.toJson()); + await prefs.setString('preferenceSaved', encodedData); +} diff --git a/lib/deviceAdvertizinScan.dart b/lib/deviceAdvertizinScan.dart index adb330f..19c86e7 100644 --- a/lib/deviceAdvertizinScan.dart +++ b/lib/deviceAdvertizinScan.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.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:provider/provider.dart'; class DeviceAdvertizinScan extends StatefulWidget { const DeviceAdvertizinScan( @@ -25,48 +27,46 @@ class _DeviceAdvertizinScanState extends State { initPosition: GeoPoint(latitude: 47.4358055, longitude: 8.4737324), ); - @override - void initState() { - tecTakingPoint.text = '5'; - FlutterBluePlus.startScan(); - } + String value = 'Aucune valeur reçue'; - @override - void dispose() { - FlutterBluePlus.stopScan(); - print('Page disposed'); - super.dispose(); - } - - String value = 'Aucune valeur trouvée'; @override Widget build(BuildContext context) { - FlutterBluePlus.onScanResults.listen((results) { - setState(() { - if (results.length > 0) value = results[0].advertisementData.toString(); - }); - }); + final bleProvider = Provider.of(context); + scanResults = bleProvider.scanResults; + + if (scanResults + .where((element) => + element.advertisementData.manufacturerData.isNotEmpty && + element.device.remoteId.toString() == widget.remoteId) + .isNotEmpty) { + value = scanResults + .where((element) => + element.device.remoteId.toString() == widget.remoteId) + .map((e) => e.advertisementData.toString()) + .first; + + // Faites quelque chose avec la variable 'value' ici + } return Scaffold( appBar: AppBar( - title: Text("Valeur de la trame publicitaire", - style: const TextStyle(color: Colors.white)), + 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: () { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - '(debug) Vous devez redémarrer l application et réaliser un nouveau scan pour voir les nouvelles valeurs (FlutterBluePlus.startScan())'), - ), - ); - return; + Navigator.pop(context); }, ), ), body: Column( children: [ + const Padding( + padding: EdgeInsets.all(16.0), + child: Text('Lecture de la trame publicitaire', + style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold)), + ), Container( alignment: Alignment.topCenter, child: Column( @@ -80,17 +80,17 @@ class _DeviceAdvertizinScanState extends State { margin: const EdgeInsets.all(10), child: //Text centré - Center( + const Center( child: Text( "La trame publicitaire est en cours de réception et actualisée en temps réel.", - style: const TextStyle( + style: TextStyle( color: Colors.green, ), textAlign: TextAlign.center), )), Container( margin: const EdgeInsets.all(10), - child: CircularProgressIndicator()), + child: const CircularProgressIndicator()), Container( margin: const EdgeInsets.all(10), alignment: Alignment.center, diff --git a/lib/deviceFlowMap.dart b/lib/deviceFlowMap.dart index 60748f5..3569aea 100644 --- a/lib/deviceFlowMap.dart +++ b/lib/deviceFlowMap.dart @@ -1,14 +1,19 @@ +import 'dart:async'; +import 'dart:io'; + import 'package:flutter/material.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 DeviceFlowMap extends StatefulWidget { const DeviceFlowMap( - {super.key, - required this.title, - required this.deviceName, - required this.deviceAddress}); + {super.key, required this.deviceName, required this.deviceAddress}); - final String title; final String deviceName; final String deviceAddress; @@ -17,22 +22,66 @@ class DeviceFlowMap extends StatefulWidget { } class _DeviceFlowMapState extends State { - TextEditingController tecTakingPoint = TextEditingController(); MapController mapController = MapController( initPosition: GeoPoint(latitude: 47.4358055, longitude: 8.4737324), ); + List scanResults = []; + List geoPositions = []; + + Timer? timer; + double progression = 0.0; + int timeTakingPointSecond = 10; + int updateInterval = 500; // Intervalle de mise à jour en millisecondes + + PreferenceSaved preferenceSaved = PreferenceSaved.empty(); + @override void initState() { - tecTakingPoint.text = '5'; + super.initState(); + + //Positions de test + geoPositions = [ + GeoPosition(latitude: 1.0, longitude: 2.0, altitude: 100.0, speed: 10.0), + GeoPosition(latitude: 3.0, longitude: 4.0, altitude: 200.0, speed: 20.0), + ]; + + restoreLocal().then((value) { + setState(() { + preferenceSaved = value; + timeTakingPointSecond = preferenceSaved.timeTakingPointSecond; + + if (timeTakingPointSecond > 0) { + int totalUpdates = (timeTakingPointSecond * 1000) ~/ updateInterval; + + timer = Timer.periodic(Duration(milliseconds: updateInterval), + (Timer timer) { + setState(() { + progression = (timer.tick % totalUpdates) / totalUpdates * 100.0; + }); + }); + } else { + print('Erreur: timeTakingPointSecond doit être supérieur à zéro.'); + } + }); + }); + } + + @override + void dispose() { + super.dispose(); + timer?.cancel(); } @override Widget build(BuildContext context) { + final bleProvider = Provider.of(context); + scanResults = bleProvider.scanResults; + return Scaffold( appBar: AppBar( - title: Text(widget.deviceName, - style: const TextStyle(color: Colors.white)), + 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), @@ -41,92 +90,118 @@ class _DeviceFlowMapState extends State { Navigator.pop(context); }, ), - actions: [ - IconButton( - icon: Icon(Icons.settings, color: Colors.white), - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Paramètres'), - content: Column( + ), + body: Stack(children: [ + OSMFlutter( + controller: mapController, + onMapIsReady: (bool value) async { + if (value) { + Future.delayed(const Duration(milliseconds: 500), () 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: [ - const Text('Temps de prise de point (en secondes)'), - TextField( - controller: tecTakingPoint, - keyboardType: TextInputType.number, + Text( + 'Altitude: ${geoPositions.last.altitude.toStringAsFixed(2)} m', + style: const TextStyle( + color: Colors.black, + fontSize: 16, + ), + ), + Text( + 'Vitesse: ${geoPositions.last.speed.toStringAsFixed(2)} km/h', + style: const TextStyle( + color: Colors.black, + fontSize: 16, + ), ), ], + ) + : const Text( + 'Aucune valeur', + style: TextStyle( + color: Colors.black, + fontSize: 16, + ), ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text('Annuler'), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop(); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Paramètres enregistrés'), - ), - ); - }, - child: const Text('Enregistrer'), - ), - ], - ); - }, - ); - }) - ], - ), - body: OSMFlutter( - controller: mapController, - osmOption: OSMOption( - userTrackingOption: UserTrackingOption( - enableTracking: true, - unFollowUser: false, - ), - zoomOption: ZoomOption( - initZoom: 14, - minZoomLevel: 3, - maxZoomLevel: 19, - stepZoom: 1.0, - ), - userLocationMarker: UserLocationMaker( - personMarker: MarkerIcon( - icon: Icon( - Icons.location_history_rounded, - color: Colors.red, - size: 48, + const SizedBox(height: 10), + SizedBox( + height: 10, + width: 180, + child: LinearProgressIndicator( + value: progression / 100, + backgroundColor: Colors.grey[300], + valueColor: + const AlwaysStoppedAnimation(Colors.blue), + ), ), - ), - directionArrowMarker: MarkerIcon( - icon: Icon( - Icons.double_arrow, - size: 48, - ), - ), + ], ), - )), + ), + ), + ]), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, floatingActionButton: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ FloatingActionButton( - heroTag: 'mapFab', - backgroundColor: Colors.blue, - onPressed: () {}, - child: Icon(Icons.map_outlined, color: Colors.white), - ), - FloatingActionButton( - heroTag: 'mapFab2', + heroTag: 'actionFab1', + backgroundColor: Colors.blue, + onPressed: () { + mapController.currentLocation(); + }, + child: const Icon(Icons.map_outlined, color: Colors.white), + ), + FloatingActionButton( + heroTag: 'actionFab2', backgroundColor: Colors.blue, onPressed: () { - //Alert for ask to delete piont ?= showDialog( context: context, builder: (BuildContext context) { @@ -137,12 +212,17 @@ class _DeviceFlowMapState extends State { 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( @@ -159,7 +239,53 @@ class _DeviceFlowMapState extends State { ); }, child: const Icon(Icons.delete_sharp, color: Colors.white), - ) + ), + FloatingActionButton( + 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), + ), + FloatingActionButton( + heroTag: 'actionFab4', + backgroundColor: Colors.blue, + onPressed: () { + List linedsPoints = []; + + for (var geoPosition in geoPositions) { + GeoPoint geoPoint = GeoPoint( + latitude: geoPosition.latitude, + longitude: geoPosition.longitude, + ); + + linedsPoints.add(geoPoint); + + mapController.clearAllRoads(); + + if (linedsPoints.length > 1 && + linedsPoints.toSet().length == linedsPoints.length) { + mapController.drawRoadManually( + linedsPoints, + const RoadOption(roadColor: Colors.red), + ); + } + } + }, + child: const Icon(Icons.draw, color: Colors.white), + ), ]), ); } diff --git a/lib/deviceSelection.dart b/lib/deviceSelection.dart index 1cf95f5..7e31c6f 100644 --- a/lib/deviceSelection.dart +++ b/lib/deviceSelection.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; -import 'package:gps_map_flowpoint/class/devicesSaved.dart'; -import 'package:gps_map_flowpoint/deviceAdvertizinScan.dart'; -import 'package:gps_map_flowpoint/deviceFlowMap.dart'; +import 'package:eagletracker/class/BLEProvider.dart'; +import 'package:eagletracker/class/devicesSaved.dart' as DevicesSaved; +import 'package:eagletracker/class/preferenceSaved.dart' as PreferenceSaved; +import 'package:eagletracker/deviceAdvertizinScan.dart'; +import 'package:eagletracker/deviceFlowMap.dart'; +import 'package:provider/provider.dart'; class DeviceSelection extends StatefulWidget { - const DeviceSelection({super.key, required this.title}); - - final String title; + const DeviceSelection({super.key}); @override State createState() => _DeviceSelectionState(); @@ -18,14 +19,28 @@ class _DeviceSelectionState extends State { TextEditingController tecSearch = TextEditingController(); bool isScanning = false; - List devicesSaved = []; + List devicesSaved = []; + PreferenceSaved.PreferenceSaved preferenceSaved = + PreferenceSaved.PreferenceSaved.empty(); + + TextEditingController tecTimeTakingPointSecond = TextEditingController(); // Initialize Bluetooth scanning and subscription in initState @override void initState() { super.initState(); - restoreLocal().then((value) { - print(value.length); + + isScanning = true; + + PreferenceSaved.restoreLocal().then((value) { + setState(() { + preferenceSaved = value; + tecTimeTakingPointSecond.text = + preferenceSaved.timeTakingPointSecond.toString(); + }); + }); + + DevicesSaved.restoreLocal().then((value) { setState(() { devicesSaved = value; }); @@ -36,15 +51,15 @@ class _DeviceSelectionState extends State { context: context, builder: (context) { return AlertDialog( - title: Text("Bluetooth non activé"), - content: Text( + title: const Text("Bluetooth non activé"), + content: const Text( "Le Bluetooth n'est pas activé sur cet appareil. Veillez l'activer pour continuer."), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, - child: Text("OK"), + child: const Text("OK"), ), ], ); @@ -52,19 +67,13 @@ class _DeviceSelectionState extends State { ); } }); - - FlutterBluePlus.onScanResults.listen( - (results) { - setState(() { - scanResults = results; - }); - }, - onError: (e) => print(e), - ); } @override Widget build(BuildContext context) { + final bleProvider = Provider.of(context); + scanResults = bleProvider.scanResults; + // Filtered list of devices List filteredDevices = scanResults .where((result) => result.device.platformName @@ -73,26 +82,74 @@ class _DeviceSelectionState extends State { .toList(); //Saved devices list is connected ? - devicesSaved.forEach((element) { + for (var element in devicesSaved) { element.connected = scanResults - .where((result) => result.device.remoteId.str == element.remoteId) - .length > - 0; - }); + .where((result) => result.device.remoteId.str == element.remoteId) + .isNotEmpty; + } return Scaffold( appBar: AppBar( + actions: [ + IconButton( + icon: const Icon(Icons.settings, color: Colors.white), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Paramètres'), + content: SizedBox( + height: 100, + child: Column(children: [ + const Text('Capture des points (secondes)'), + TextField( + controller: tecTimeTakingPointSecond, + keyboardType: TextInputType.number, + ), + ]), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Annuler'), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + + preferenceSaved.timeTakingPointSecond = + int.parse(tecTimeTakingPointSecond.text); + + PreferenceSaved.saveLocal(preferenceSaved); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Paramètres enregistrés'), + ), + ); + }, + child: const Text('Enregistrer'), + ), + ], + ); + }, + ); + }) + ], backgroundColor: Colors.blue, - title: Text("Eagle Tr@cker", style: TextStyle(color: Colors.white)), + title: + const Text("Eagle Tr@cker", style: TextStyle(color: Colors.white)), ), body: Column( children: [ - //List of devices saved in local storage by card - devicesSaved.length > 0 + devicesSaved.isNotEmpty ? Column( children: [ - Padding( - padding: const EdgeInsets.all(16.0), + const Padding( + padding: EdgeInsets.all(16.0), child: Text('Appareils sauvegardés', style: TextStyle( fontSize: 18.0, fontWeight: FontWeight.bold)), @@ -101,82 +158,82 @@ class _DeviceSelectionState extends State { shrinkWrap: true, itemCount: devicesSaved.length, itemBuilder: (context, index) { - DevicesSaved device = devicesSaved[index]; - return Card( - child: ListTile( - title: Row(children: [ - Icon(Icons.circle, - color: devicesSaved[index].connected - ? Colors.green - : Colors.orange, - size: 10.0), - Text(device.deviceName) - ]), - subtitle: Text(device.deviceAddress), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - GestureDetector( - onTap: () async { - devicesSaved.removeAt(index); - await saveLocal(devicesSaved); - setState(() { - devicesSaved = devicesSaved; - }); - }, - child: Icon(Icons.delete)), - GestureDetector( - onTap: () { - //Stop scanning when a device is selected - FlutterBluePlus.stopScan(); - setState(() { - isScanning = false; - }); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - DeviceAdvertizinScan( + DevicesSaved.DevicesSaved device = devicesSaved[index]; + return Container( + margin: + const EdgeInsets.only(left: 10, right: 10.0), + child: Card( + child: ListTile( + title: Row(children: [ + Icon(Icons.circle, + color: devicesSaved[index].connected + ? Colors.green + : Colors.orange, + size: 10.0), + Text(device.deviceName) + ]), + subtitle: Text(device.deviceAddress), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onTap: () async { + devicesSaved.removeAt(index); + await DevicesSaved.saveLocal( + devicesSaved); + setState(() { + devicesSaved = devicesSaved; + }); + }, + child: const Icon(Icons.delete)), + GestureDetector( + onTap: () { + if (checkIsScanning( + context, isScanning)) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + DeviceAdvertizinScan( + deviceName: + device.deviceName, + deviceAddress: + device.deviceAddress, + remoteId: device.remoteId, + )), + ); + } + }, + child: const Icon(Icons.info_outline), + ), + GestureDetector( + onTap: () { + if (checkIsScanning( + context, isScanning)) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DeviceFlowMap( deviceName: device.deviceName, deviceAddress: device.deviceAddress, - remoteId: device.remoteId, - )), - ); - }, - child: Icon(Icons.info_outline), + ), + ), + ); + } + }, + child: const Icon(Icons.map_outlined), + ), + ], ), - GestureDetector( - onTap: () { - //Stop scanning when a device is selected - FlutterBluePlus.stopScan(); - setState(() { - isScanning = false; - }); - - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DeviceFlowMap( - title: 'GPS Map Flowpoint', - deviceName: device.deviceName, - deviceAddress: device.deviceAddress, - ), - ), - ); - }, - child: Icon(Icons.map_outlined), - ), - ], - ), - )); + ))); }, ), ], ) : Container(), - Padding( - padding: const EdgeInsets.all(16.0), + const Padding( + padding: EdgeInsets.all(16.0), child: Text('Recherche d\' appareils BLE', style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold)), ), @@ -184,7 +241,7 @@ class _DeviceSelectionState extends State { padding: const EdgeInsets.all(16.0), child: TextFormField( controller: tecSearch, - decoration: InputDecoration( + decoration: const InputDecoration( hintText: 'Rechercher un appareil par nom', prefixIcon: Icon(Icons.search), ), @@ -193,7 +250,7 @@ class _DeviceSelectionState extends State { }, ), ), - SizedBox(height: 16.0), + const SizedBox(height: 16.0), Expanded( child: ListView.builder( itemCount: filteredDevices.length, @@ -206,13 +263,12 @@ class _DeviceSelectionState extends State { GestureDetector( onTap: () async { if (devicesSaved - .where((element) => - element.deviceAddress == - result.device.remoteId.str) - .length > - 0) { + .where((element) => + element.deviceAddress == + result.device.remoteId.str) + .isNotEmpty) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar( + const SnackBar( content: Text('Cet appareil est déjà sauvegardé'), ), @@ -220,64 +276,55 @@ class _DeviceSelectionState extends State { return; } - devicesSaved.add(DevicesSaved( + devicesSaved.add(DevicesSaved.DevicesSaved( deviceName: result.device.platformName, deviceAddress: result.device.remoteId.str, remoteId: result.device.remoteId.str, )); - await saveLocal(devicesSaved); + await DevicesSaved.saveLocal(devicesSaved); setState(() { devicesSaved = devicesSaved; }); ; }, - child: Icon(Icons.add), + child: const Icon(Icons.add), ), GestureDetector( onTap: () { - //Stop scanning when a device is selected - FlutterBluePlus.stopScan(); - setState(() { - isScanning = false; - }); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DeviceAdvertizinScan( - deviceName: result.device.platformName, - deviceAddress: result.device.remoteId.str, - remoteId: result.device.remoteId.str, + if (checkIsScanning(context, isScanning)) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DeviceAdvertizinScan( + deviceName: result.device.platformName, + deviceAddress: result.device.remoteId.str, + remoteId: result.device.remoteId.str, + ), ), - ), - ); + ); + } }, - child: Icon(Icons.info_outline), + child: const Icon(Icons.info_outline), ), GestureDetector( onTap: () { - //Stop scanning when a device is selected - FlutterBluePlus.stopScan(); - setState(() { - isScanning = false; - }); - + if (checkIsScanning(context, isScanning)) return; Navigator.push( context, MaterialPageRoute( builder: (context) => DeviceFlowMap( - title: 'GPS Map Flowpoint', deviceName: result.device.platformName, deviceAddress: result.device.remoteId.str, ), ), ); }, - child: Icon(Icons.map_outlined), + child: const Icon(Icons.map_outlined), ), ], ), title: Row(children: [ - Icon(Icons.circle, color: Colors.green, size: 10.0), + const Icon(Icons.circle, color: Colors.green, size: 10.0), Text(result.device.platformName) ]), subtitle: Text(result.device.remoteId.str), @@ -290,14 +337,12 @@ class _DeviceSelectionState extends State { floatingActionButton: FloatingActionButton( backgroundColor: Colors.blue, onPressed: () { - print('Scanning'); - setState(() { isScanning = !isScanning; if (isScanning) { - FlutterBluePlus.startScan(); + bleProvider.startScan(); } else { - FlutterBluePlus.stopScan(); + bleProvider.stopScan(); } }); }, @@ -307,3 +352,16 @@ class _DeviceSelectionState extends State { ); } } + +bool checkIsScanning(BuildContext context, bool isScanning) { + if (!isScanning) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Vous devez activer le scan pour continuer'), + ), + ); + return false; + } else { + return true; + } +} diff --git a/lib/function.dart b/lib/function.dart new file mode 100644 index 0000000..f1fcb48 --- /dev/null +++ b/lib/function.dart @@ -0,0 +1,42 @@ +import 'dart:io'; + +import 'package:csv/csv.dart'; +import 'package:flutter_email_sender/flutter_email_sender.dart'; +import 'package:eagletracker/class/geoPosition.dart'; +import 'package:path_provider/path_provider.dart'; + +Future generateCsv(List positions) async { + List> csvData = [ + ['latitude', 'longitude', 'altitude', 'spped'] // Header + ]; + + for (var position in positions) { + csvData.add([position.latitude, position.longitude, position.altitude]); + } + + Directory appDocumentsDirectory = await getApplicationDocumentsDirectory(); + String appDocumentsPath = appDocumentsDirectory.path; + + String csvFilePath = '$appDocumentsPath/positions.csv'; + File csvFile = File(csvFilePath); + + String csvContent = const ListToCsvConverter().convert(csvData); + await csvFile.writeAsString(csvContent); + + return csvFile; +} + +void sendEmailWithAttachment(File csvFile) async { + final Email email = Email( + subject: 'Eagle Tr@cker - Positions', + body: 'Veuillez trouver ci-joint le fichier CSV contenant les positions.', + recipients: [], + attachmentPaths: [csvFile.path], + ); + + try { + await FlutterEmailSender.send(email); + } catch (error) { + throw 'Failed to send email: $error'; + } +} diff --git a/lib/main.dart b/lib/main.dart index 436fdd5..e31ff83 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:gps_map_flowpoint/class/BLEProvider.dart'; -import 'package:gps_map_flowpoint/deviceSelection.dart'; +import 'package:eagletracker/class/BLEProvider.dart'; +import 'package:eagletracker/deviceSelection.dart'; import 'package:provider/provider.dart'; void main() { @@ -9,7 +9,7 @@ void main() { providers: [ ChangeNotifierProvider(create: (_) => BLEProvider()), ], - child: MyApp(), + child: const MyApp(), ), ); } @@ -22,12 +22,12 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, - title: 'GPS Map Flowpoint', + title: 'Eagle Tr@cker', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), - home: const DeviceSelection(title: 'GPS Map Flowpoint'), + home: const DeviceSelection(), ); } } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 90c07c9..086b32b 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,12 +7,14 @@ import Foundation import flutter_blue_plus import geolocator_apple +import path_provider_foundation import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 05f3a75..7c175b5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + csv: + dependency: "direct main" + description: + name: csv + sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c + url: "https://pub.dev" + source: hosted + version: "6.0.0" cupertino_icons: dependency: "direct main" description: @@ -110,6 +118,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.32.7" + flutter_email_sender: + dependency: "direct main" + description: + name: flutter_email_sender + sha256: fb515d4e073d238d0daf1d765e5318487b6396d46b96e0ae9745dbc9a133f97a + url: "https://pub.dev" + source: hosted + version: "6.0.3" flutter_lints: dependency: "direct dev" description: @@ -304,6 +320,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a + url: "https://pub.dev" + source: hosted + version: "2.2.6" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -542,13 +582,13 @@ packages: source: hosted version: "1.3.2" url_launcher: - dependency: transitive + dependency: "direct main" description: name: url_launcher - sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" url: "https://pub.dev" source: hosted - version: "6.2.6" + version: "6.3.0" url_launcher_android: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c7680aa..8811d75 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,4 +1,4 @@ -name: gps_map_flowpoint +name: eagletracker description: "A new Flutter project." # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. @@ -40,6 +40,10 @@ dependencies: geolocator: ^12.0.0 shared_preferences: ^2.2.3 provider: ^6.1.2 + csv: ^6.0.0 + url_launcher: ^6.3.0 + path_provider: ^2.1.3 + flutter_email_sender: ^6.0.3 dev_dependencies: flutter_test: