diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3a1c935..843d521 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -14,6 +14,9 @@ PODS: - permission_handler_apple (9.3.0): - Flutter - Polyline (5.1.0) + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS - Tangram-es (0.17.1) - url_launcher_ios (0.0.1): - Flutter @@ -25,6 +28,7 @@ DEPENDENCIES: - flutter_osm_plugin (from `.symlinks/plugins/flutter_osm_plugin/ios`) - geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`) - 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`) SPEC REPOS: @@ -45,6 +49,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/geolocator_apple/ios" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" @@ -56,6 +62,7 @@ SPEC CHECKSUMS: geolocator_apple: 6cbaf322953988e009e5ecb481f07efece75c450 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 Polyline: 2a1f29f87f8d9b7de868940f4f76deb8c678a5b1 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 Tangram-es: 628b634f7fc09d2217469b9914de00d9de49ff9d url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 Yams: e10dae147f517ed57ecae37c5e8681bdf8fcab65 diff --git a/lib/class/BLEProvider.dart b/lib/class/BLEProvider.dart new file mode 100644 index 0000000..ce7ed73 --- /dev/null +++ b/lib/class/BLEProvider.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; + +class BLEProvider with ChangeNotifier { + List _scanResults = []; + + List get scanResults => _scanResults; + + BLEProvider() { + startScan(); + } + + void startScan() { + FlutterBluePlus.startScan(); + FlutterBluePlus.onScanResults.listen( + (results) { + _scanResults = results; + notifyListeners(); + }, + onError: (e) => print(e), + ); + } + + void stopScan() { + FlutterBluePlus.stopScan(); + } +} diff --git a/lib/class/devicesSaved.dart b/lib/class/devicesSaved.dart new file mode 100644 index 0000000..e079ffa --- /dev/null +++ b/lib/class/devicesSaved.dart @@ -0,0 +1,46 @@ +import 'dart:convert'; + +import 'package:shared_preferences/shared_preferences.dart'; + +class DevicesSaved { + String deviceName = ''; + String deviceAddress = ''; + String remoteId = ''; + bool connected = false; + + DevicesSaved( + {required this.deviceName, + required this.deviceAddress, + required this.remoteId}); + + DevicesSaved.fromJson(Map json) { + deviceName = json['deviceName']; + deviceAddress = json['deviceAddress']; + remoteId = json['remoteId']; + } + + Map toJson() { + final Map data = {}; + data['deviceName'] = deviceName; + data['deviceAddress'] = deviceAddress; + data['remoteId'] = remoteId; + return data; + } +} + +Future> restoreLocal() async { + final prefs = await SharedPreferences.getInstance(); + final String? encodedData = prefs.getString('deviceSaved'); + if (encodedData != null) { + final List decodedData = jsonDecode(encodedData); + return decodedData.map((json) => DevicesSaved.fromJson(json)).toList(); + } + return []; +} + +Future saveLocal(List deviceSaved) async { + final prefs = await SharedPreferences.getInstance(); + final String encodedData = + jsonEncode(deviceSaved.map((obj) => obj.toJson()).toList()); + await prefs.setString('deviceSaved', encodedData); +} diff --git a/lib/class/geoPosition.dart b/lib/class/geoPosition.dart new file mode 100644 index 0000000..81632a1 --- /dev/null +++ b/lib/class/geoPosition.dart @@ -0,0 +1,19 @@ +class GeoPosition { + final double latitude; + final double longitude; + final double altitude; + + GeoPosition({ + required this.latitude, + required this.longitude, + required this.altitude, + }); + + factory GeoPosition.fromJson(Map json) { + return GeoPosition( + latitude: json['latitude'], + longitude: json['longitude'], + altitude: json['altitude'], + ); + } +} diff --git a/lib/deviceAdvertizinScan.dart b/lib/deviceAdvertizinScan.dart index 688c7a7..adb330f 100644 --- a/lib/deviceAdvertizinScan.dart +++ b/lib/deviceAdvertizinScan.dart @@ -5,12 +5,10 @@ import 'package:flutter_osm_plugin/flutter_osm_plugin.dart'; class DeviceAdvertizinScan extends StatefulWidget { const DeviceAdvertizinScan( {super.key, - required this.title, required this.deviceName, required this.deviceAddress, required this.remoteId}); - final String title; final String deviceName; final String deviceAddress; final String remoteId; @@ -45,13 +43,14 @@ class _DeviceAdvertizinScanState extends State { Widget build(BuildContext context) { FlutterBluePlus.onScanResults.listen((results) { setState(() { - if (results.length > 0) value = results[0].toString(); + if (results.length > 0) value = results[0].advertisementData.toString(); }); }); return Scaffold( appBar: AppBar( - title: Text(widget.title, style: const TextStyle(color: Colors.white)), + title: Text("Valeur de la trame publicitaire", + style: const TextStyle(color: Colors.white)), backgroundColor: Colors.blue, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), @@ -70,20 +69,34 @@ class _DeviceAdvertizinScanState extends State { children: [ Container( alignment: Alignment.topCenter, - child: Text('Advertizin data value for ${widget.deviceName}' + - ' with remoteId ${widget.remoteId}')), + child: Column( + children: [ + Text(widget.deviceName), + Text(widget.deviceAddress), + Text(widget.remoteId), + ], + )), Container( + margin: const EdgeInsets.all(10), + child: + //Text centré + Center( + child: Text( + "La trame publicitaire est en cours de réception et actualisée en temps réel.", + style: const TextStyle( + color: Colors.green, + ), + textAlign: TextAlign.center), + )), + Container( + margin: const EdgeInsets.all(10), + child: CircularProgressIndicator()), + Container( + margin: const EdgeInsets.all(10), alignment: Alignment.center, child: Text(value, style: const TextStyle( - color: Colors.black, - fontSize: 20, - fontWeight: FontWeight.bold))), - Container(child: CircularProgressIndicator()), - Spacer(), - Container( - child: Text( - 'La trame publicitaire est en cours de réception et actualisée en temps réel.')), + color: Colors.black, fontWeight: FontWeight.bold))), ], ), ); diff --git a/lib/deviceSelection.dart b/lib/deviceSelection.dart index ab2d6bd..1cf95f5 100644 --- a/lib/deviceSelection.dart +++ b/lib/deviceSelection.dart @@ -1,5 +1,6 @@ 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'; @@ -13,15 +14,22 @@ class DeviceSelection extends StatefulWidget { } class _DeviceSelectionState extends State { - // List to hold the scanned devices List scanResults = []; TextEditingController tecSearch = TextEditingController(); bool isScanning = false; + List devicesSaved = []; + // Initialize Bluetooth scanning and subscription in initState @override void initState() { super.initState(); + restoreLocal().then((value) { + print(value.length); + setState(() { + devicesSaved = value; + }); + }); FlutterBluePlus.isSupported.then((isSupported) { if (!isSupported) { showDialog( @@ -64,14 +72,114 @@ class _DeviceSelectionState extends State { .contains(tecSearch.text.toLowerCase())) .toList(); + //Saved devices list is connected ? + devicesSaved.forEach((element) { + element.connected = scanResults + .where((result) => result.device.remoteId.str == element.remoteId) + .length > + 0; + }); + return Scaffold( appBar: AppBar( backgroundColor: Colors.blue, - title: Text("Selection de l'appareil BLE", - style: TextStyle(color: Colors.white)), + title: Text("Eagle Tr@cker", style: TextStyle(color: Colors.white)), ), body: Column( children: [ + //List of devices saved in local storage by card + devicesSaved.length > 0 + ? Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Text('Appareils sauvegardés', + style: TextStyle( + fontSize: 18.0, fontWeight: FontWeight.bold)), + ), + ListView.builder( + 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( + deviceName: device.deviceName, + deviceAddress: + device.deviceAddress, + remoteId: device.remoteId, + )), + ); + }, + child: Icon(Icons.info_outline), + ), + 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), + child: Text('Recherche d\' appareils BLE', + style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold)), + ), Padding( padding: const EdgeInsets.all(16.0), child: TextFormField( @@ -95,6 +203,36 @@ class _DeviceSelectionState extends State { trailing: Row( mainAxisSize: MainAxisSize.min, children: [ + GestureDetector( + onTap: () async { + if (devicesSaved + .where((element) => + element.deviceAddress == + result.device.remoteId.str) + .length > + 0) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text('Cet appareil est déjà sauvegardé'), + ), + ); + return; + } + + devicesSaved.add(DevicesSaved( + deviceName: result.device.platformName, + deviceAddress: result.device.remoteId.str, + remoteId: result.device.remoteId.str, + )); + await saveLocal(devicesSaved); + setState(() { + devicesSaved = devicesSaved; + }); + ; + }, + child: Icon(Icons.add), + ), GestureDetector( onTap: () { //Stop scanning when a device is selected @@ -106,7 +244,6 @@ class _DeviceSelectionState extends State { context, MaterialPageRoute( builder: (context) => DeviceAdvertizinScan( - title: 'Advertising Frame View', deviceName: result.device.platformName, deviceAddress: result.device.remoteId.str, remoteId: result.device.remoteId.str, @@ -139,7 +276,10 @@ class _DeviceSelectionState extends State { ), ], ), - title: Text(result.device.platformName), + title: Row(children: [ + Icon(Icons.circle, color: Colors.green, size: 10.0), + Text(result.device.platformName) + ]), subtitle: Text(result.device.remoteId.str), ); }, diff --git a/lib/main.dart b/lib/main.dart index ff03f3b..436fdd5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:gps_map_flowpoint/class/BLEProvider.dart'; import 'package:gps_map_flowpoint/deviceSelection.dart'; +import 'package:provider/provider.dart'; void main() { - runApp(const MyApp()); + runApp( + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => BLEProvider()), + ], + child: MyApp(), + ), + ); } class MyApp extends StatelessWidget { @@ -13,7 +22,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, - title: 'GPD Map Flowpoint', + title: 'GPS Map Flowpoint', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 04de5e9..90c07c9 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,10 +7,12 @@ import Foundation import flutter_blue_plus import geolocator_apple +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")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index ec1c701..05f3a75 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -73,6 +73,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" fixnum: dependency: transitive description: @@ -272,6 +288,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -280,6 +304,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" permission_handler: dependency: transitive description: @@ -328,6 +376,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.1" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" + source: hosted + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -336,6 +392,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + provider: + dependency: "direct main" + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" routing_client_dart: dependency: transitive description: @@ -344,6 +408,62 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.5" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 + url: "https://pub.dev" + source: hosted + version: "2.2.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + url: "https://pub.dev" + source: hosted + version: "2.3.2" sky_engine: dependency: transitive description: flutter @@ -517,6 +637,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.1" + win32: + dependency: transitive + description: + name: win32 + sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 + url: "https://pub.dev" + source: hosted + version: "5.5.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 824d22d..c7680aa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,6 +38,8 @@ dependencies: flutter_blue_plus: ^1.32.7 flutter_osm_plugin: ^0.70.4 geolocator: ^12.0.0 + shared_preferences: ^2.2.3 + provider: ^6.1.2 dev_dependencies: flutter_test: