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_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

View File

@ -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<String, dynamic> json) {
@ -14,6 +16,16 @@ class GeoPosition {
latitude: json['latitude'],
longitude: json['longitude'],
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_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<DeviceAdvertizinScan> {
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<BLEProvider>(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<DeviceAdvertizinScan> {
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,

View File

@ -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<DeviceFlowMap> {
TextEditingController tecTakingPoint = TextEditingController();
MapController mapController = MapController(
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
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<BLEProvider>(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<DeviceFlowMap> {
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(
children: [
const Text('Temps de prise de point (en secondes)'),
TextField(
controller: tecTakingPoint,
keyboardType: TextInputType.number,
),
],
),
actions: <Widget>[
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(
body: Stack(children: [
OSMFlutter(
controller: mapController,
onMapIsReady: (bool value) async {
if (value) {
Future.delayed(const Duration(milliseconds: 500), () async {
await mapController.currentLocation();
});
}
},
osmOption: OSMOption(
userTrackingOption: UserTrackingOption(
enableTracking: true,
unFollowUser: false,
),
zoomOption: ZoomOption(
zoomOption: const ZoomOption(
initZoom: 14,
minZoomLevel: 3,
maxZoomLevel: 19,
stepZoom: 1.0,
),
userLocationMarker: UserLocationMaker(
personMarker: MarkerIcon(
personMarker: const MarkerIcon(
icon: Icon(
Icons.location_history_rounded,
Icons.place_rounded,
color: Colors.red,
size: 48,
),
),
directionArrowMarker: MarkerIcon(
directionArrowMarker: const MarkerIcon(
icon: Icon(
Icons.double_arrow,
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: ${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,
),
),
const SizedBox(height: 10),
SizedBox(
height: 10,
width: 180,
child: LinearProgressIndicator(
value: progression / 100,
backgroundColor: Colors.grey[300],
valueColor:
const AlwaysStoppedAnimation<Color>(Colors.blue),
),
),
],
),
),
),
]),
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<DeviceFlowMap> {
actions: <Widget>[
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<DeviceFlowMap> {
);
},
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_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<DeviceSelection> createState() => _DeviceSelectionState();
@ -18,14 +19,28 @@ class _DeviceSelectionState extends State<DeviceSelection> {
TextEditingController tecSearch = TextEditingController();
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
@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<DeviceSelection> {
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<DeviceSelection> {
);
}
});
FlutterBluePlus.onScanResults.listen(
(results) {
setState(() {
scanResults = results;
});
},
onError: (e) => print(e),
);
}
@override
Widget build(BuildContext context) {
final bleProvider = Provider.of<BLEProvider>(context);
scanResults = bleProvider.scanResults;
// Filtered list of devices
List<ScanResult> filteredDevices = scanResults
.where((result) => result.device.platformName
@ -73,26 +82,74 @@ class _DeviceSelectionState extends State<DeviceSelection> {
.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;
});
.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: <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,
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,8 +158,11 @@ class _DeviceSelectionState extends State<DeviceSelection> {
shrinkWrap: true,
itemCount: devicesSaved.length,
itemBuilder: (context, index) {
DevicesSaved device = devicesSaved[index];
return Card(
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,
@ -119,64 +179,61 @@ class _DeviceSelectionState extends State<DeviceSelection> {
GestureDetector(
onTap: () async {
devicesSaved.removeAt(index);
await saveLocal(devicesSaved);
await DevicesSaved.saveLocal(
devicesSaved);
setState(() {
devicesSaved = devicesSaved;
});
},
child: Icon(Icons.delete)),
child: const Icon(Icons.delete)),
GestureDetector(
onTap: () {
//Stop scanning when a device is selected
FlutterBluePlus.stopScan();
setState(() {
isScanning = false;
});
if (checkIsScanning(
context, isScanning)) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
DeviceAdvertizinScan(
deviceName: device.deviceName,
deviceName:
device.deviceName,
deviceAddress:
device.deviceAddress,
remoteId: device.remoteId,
)),
);
}
},
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)) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DeviceFlowMap(
title: 'GPS Map Flowpoint',
deviceName: device.deviceName,
deviceAddress: device.deviceAddress,
deviceAddress:
device.deviceAddress,
),
),
);
}
},
child: Icon(Icons.map_outlined),
child: const 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<DeviceSelection> {
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<DeviceSelection> {
},
),
),
SizedBox(height: 16.0),
const SizedBox(height: 16.0),
Expanded(
child: ListView.builder(
itemCount: filteredDevices.length,
@ -209,10 +266,9 @@ class _DeviceSelectionState extends State<DeviceSelection> {
.where((element) =>
element.deviceAddress ==
result.device.remoteId.str)
.length >
0) {
.isNotEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
const SnackBar(
content:
Text('Cet appareil est déjà sauvegardé'),
),
@ -220,26 +276,22 @@ class _DeviceSelectionState extends State<DeviceSelection> {
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;
});
if (checkIsScanning(context, isScanning)) {
Navigator.push(
context,
MaterialPageRoute(
@ -250,34 +302,29 @@ class _DeviceSelectionState extends State<DeviceSelection> {
),
),
);
}
},
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<DeviceSelection> {
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<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: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(),
);
}
}

View File

@ -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"))
}

View File

@ -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:

View File

@ -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: