First commit

This commit is contained in:
Guillaume David 2024-07-08 14:18:00 +02:00
parent 92957a1903
commit 27d8f55d7e
11 changed files with 594 additions and 260 deletions

View File

@ -3,6 +3,8 @@ PODS:
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_blue_plus (0.0.1): - flutter_blue_plus (0.0.1):
- Flutter - Flutter
- flutter_email_sender (0.0.1):
- Flutter
- flutter_osm_plugin (0.0.1): - flutter_osm_plugin (0.0.1):
- Alamofire - Alamofire
- Flutter - Flutter
@ -11,6 +13,9 @@ PODS:
- Yams - Yams
- geolocator_apple (1.2.0): - geolocator_apple (1.2.0):
- Flutter - Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.3.0): - permission_handler_apple (9.3.0):
- Flutter - Flutter
- Polyline (5.1.0) - Polyline (5.1.0)
@ -25,8 +30,10 @@ PODS:
DEPENDENCIES: DEPENDENCIES:
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_blue_plus (from `.symlinks/plugins/flutter_blue_plus/ios`) - 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`) - flutter_osm_plugin (from `.symlinks/plugins/flutter_osm_plugin/ios`)
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/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`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
@ -43,10 +50,14 @@ EXTERNAL SOURCES:
:path: Flutter :path: Flutter
flutter_blue_plus: flutter_blue_plus:
:path: ".symlinks/plugins/flutter_blue_plus/ios" :path: ".symlinks/plugins/flutter_blue_plus/ios"
flutter_email_sender:
:path: ".symlinks/plugins/flutter_email_sender/ios"
flutter_osm_plugin: flutter_osm_plugin:
:path: ".symlinks/plugins/flutter_osm_plugin/ios" :path: ".symlinks/plugins/flutter_osm_plugin/ios"
geolocator_apple: geolocator_apple:
:path: ".symlinks/plugins/geolocator_apple/ios" :path: ".symlinks/plugins/geolocator_apple/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple: permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios" :path: ".symlinks/plugins/permission_handler_apple/ios"
shared_preferences_foundation: shared_preferences_foundation:
@ -58,8 +69,10 @@ SPEC CHECKSUMS:
Alamofire: f36a35757af4587d8e4f4bfa223ad10be2422b8c Alamofire: f36a35757af4587d8e4f4bfa223ad10be2422b8c
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_blue_plus: 4837da7d00cf5d441fdd6635b3a57f936778ea96 flutter_blue_plus: 4837da7d00cf5d441fdd6635b3a57f936778ea96
flutter_email_sender: 10a22605f92809a11ef52b2f412db806c6082d40
flutter_osm_plugin: a661df71d2a3d1698ee410dd2272c8536f9e46f4 flutter_osm_plugin: a661df71d2a3d1698ee410dd2272c8536f9e46f4
geolocator_apple: 6cbaf322953988e009e5ecb481f07efece75c450 geolocator_apple: 6cbaf322953988e009e5ecb481f07efece75c450
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
Polyline: 2a1f29f87f8d9b7de868940f4f76deb8c678a5b1 Polyline: 2a1f29f87f8d9b7de868940f4f76deb8c678a5b1
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78

View File

@ -2,11 +2,13 @@ class GeoPosition {
final double latitude; final double latitude;
final double longitude; final double longitude;
final double altitude; final double altitude;
final double speed;
GeoPosition({ GeoPosition({
required this.latitude, required this.latitude,
required this.longitude, required this.longitude,
required this.altitude, required this.altitude,
this.speed = 0.0,
}); });
factory GeoPosition.fromJson(Map<String, dynamic> json) { factory GeoPosition.fromJson(Map<String, dynamic> json) {
@ -14,6 +16,16 @@ class GeoPosition {
latitude: json['latitude'], latitude: json['latitude'],
longitude: json['longitude'], longitude: json['longitude'],
altitude: json['altitude'], altitude: json['altitude'],
speed: json['speed'],
); );
} }
Map<String, dynamic> toJson() {
return {
'latitude': latitude,
'longitude': longitude,
'altitude': altitude,
'speed': speed,
};
}
} }

View File

@ -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<String, dynamic> json) {
timeTakingPointSecond = json['timeTakingPointSecond'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['timeTakingPointSecond'] = timeTakingPointSecond;
return data;
}
}
Future<PreferenceSaved> restoreLocal() async {
final prefs = await SharedPreferences.getInstance();
final String? encodedData = prefs.getString('preferenceSaved');
if (encodedData != null) {
final Map<String, dynamic> decodedData = jsonDecode(encodedData);
return PreferenceSaved.fromJson(decodedData);
}
return PreferenceSaved(timeTakingPointSecond: 0);
}
Future<void> saveLocal(PreferenceSaved preferenceSaved) async {
final prefs = await SharedPreferences.getInstance();
final String encodedData = jsonEncode(preferenceSaved.toJson());
await prefs.setString('preferenceSaved', encodedData);
}

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:flutter_osm_plugin/flutter_osm_plugin.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 { class DeviceAdvertizinScan extends StatefulWidget {
const DeviceAdvertizinScan( const DeviceAdvertizinScan(
@ -25,48 +27,46 @@ class _DeviceAdvertizinScanState extends State<DeviceAdvertizinScan> {
initPosition: GeoPoint(latitude: 47.4358055, longitude: 8.4737324), initPosition: GeoPoint(latitude: 47.4358055, longitude: 8.4737324),
); );
@override String value = 'Aucune valeur reçue';
void initState() {
tecTakingPoint.text = '5';
FlutterBluePlus.startScan();
}
@override
void dispose() {
FlutterBluePlus.stopScan();
print('Page disposed');
super.dispose();
}
String value = 'Aucune valeur trouvée';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
FlutterBluePlus.onScanResults.listen((results) { final bleProvider = Provider.of<BLEProvider>(context);
setState(() { scanResults = bleProvider.scanResults;
if (results.length > 0) value = results[0].advertisementData.toString();
}); 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( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text("Valeur de la trame publicitaire", title:
style: const TextStyle(color: Colors.white)), const Text("Eagle Tr@cker", style: TextStyle(color: Colors.white)),
backgroundColor: Colors.blue, backgroundColor: Colors.blue,
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white), icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () { onPressed: () {
ScaffoldMessenger.of(context).showSnackBar( Navigator.pop(context);
SnackBar(
content: Text(
'(debug) Vous devez redémarrer l application et réaliser un nouveau scan pour voir les nouvelles valeurs (FlutterBluePlus.startScan())'),
),
);
return;
}, },
), ),
), ),
body: Column( body: Column(
children: [ children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text('Lecture de la trame publicitaire',
style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold)),
),
Container( Container(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
child: Column( child: Column(
@ -80,17 +80,17 @@ class _DeviceAdvertizinScanState extends State<DeviceAdvertizinScan> {
margin: const EdgeInsets.all(10), margin: const EdgeInsets.all(10),
child: child:
//Text centré //Text centré
Center( const Center(
child: Text( child: Text(
"La trame publicitaire est en cours de réception et actualisée en temps réel.", "La trame publicitaire est en cours de réception et actualisée en temps réel.",
style: const TextStyle( style: TextStyle(
color: Colors.green, color: Colors.green,
), ),
textAlign: TextAlign.center), textAlign: TextAlign.center),
)), )),
Container( Container(
margin: const EdgeInsets.all(10), margin: const EdgeInsets.all(10),
child: CircularProgressIndicator()), child: const CircularProgressIndicator()),
Container( Container(
margin: const EdgeInsets.all(10), margin: const EdgeInsets.all(10),
alignment: Alignment.center, alignment: Alignment.center,

View File

@ -1,14 +1,19 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart'; 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: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 { class DeviceFlowMap extends StatefulWidget {
const DeviceFlowMap( const DeviceFlowMap(
{super.key, {super.key, required this.deviceName, required this.deviceAddress});
required this.title,
required this.deviceName,
required this.deviceAddress});
final String title;
final String deviceName; final String deviceName;
final String deviceAddress; final String deviceAddress;
@ -17,22 +22,66 @@ class DeviceFlowMap extends StatefulWidget {
} }
class _DeviceFlowMapState extends State<DeviceFlowMap> { class _DeviceFlowMapState extends State<DeviceFlowMap> {
TextEditingController tecTakingPoint = TextEditingController();
MapController mapController = MapController( MapController mapController = MapController(
initPosition: GeoPoint(latitude: 47.4358055, longitude: 8.4737324), initPosition: GeoPoint(latitude: 47.4358055, longitude: 8.4737324),
); );
List<ScanResult> scanResults = [];
List<GeoPosition> geoPositions = [];
Timer? timer;
double progression = 0.0;
int timeTakingPointSecond = 10;
int updateInterval = 500; // Intervalle de mise à jour en millisecondes
PreferenceSaved preferenceSaved = PreferenceSaved.empty();
@override @override
void initState() { 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bleProvider = Provider.of<BLEProvider>(context);
scanResults = bleProvider.scanResults;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(widget.deviceName, title:
style: const TextStyle(color: Colors.white)), const Text("Eagle Tr@cker", style: TextStyle(color: Colors.white)),
backgroundColor: Colors.blue, backgroundColor: Colors.blue,
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white), icon: const Icon(Icons.arrow_back, color: Colors.white),
@ -41,92 +90,118 @@ class _DeviceFlowMapState extends State<DeviceFlowMap> {
Navigator.pop(context); Navigator.pop(context);
}, },
), ),
actions: [ ),
IconButton( body: Stack(children: [
icon: Icon(Icons.settings, color: Colors.white), OSMFlutter(
onPressed: () { controller: mapController,
showDialog( onMapIsReady: (bool value) async {
context: context, if (value) {
builder: (BuildContext context) { Future.delayed(const Duration(milliseconds: 500), () async {
return AlertDialog( await mapController.currentLocation();
title: const Text('Paramètres'), });
content: Column( }
},
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: [ children: [
const Text('Temps de prise de point (en secondes)'), Text(
TextField( 'Altitude: ${geoPositions.last.altitude.toStringAsFixed(2)} m',
controller: tecTakingPoint, style: const TextStyle(
keyboardType: TextInputType.number, 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: <Widget>[ const SizedBox(height: 10),
TextButton( SizedBox(
onPressed: () { height: 10,
Navigator.of(context).pop(); width: 180,
}, child: LinearProgressIndicator(
child: const Text('Annuler'), value: progression / 100,
), backgroundColor: Colors.grey[300],
TextButton( valueColor:
onPressed: () { const AlwaysStoppedAnimation<Color>(Colors.blue),
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,
), ),
), ],
directionArrowMarker: MarkerIcon(
icon: Icon(
Icons.double_arrow,
size: 48,
),
),
), ),
)), ),
),
]),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: floatingActionButton:
Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
FloatingActionButton( FloatingActionButton(
heroTag: 'mapFab', heroTag: 'actionFab1',
backgroundColor: Colors.blue, backgroundColor: Colors.blue,
onPressed: () {}, onPressed: () {
child: Icon(Icons.map_outlined, color: Colors.white), mapController.currentLocation();
), },
FloatingActionButton( child: const Icon(Icons.map_outlined, color: Colors.white),
heroTag: 'mapFab2', ),
FloatingActionButton(
heroTag: 'actionFab2',
backgroundColor: Colors.blue, backgroundColor: Colors.blue,
onPressed: () { onPressed: () {
//Alert for ask to delete piont ?=
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
@ -137,12 +212,17 @@ class _DeviceFlowMapState extends State<DeviceFlowMap> {
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(
onPressed: () { onPressed: () {
geoPositions.clear();
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: const Text('Annuler'), child: const Text('Annuler'),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
setState(() {
geoPositions.clear();
});
Navigator.of(context).pop(); Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@ -159,7 +239,53 @@ class _DeviceFlowMapState extends State<DeviceFlowMap> {
); );
}, },
child: const Icon(Icons.delete_sharp, color: Colors.white), 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<GeoPoint> 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),
),
]), ]),
); );
} }

View File

@ -1,13 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:gps_map_flowpoint/class/devicesSaved.dart'; import 'package:eagletracker/class/BLEProvider.dart';
import 'package:gps_map_flowpoint/deviceAdvertizinScan.dart'; import 'package:eagletracker/class/devicesSaved.dart' as DevicesSaved;
import 'package:gps_map_flowpoint/deviceFlowMap.dart'; 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 { class DeviceSelection extends StatefulWidget {
const DeviceSelection({super.key, required this.title}); const DeviceSelection({super.key});
final String title;
@override @override
State<DeviceSelection> createState() => _DeviceSelectionState(); State<DeviceSelection> createState() => _DeviceSelectionState();
@ -18,14 +19,28 @@ class _DeviceSelectionState extends State<DeviceSelection> {
TextEditingController tecSearch = TextEditingController(); TextEditingController tecSearch = TextEditingController();
bool isScanning = false; bool isScanning = false;
List<DevicesSaved> devicesSaved = []; List<DevicesSaved.DevicesSaved> devicesSaved = [];
PreferenceSaved.PreferenceSaved preferenceSaved =
PreferenceSaved.PreferenceSaved.empty();
TextEditingController tecTimeTakingPointSecond = TextEditingController();
// Initialize Bluetooth scanning and subscription in initState // Initialize Bluetooth scanning and subscription in initState
@override @override
void initState() { void initState() {
super.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(() { setState(() {
devicesSaved = value; devicesSaved = value;
}); });
@ -36,15 +51,15 @@ class _DeviceSelectionState extends State<DeviceSelection> {
context: context, context: context,
builder: (context) { builder: (context) {
return AlertDialog( return AlertDialog(
title: Text("Bluetooth non activé"), title: const Text("Bluetooth non activé"),
content: Text( content: const Text(
"Le Bluetooth n'est pas activé sur cet appareil. Veillez l'activer pour continuer."), "Le Bluetooth n'est pas activé sur cet appareil. Veillez l'activer pour continuer."),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: Text("OK"), child: const Text("OK"),
), ),
], ],
); );
@ -52,19 +67,13 @@ class _DeviceSelectionState extends State<DeviceSelection> {
); );
} }
}); });
FlutterBluePlus.onScanResults.listen(
(results) {
setState(() {
scanResults = results;
});
},
onError: (e) => print(e),
);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bleProvider = Provider.of<BLEProvider>(context);
scanResults = bleProvider.scanResults;
// Filtered list of devices // Filtered list of devices
List<ScanResult> filteredDevices = scanResults List<ScanResult> filteredDevices = scanResults
.where((result) => result.device.platformName .where((result) => result.device.platformName
@ -73,26 +82,74 @@ class _DeviceSelectionState extends State<DeviceSelection> {
.toList(); .toList();
//Saved devices list is connected ? //Saved devices list is connected ?
devicesSaved.forEach((element) { for (var element in devicesSaved) {
element.connected = scanResults element.connected = scanResults
.where((result) => result.device.remoteId.str == element.remoteId) .where((result) => result.device.remoteId.str == element.remoteId)
.length > .isNotEmpty;
0; }
});
return Scaffold( return Scaffold(
appBar: AppBar( 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: <Widget>[
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, 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( body: Column(
children: [ children: [
//List of devices saved in local storage by card devicesSaved.isNotEmpty
devicesSaved.length > 0
? Column( ? Column(
children: [ children: [
Padding( const Padding(
padding: const EdgeInsets.all(16.0), padding: EdgeInsets.all(16.0),
child: Text('Appareils sauvegardés', child: Text('Appareils sauvegardés',
style: TextStyle( style: TextStyle(
fontSize: 18.0, fontWeight: FontWeight.bold)), fontSize: 18.0, fontWeight: FontWeight.bold)),
@ -101,82 +158,82 @@ class _DeviceSelectionState extends State<DeviceSelection> {
shrinkWrap: true, shrinkWrap: true,
itemCount: devicesSaved.length, itemCount: devicesSaved.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
DevicesSaved device = devicesSaved[index]; DevicesSaved.DevicesSaved device = devicesSaved[index];
return Card( return Container(
child: ListTile( margin:
title: Row(children: [ const EdgeInsets.only(left: 10, right: 10.0),
Icon(Icons.circle, child: Card(
color: devicesSaved[index].connected child: ListTile(
? Colors.green title: Row(children: [
: Colors.orange, Icon(Icons.circle,
size: 10.0), color: devicesSaved[index].connected
Text(device.deviceName) ? Colors.green
]), : Colors.orange,
subtitle: Text(device.deviceAddress), size: 10.0),
trailing: Row( Text(device.deviceName)
mainAxisSize: MainAxisSize.min, ]),
children: [ subtitle: Text(device.deviceAddress),
GestureDetector( trailing: Row(
onTap: () async { mainAxisSize: MainAxisSize.min,
devicesSaved.removeAt(index); children: [
await saveLocal(devicesSaved); GestureDetector(
setState(() { onTap: () async {
devicesSaved = devicesSaved; devicesSaved.removeAt(index);
}); await DevicesSaved.saveLocal(
}, devicesSaved);
child: Icon(Icons.delete)), setState(() {
GestureDetector( devicesSaved = devicesSaved;
onTap: () { });
//Stop scanning when a device is selected },
FlutterBluePlus.stopScan(); child: const Icon(Icons.delete)),
setState(() { GestureDetector(
isScanning = false; onTap: () {
}); if (checkIsScanning(
Navigator.push( context, isScanning)) {
context, Navigator.push(
MaterialPageRoute( context,
builder: (context) => MaterialPageRoute(
DeviceAdvertizinScan( 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, deviceName: device.deviceName,
deviceAddress: deviceAddress:
device.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(), : Container(),
Padding( const Padding(
padding: const EdgeInsets.all(16.0), padding: EdgeInsets.all(16.0),
child: Text('Recherche d\' appareils BLE', child: Text('Recherche d\' appareils BLE',
style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold)), style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold)),
), ),
@ -184,7 +241,7 @@ class _DeviceSelectionState extends State<DeviceSelection> {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: TextFormField( child: TextFormField(
controller: tecSearch, controller: tecSearch,
decoration: InputDecoration( decoration: const InputDecoration(
hintText: 'Rechercher un appareil par nom', hintText: 'Rechercher un appareil par nom',
prefixIcon: Icon(Icons.search), prefixIcon: Icon(Icons.search),
), ),
@ -193,7 +250,7 @@ class _DeviceSelectionState extends State<DeviceSelection> {
}, },
), ),
), ),
SizedBox(height: 16.0), const SizedBox(height: 16.0),
Expanded( Expanded(
child: ListView.builder( child: ListView.builder(
itemCount: filteredDevices.length, itemCount: filteredDevices.length,
@ -206,13 +263,12 @@ class _DeviceSelectionState extends State<DeviceSelection> {
GestureDetector( GestureDetector(
onTap: () async { onTap: () async {
if (devicesSaved if (devicesSaved
.where((element) => .where((element) =>
element.deviceAddress == element.deviceAddress ==
result.device.remoteId.str) result.device.remoteId.str)
.length > .isNotEmpty) {
0) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( const SnackBar(
content: content:
Text('Cet appareil est déjà sauvegardé'), Text('Cet appareil est déjà sauvegardé'),
), ),
@ -220,64 +276,55 @@ class _DeviceSelectionState extends State<DeviceSelection> {
return; return;
} }
devicesSaved.add(DevicesSaved( devicesSaved.add(DevicesSaved.DevicesSaved(
deviceName: result.device.platformName, deviceName: result.device.platformName,
deviceAddress: result.device.remoteId.str, deviceAddress: result.device.remoteId.str,
remoteId: result.device.remoteId.str, remoteId: result.device.remoteId.str,
)); ));
await saveLocal(devicesSaved); await DevicesSaved.saveLocal(devicesSaved);
setState(() { setState(() {
devicesSaved = devicesSaved; devicesSaved = devicesSaved;
}); });
; ;
}, },
child: Icon(Icons.add), child: const Icon(Icons.add),
), ),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
//Stop scanning when a device is selected if (checkIsScanning(context, isScanning)) {
FlutterBluePlus.stopScan(); Navigator.push(
setState(() { context,
isScanning = false; MaterialPageRoute(
}); builder: (context) => DeviceAdvertizinScan(
Navigator.push( deviceName: result.device.platformName,
context, deviceAddress: result.device.remoteId.str,
MaterialPageRoute( remoteId: result.device.remoteId.str,
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( GestureDetector(
onTap: () { onTap: () {
//Stop scanning when a device is selected if (checkIsScanning(context, isScanning)) return;
FlutterBluePlus.stopScan();
setState(() {
isScanning = false;
});
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => DeviceFlowMap( builder: (context) => DeviceFlowMap(
title: 'GPS Map Flowpoint',
deviceName: result.device.platformName, deviceName: result.device.platformName,
deviceAddress: result.device.remoteId.str, deviceAddress: result.device.remoteId.str,
), ),
), ),
); );
}, },
child: Icon(Icons.map_outlined), child: const Icon(Icons.map_outlined),
), ),
], ],
), ),
title: Row(children: [ 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) Text(result.device.platformName)
]), ]),
subtitle: Text(result.device.remoteId.str), subtitle: Text(result.device.remoteId.str),
@ -290,14 +337,12 @@ class _DeviceSelectionState extends State<DeviceSelection> {
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
backgroundColor: Colors.blue, backgroundColor: Colors.blue,
onPressed: () { onPressed: () {
print('Scanning');
setState(() { setState(() {
isScanning = !isScanning; isScanning = !isScanning;
if (isScanning) { if (isScanning) {
FlutterBluePlus.startScan(); bleProvider.startScan();
} else { } else {
FlutterBluePlus.stopScan(); bleProvider.stopScan();
} }
}); });
}, },
@ -307,3 +352,16 @@ class _DeviceSelectionState extends State<DeviceSelection> {
); );
} }
} }
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;
}
}

42
lib/function.dart Normal file
View File

@ -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<File> generateCsv(List<GeoPosition> positions) async {
List<List<dynamic>> 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';
}
}

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gps_map_flowpoint/class/BLEProvider.dart'; import 'package:eagletracker/class/BLEProvider.dart';
import 'package:gps_map_flowpoint/deviceSelection.dart'; import 'package:eagletracker/deviceSelection.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
void main() { void main() {
@ -9,7 +9,7 @@ void main() {
providers: [ providers: [
ChangeNotifierProvider(create: (_) => BLEProvider()), ChangeNotifierProvider(create: (_) => BLEProvider()),
], ],
child: MyApp(), child: const MyApp(),
), ),
); );
} }
@ -22,12 +22,12 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: 'GPS Map Flowpoint', title: 'Eagle Tr@cker',
theme: ThemeData( theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true, useMaterial3: true,
), ),
home: const DeviceSelection(title: 'GPS Map Flowpoint'), home: const DeviceSelection(),
); );
} }
} }

View File

@ -7,12 +7,14 @@ import Foundation
import flutter_blue_plus import flutter_blue_plus
import geolocator_apple import geolocator_apple
import path_provider_foundation
import shared_preferences_foundation import shared_preferences_foundation
import url_launcher_macos import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin")) FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
} }

View File

@ -49,6 +49,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" 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: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -110,6 +118,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.32.7" 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: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -304,6 +320,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" 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: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@ -542,13 +582,13 @@ packages:
source: hosted source: hosted
version: "1.3.2" version: "1.3.2"
url_launcher: url_launcher:
dependency: transitive dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.6" version: "6.3.0"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:

View File

@ -1,4 +1,4 @@
name: gps_map_flowpoint name: eagletracker
description: "A new Flutter project." description: "A new Flutter project."
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
@ -40,6 +40,10 @@ dependencies:
geolocator: ^12.0.0 geolocator: ^12.0.0
shared_preferences: ^2.2.3 shared_preferences: ^2.2.3
provider: ^6.1.2 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: dev_dependencies:
flutter_test: flutter_test: