From d1ec4f36ccdc13fea9bc048164a41e1ab24558d2 Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Sun, 20 Oct 2019 17:54:29 +0000 Subject: [PATCH] Resolves #49 Location tracking --- android/.project | 17 ++ android/app/.classpath | 6 + android/app/.project | 23 +++ .../hassclient/Application.java | 2 + lib/main.dart | 2 + lib/managers/connection_manager.class.dart | 2 - lib/managers/location_manager.class.dart | 153 ++++++++++++++++++ lib/pages/main.page.dart | 1 + lib/panels/config_panel_widget.dart | 71 +++++++- pubspec.yaml | 2 + 10 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 android/.project create mode 100644 android/app/.classpath create mode 100644 android/app/.project diff --git a/android/.project b/android/.project new file mode 100644 index 0000000..3964dd3 --- /dev/null +++ b/android/.project @@ -0,0 +1,17 @@ + + + android + Project android created by Buildship. + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/android/app/.classpath b/android/app/.classpath new file mode 100644 index 0000000..eb19361 --- /dev/null +++ b/android/app/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/app/.project b/android/app/.project new file mode 100644 index 0000000..ac485d7 --- /dev/null +++ b/android/app/.project @@ -0,0 +1,23 @@ + + + app + Project app created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/Application.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/Application.java index 857bda2..5fde4fb 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/Application.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/Application.java @@ -4,11 +4,13 @@ import io.flutter.app.FlutterApplication; import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; import io.flutter.plugins.GeneratedPluginRegistrant; +import be.tramckrijte.workmanager.WorkmanagerPlugin; public class Application extends FlutterApplication implements PluginRegistrantCallback { @Override public void onCreate() { super.onCreate(); + WorkmanagerPlugin.setPluginRegistrantCallback(this); } @Override diff --git a/lib/main.dart b/lib/main.dart index 9905126..62386ef 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,6 +27,8 @@ import 'package:share/share.dart'; import 'plugins/dynamic_multi_column_layout.dart'; import 'plugins/spoiler_card.dart'; import 'package:uni_links/uni_links.dart'; +import 'package:workmanager/workmanager.dart' as workManager; +import 'package:geolocator/geolocator.dart'; import 'utils/logger.dart'; diff --git a/lib/managers/connection_manager.class.dart b/lib/managers/connection_manager.class.dart index b3a2765..b09efdf 100644 --- a/lib/managers/connection_manager.class.dart +++ b/lib/managers/connection_manager.class.dart @@ -163,8 +163,6 @@ class ConnectionManager { } } - - Future _disconnect() { Completer completer = Completer(); if (!isConnected) { diff --git a/lib/managers/location_manager.class.dart b/lib/managers/location_manager.class.dart index 4b3d35f..d79cbf1 100644 --- a/lib/managers/location_manager.class.dart +++ b/lib/managers/location_manager.class.dart @@ -2,4 +2,157 @@ part of '../main.dart'; 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 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 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); + }); } \ No newline at end of file diff --git a/lib/pages/main.page.dart b/lib/pages/main.page.dart index 29232fd..2e748d2 100644 --- a/lib/pages/main.page.dart +++ b/lib/pages/main.page.dart @@ -109,6 +109,7 @@ class _MainPageState extends ReceiveShareState with WidgetsBindingObse _subscribe().then((_) { ConnectionManager().init(loadSettings: true, forceReconnect: true).then((__){ _fetchData(); + LocationManager(); StartupUserMessagesManager().checkMessagesToShow(); }, onError: (e) { _setErrorState(e); diff --git a/lib/panels/config_panel_widget.dart b/lib/panels/config_panel_widget.dart index d3133bd..4da87ce 100644 --- a/lib/panels/config_panel_widget.dart +++ b/lib/panels/config_panel_widget.dart @@ -9,9 +9,40 @@ class ConfigPanelWidget extends StatefulWidget { class _ConfigPanelWidgetState extends State { + int _locationInterval = LocationManager().defaultUpdateIntervalMinutes; + bool _locationTrackingEnabled = false; + @override void 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() { @@ -92,17 +123,55 @@ class _ConfigPanelWidgetState extends State { ) ], ), + Divider(), + Text("Location tracking", style: TextStyle(fontSize: Sizes.largeFontSize-2)), + Container(height: Sizes.rowPadding,), + Row( + children: [ + 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: [ + //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 void dispose() { + LocationManager().setSettings(_locationTrackingEnabled, _locationInterval); super.dispose(); } } diff --git a/pubspec.yaml b/pubspec.yaml index 63a1684..6d454c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,6 +25,8 @@ dependencies: flutter_secure_storage: ^3.3.1+1 device_info: ^0.4.0+3 flutter_local_notifications: ^0.8.4 + geolocator: ^5.1.4+2 + workmanager: ^0.1.3 share: git: url: https://github.com/d-silveira/flutter-share.git