Resolves #49 Location tracking
This commit is contained in:
		
							
								
								
									
										17
									
								
								android/.project
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								android/.project
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <projectDescription> | ||||||
|  | 	<name>android</name> | ||||||
|  | 	<comment>Project android created by Buildship.</comment> | ||||||
|  | 	<projects> | ||||||
|  | 	</projects> | ||||||
|  | 	<buildSpec> | ||||||
|  | 		<buildCommand> | ||||||
|  | 			<name>org.eclipse.buildship.core.gradleprojectbuilder</name> | ||||||
|  | 			<arguments> | ||||||
|  | 			</arguments> | ||||||
|  | 		</buildCommand> | ||||||
|  | 	</buildSpec> | ||||||
|  | 	<natures> | ||||||
|  | 		<nature>org.eclipse.buildship.core.gradleprojectnature</nature> | ||||||
|  | 	</natures> | ||||||
|  | </projectDescription> | ||||||
							
								
								
									
										6
									
								
								android/app/.classpath
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								android/app/.classpath
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <classpath> | ||||||
|  | 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/> | ||||||
|  | 	<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/> | ||||||
|  | 	<classpathentry kind="output" path="bin/default"/> | ||||||
|  | </classpath> | ||||||
							
								
								
									
										23
									
								
								android/app/.project
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								android/app/.project
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <projectDescription> | ||||||
|  | 	<name>app</name> | ||||||
|  | 	<comment>Project app created by Buildship.</comment> | ||||||
|  | 	<projects> | ||||||
|  | 	</projects> | ||||||
|  | 	<buildSpec> | ||||||
|  | 		<buildCommand> | ||||||
|  | 			<name>org.eclipse.jdt.core.javabuilder</name> | ||||||
|  | 			<arguments> | ||||||
|  | 			</arguments> | ||||||
|  | 		</buildCommand> | ||||||
|  | 		<buildCommand> | ||||||
|  | 			<name>org.eclipse.buildship.core.gradleprojectbuilder</name> | ||||||
|  | 			<arguments> | ||||||
|  | 			</arguments> | ||||||
|  | 		</buildCommand> | ||||||
|  | 	</buildSpec> | ||||||
|  | 	<natures> | ||||||
|  | 		<nature>org.eclipse.jdt.core.javanature</nature> | ||||||
|  | 		<nature>org.eclipse.buildship.core.gradleprojectnature</nature> | ||||||
|  | 	</natures> | ||||||
|  | </projectDescription> | ||||||
| @@ -4,11 +4,13 @@ import io.flutter.app.FlutterApplication; | |||||||
| import io.flutter.plugin.common.PluginRegistry; | import io.flutter.plugin.common.PluginRegistry; | ||||||
| import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; | import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; | ||||||
| import io.flutter.plugins.GeneratedPluginRegistrant; | import io.flutter.plugins.GeneratedPluginRegistrant; | ||||||
|  | import be.tramckrijte.workmanager.WorkmanagerPlugin; | ||||||
|  |  | ||||||
| public class Application extends FlutterApplication implements PluginRegistrantCallback { | public class Application extends FlutterApplication implements PluginRegistrantCallback { | ||||||
|     @Override |     @Override | ||||||
|     public void onCreate() { |     public void onCreate() { | ||||||
|         super.onCreate(); |         super.onCreate(); | ||||||
|  |         WorkmanagerPlugin.setPluginRegistrantCallback(this); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -27,6 +27,8 @@ import 'package:share/share.dart'; | |||||||
| import 'plugins/dynamic_multi_column_layout.dart'; | import 'plugins/dynamic_multi_column_layout.dart'; | ||||||
| import 'plugins/spoiler_card.dart'; | import 'plugins/spoiler_card.dart'; | ||||||
| import 'package:uni_links/uni_links.dart'; | import 'package:uni_links/uni_links.dart'; | ||||||
|  | import 'package:workmanager/workmanager.dart' as workManager; | ||||||
|  | import 'package:geolocator/geolocator.dart'; | ||||||
|  |  | ||||||
| import 'utils/logger.dart'; | import 'utils/logger.dart'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -163,8 +163,6 @@ class ConnectionManager { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   Future _disconnect() { |   Future _disconnect() { | ||||||
|     Completer completer = Completer(); |     Completer completer = Completer(); | ||||||
|     if (!isConnected) { |     if (!isConnected) { | ||||||
|   | |||||||
| @@ -2,4 +2,157 @@ part of '../main.dart'; | |||||||
|  |  | ||||||
| class LocationManager { | class LocationManager { | ||||||
|  |  | ||||||
|  |   static final LocationManager _instance = LocationManager | ||||||
|  |       ._internal(); | ||||||
|  |  | ||||||
|  |   factory LocationManager() { | ||||||
|  |     return _instance; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   LocationManager._internal() { | ||||||
|  |     init(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   final int defaultUpdateIntervalMinutes = 15; | ||||||
|  |   final String alarmId = "ha_location_background"; | ||||||
|  |   Duration _updateInterval; | ||||||
|  |   bool _isEnabled; | ||||||
|  |  | ||||||
|  |   void init() async { | ||||||
|  |     SharedPreferences prefs = await SharedPreferences.getInstance(); | ||||||
|  |     await prefs.reload(); | ||||||
|  |     _updateInterval = Duration(minutes: prefs.getInt("location-interval") ?? | ||||||
|  |         defaultUpdateIntervalMinutes); | ||||||
|  |     _isEnabled = prefs.getBool("location-enabled") ?? false; | ||||||
|  |     if (_isEnabled) { | ||||||
|  |       _startLocationService(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void setSettings(bool enabled, int interval) async { | ||||||
|  |     SharedPreferences prefs = await SharedPreferences.getInstance(); | ||||||
|  |     if (interval != _updateInterval.inMinutes) { | ||||||
|  |       prefs.setInt("location-interval", interval); | ||||||
|  |       _updateInterval = Duration(minutes: interval); | ||||||
|  |     } | ||||||
|  |     if (enabled && !_isEnabled) { | ||||||
|  |       Logger.d("Enabling location service"); | ||||||
|  |       SharedPreferences prefs = await SharedPreferences.getInstance(); | ||||||
|  |       prefs.setBool("location-enabled", enabled); | ||||||
|  |       _isEnabled = true; | ||||||
|  |       _startLocationService(); | ||||||
|  |       updateDeviceLocation(); | ||||||
|  |     } else if (!enabled && _isEnabled) { | ||||||
|  |       Logger.d("Disabling location service"); | ||||||
|  |       SharedPreferences prefs = await SharedPreferences.getInstance(); | ||||||
|  |       prefs.setBool("location-enabled", enabled); | ||||||
|  |       _isEnabled = false; | ||||||
|  |       _stopLocationService(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void _startLocationService() async { | ||||||
|  |     Logger.d("Scheduling location update for every ${_updateInterval | ||||||
|  |         .inMinutes} minutes..."); | ||||||
|  |     await workManager.Workmanager.registerPeriodicTask( | ||||||
|  |       alarmId, | ||||||
|  |       "simplePeriodicTask", | ||||||
|  |       frequency: _updateInterval, | ||||||
|  |       existingWorkPolicy: workManager.ExistingWorkPolicy.replace, | ||||||
|  |       constraints: workManager.Constraints( | ||||||
|  |         networkType: workManager.NetworkType.connected | ||||||
|  |       ) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void _stopLocationService() async { | ||||||
|  |     Logger.d("Canceling previous schedule if any..."); | ||||||
|  |     await workManager.Workmanager.cancelByUniqueName(alarmId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void updateDeviceLocation() async { | ||||||
|  |     if (_isEnabled) { | ||||||
|  |       if (ConnectionManager().webhookId != null && | ||||||
|  |           ConnectionManager().webhookId.isNotEmpty) { | ||||||
|  |         String url = "${ConnectionManager() | ||||||
|  |             .httpWebHost}/api/webhook/${ConnectionManager().webhookId}"; | ||||||
|  |         Map<String, String> headers = {}; | ||||||
|  |         Logger.d("[Location] Getting device location..."); | ||||||
|  |         Position location = await Geolocator().getCurrentPosition( | ||||||
|  |             desiredAccuracy: LocationAccuracy.medium); | ||||||
|  |         Logger.d("[Location] Got location: ${location.latitude} ${location | ||||||
|  |             .longitude}. Sending home..."); | ||||||
|  |         int battery = DateTime | ||||||
|  |             .now() | ||||||
|  |             .hour; | ||||||
|  |         var data = { | ||||||
|  |           "type": "update_location", | ||||||
|  |           "data": { | ||||||
|  |             "gps": [location.latitude, location.longitude], | ||||||
|  |             "gps_accuracy": location.accuracy, | ||||||
|  |             "battery": battery | ||||||
|  |           } | ||||||
|  |         }; | ||||||
|  |         headers["Content-Type"] = "application/json"; | ||||||
|  |         await http.post( | ||||||
|  |             url, | ||||||
|  |             headers: headers, | ||||||
|  |             body: json.encode(data) | ||||||
|  |         ); | ||||||
|  |         Logger.d("[Location] ...done."); | ||||||
|  |       } else { | ||||||
|  |         Logger.d("[Location] No webhook id. Aborting"); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       Logger.d("[Location] Location tracking is disabled"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void updateDeviceLocationIsolate() { | ||||||
|  |   workManager.Workmanager.executeTask((backgroundTask, _) { | ||||||
|  |     print("[Location isolate] Started: $backgroundTask"); | ||||||
|  |     //Completer completer = Completer(); | ||||||
|  |  | ||||||
|  |     SharedPreferences.getInstance().then((prefs){ | ||||||
|  |       print("[Location isolate] loading settings"); | ||||||
|  |       String webhookId = prefs.getString('app-webhook-id'); | ||||||
|  |       String domain = prefs.getString('hassio-domain'); | ||||||
|  |       String port = prefs.getString('hassio-port'); | ||||||
|  |       String httpWebHost = | ||||||
|  |           "${prefs.getString('hassio-res-protocol')}://$domain:$port"; | ||||||
|  |       if (webhookId != null && webhookId.isNotEmpty) { | ||||||
|  |         Logger.d("[Location isolate] Getting device location..."); | ||||||
|  |         Geolocator().getCurrentPosition(desiredAccuracy: LocationAccuracy.medium).then((location) { | ||||||
|  |           Logger.d("[Location isolate] Got location: ${location.latitude} ${location.longitude}. Sending home..."); | ||||||
|  |           int battery = DateTime.now().hour; | ||||||
|  |           String url = "$httpWebHost/api/webhook/$webhookId"; | ||||||
|  |           Map<String, String> headers = {}; | ||||||
|  |           headers["Content-Type"] = "application/json"; | ||||||
|  |           var data = { | ||||||
|  |             "type": "update_location", | ||||||
|  |             "data": { | ||||||
|  |               "gps": [location.latitude, location.longitude], | ||||||
|  |               "gps_accuracy": location.accuracy, | ||||||
|  |               "battery": battery | ||||||
|  |             } | ||||||
|  |           }; | ||||||
|  |           http.post( | ||||||
|  |               url, | ||||||
|  |               headers: headers, | ||||||
|  |               body: json.encode(data) | ||||||
|  |           ).catchError((e) { | ||||||
|  |             print("[Location isolate] Error sending data: ${e.toString()}"); | ||||||
|  |           }).then((_) { | ||||||
|  |             print("[Location isolate] done!"); | ||||||
|  |           }); | ||||||
|  |         }); | ||||||
|  |       } else { | ||||||
|  |         print("[Location isolate] No webhook id. Aborting"); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     return Future.value(true); | ||||||
|  |   }); | ||||||
| } | } | ||||||
| @@ -109,6 +109,7 @@ class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObse | |||||||
|     _subscribe().then((_) { |     _subscribe().then((_) { | ||||||
|       ConnectionManager().init(loadSettings: true, forceReconnect: true).then((__){ |       ConnectionManager().init(loadSettings: true, forceReconnect: true).then((__){ | ||||||
|         _fetchData(); |         _fetchData(); | ||||||
|  |         LocationManager(); | ||||||
|         StartupUserMessagesManager().checkMessagesToShow(); |         StartupUserMessagesManager().checkMessagesToShow(); | ||||||
|       }, onError: (e) { |       }, onError: (e) { | ||||||
|         _setErrorState(e); |         _setErrorState(e); | ||||||
|   | |||||||
| @@ -9,9 +9,40 @@ class ConfigPanelWidget extends StatefulWidget { | |||||||
|  |  | ||||||
| class _ConfigPanelWidgetState extends State<ConfigPanelWidget> { | class _ConfigPanelWidgetState extends State<ConfigPanelWidget> { | ||||||
|  |  | ||||||
|  |   int _locationInterval = LocationManager().defaultUpdateIntervalMinutes; | ||||||
|  |   bool _locationTrackingEnabled = false; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void initState() { |   void initState() { | ||||||
|     super.initState(); |     super.initState(); | ||||||
|  |     _loadSettings(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   _loadSettings() async { | ||||||
|  |     SharedPreferences prefs = await SharedPreferences.getInstance(); | ||||||
|  |     await prefs.reload(); | ||||||
|  |     SharedPreferences.getInstance().then((prefs) { | ||||||
|  |       setState(() { | ||||||
|  |         _locationTrackingEnabled = prefs.getBool("location-enabled") ?? false; | ||||||
|  |         _locationInterval = prefs.getInt("location-interval") ?? LocationManager().defaultUpdateIntervalMinutes; | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void incLocationInterval() { | ||||||
|  |     if (_locationInterval < 720) { | ||||||
|  |       setState(() { | ||||||
|  |         _locationInterval = _locationInterval + 1; | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void decLocationInterval() { | ||||||
|  |     if (_locationInterval > 1) { | ||||||
|  |       setState(() { | ||||||
|  |         _locationInterval = _locationInterval - 1; | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   restart() { |   restart() { | ||||||
| @@ -92,17 +123,55 @@ class _ConfigPanelWidgetState extends State<ConfigPanelWidget> { | |||||||
|                     ) |                     ) | ||||||
|                   ], |                   ], | ||||||
|                 ), |                 ), | ||||||
|  |                 Divider(), | ||||||
|  |                 Text("Location tracking", style: TextStyle(fontSize: Sizes.largeFontSize-2)), | ||||||
|  |                 Container(height: Sizes.rowPadding,), | ||||||
|  |                 Row( | ||||||
|  |                   children: <Widget>[ | ||||||
|  |                     Text("Enable device location tracking"), | ||||||
|  |                     Switch( | ||||||
|  |                       value: _locationTrackingEnabled, | ||||||
|  |                       onChanged: (value) { | ||||||
|  |                         SharedPreferences.getInstance().then((prefs) => prefs.setBool("location-enabled", value)); | ||||||
|  |                         setState(() { | ||||||
|  |                           _locationTrackingEnabled = value; | ||||||
|  |                         }); | ||||||
|  |                       }, | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |                 Container(height: Sizes.rowPadding,), | ||||||
|  |                 Text("Location update interval in minutes:"), | ||||||
|  |                 Row( | ||||||
|  |                   mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |                   mainAxisSize: MainAxisSize.max, | ||||||
|  |                   children: <Widget>[ | ||||||
|  |                     //Expanded(child: Container(),), | ||||||
|  |                     FlatButton( | ||||||
|  |                       padding: EdgeInsets.all(0.0), | ||||||
|  |                       child: Text("+", style: TextStyle(fontSize: Sizes.largeFontSize)), | ||||||
|  |                       onPressed: () => incLocationInterval(), | ||||||
|  |                     ), | ||||||
|  |                     Text("$_locationInterval", style: TextStyle(fontSize: Sizes.largeFontSize)), | ||||||
|  |                     FlatButton( | ||||||
|  |                       padding: EdgeInsets.all(0.0), | ||||||
|  |                       child: Text("-", style: TextStyle(fontSize: Sizes.largeFontSize)), | ||||||
|  |                       onPressed: () => decLocationInterval(), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ) | ||||||
|               ], |               ], | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|         ), |         ), | ||||||
|         LinkToWebConfig(name: "Home AssistantConfiguration", url: ConnectionManager().httpWebHost+"/config"), |         LinkToWebConfig(name: "Home Assistant configuration", url: ConnectionManager().httpWebHost+"/config"), | ||||||
|       ], |       ], | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void dispose() { |   void dispose() { | ||||||
|  |     LocationManager().setSettings(_locationTrackingEnabled, _locationInterval); | ||||||
|     super.dispose(); |     super.dispose(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,6 +25,8 @@ dependencies: | |||||||
|   flutter_secure_storage: ^3.3.1+1 |   flutter_secure_storage: ^3.3.1+1 | ||||||
|   device_info: ^0.4.0+3 |   device_info: ^0.4.0+3 | ||||||
|   flutter_local_notifications: ^0.8.4 |   flutter_local_notifications: ^0.8.4 | ||||||
|  |   geolocator: ^5.1.4+2 | ||||||
|  |   workmanager: ^0.1.3 | ||||||
|   share: |   share: | ||||||
|     git: |     git: | ||||||
|       url: https://github.com/d-silveira/flutter-share.git |       url: https://github.com/d-silveira/flutter-share.git | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user