From dce93966e3938e15a4eeb443ee0d037e739770ba Mon Sep 17 00:00:00 2001 From: estevez-dev Date: Tue, 7 Jul 2020 22:49:51 +0300 Subject: [PATCH] Resolves #571, Resolves #490, Resolves #517 --- android/app/build.gradle | 1 + .../hassclient/LocationUpdatesService.java | 68 +----- .../hassclient/LocationUpdatesWorker.java | 22 ++ .../hassclient/LocationUtils.java | 32 +++ .../hassclient/MainActivity.java | 13 +- lib/main.dart | 8 - lib/managers/app_settings.dart | 43 +++- lib/managers/location_manager.class.dart | 229 ----------------- lib/pages/main/main.page.dart | 1 - .../settings/integration_settings.part.dart | 231 ++++++++++-------- pubspec.yaml | 5 +- 11 files changed, 235 insertions(+), 418 deletions(-) delete mode 100644 lib/managers/location_manager.class.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index c30904e..2c6f1ad 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -80,6 +80,7 @@ flutter { dependencies { implementation 'com.google.firebase:firebase-analytics:17.2.2' implementation 'com.google.firebase:firebase-messaging:20.2.0' + implementation 'com.google.android.gms:play-services-location:17.0.0' implementation 'androidx.work:work-runtime:2.3.4' implementation "androidx.concurrent:concurrent-futures:1.0.0" testImplementation 'junit:junit:4.12' diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesService.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesService.java index 728f436..975bdf8 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesService.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesService.java @@ -1,9 +1,7 @@ package com.keyboardcrumbs.hassclient; -import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; -import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.location.Location; @@ -14,7 +12,6 @@ import android.os.IBinder; import android.os.Looper; import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; import androidx.work.BackoffPolicy; import androidx.work.Constraints; import androidx.work.Data; @@ -35,20 +32,8 @@ import java.util.concurrent.TimeUnit; public class LocationUpdatesService extends Service { - private static final String PACKAGE_NAME = - "com.keyboardcrumbs.hassclient"; - private static final String TAG = LocationUpdatesService.class.getSimpleName(); - private static final String CHANNEL_ID = "location_service"; - - private static final String EXTRA_STARTED_FROM_NOTIFICATION = PACKAGE_NAME + - ".started_from_notification"; - - //private final IBinder mBinder = new LocalBinder(); - - private static final int NOTIFICATION_ID = 954311; - private NotificationManager mNotificationManager; private LocationRequest mLocationRequest; @@ -59,8 +44,6 @@ public class LocationUpdatesService extends Service { private Handler mServiceHandler; - private Location mLocation; - public LocationUpdatesService() { } @@ -86,7 +69,7 @@ public class LocationUpdatesService extends Service { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { CharSequence name = "Location updates"; NotificationChannel mChannel = - new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_LOW); + new NotificationChannel(LocationUtils.SERVICE_NOTIFICATION_CHANNEL_ID, name, NotificationManager.IMPORTANCE_LOW); mNotificationManager.createNotificationChannel(mChannel); } @@ -95,15 +78,8 @@ public class LocationUpdatesService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "Service started"); - boolean startedFromNotification = intent.getBooleanExtra(EXTRA_STARTED_FROM_NOTIFICATION, - false); - // We got here because the user decided to remove location updates from the notification. - if (startedFromNotification) { - stopSelf(); - } else { - requestLocationUpdates(); - } + requestLocationUpdates(); return START_STICKY; } @@ -132,7 +108,7 @@ public class LocationUpdatesService extends Service { mLocationRequest.setPriority(priority); mLocationRequest.setInterval(requestInterval); mLocationRequest.setFastestInterval(requestInterval); - startForeground(NOTIFICATION_ID, getNotification()); + startForeground(LocationUtils.SERVICE_NOTIFICATION_ID, LocationUtils.getNotification(this, null, LocationUtils.SERVICE_NOTIFICATION_CHANNEL_ID)); try { mFusedLocationClient.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.myLooper()); @@ -141,40 +117,10 @@ public class LocationUpdatesService extends Service { } } - private Notification getNotification() { - Intent intent = new Intent(this, LocationUpdatesService.class); - - CharSequence text = LocationUtils.getLocationText(mLocation); - - intent.putExtra(EXTRA_STARTED_FROM_NOTIFICATION, true); - - PendingIntent servicePendingIntent = PendingIntent.getService(this, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - - PendingIntent activityPendingIntent = PendingIntent.getActivity(this, 0, - new Intent(this, MainActivity.class), 0); - - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID) - .addAction(R.drawable.blank_icon, "Open app", - activityPendingIntent) - .addAction(R.drawable.blank_icon, "Stop tracking", - servicePendingIntent) - .setContentText(text) - .setPriority(-1) - .setContentTitle(LocationUtils.getLocationTitle(mLocation)) - .setOngoing(true) - .setSmallIcon(R.drawable.mini_icon) - .setWhen(System.currentTimeMillis()); - - return builder.build(); - } - private void onNewLocation(Location location) { Log.i(TAG, "New location: " + location); - mLocation = location; - - mNotificationManager.notify(NOTIFICATION_ID, getNotification()); + mNotificationManager.notify(LocationUtils.SERVICE_NOTIFICATION_ID, LocationUtils.getNotification(this, location, LocationUtils.SERVICE_NOTIFICATION_CHANNEL_ID)); Constraints constraints = new Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) @@ -182,9 +128,9 @@ public class LocationUpdatesService extends Service { Data locationData = new Data.Builder() .putInt(SendDataHomeWorker.DATA_TYPE_KEY, SendDataHomeWorker.DATA_TYPE_LOCATION) - .putDouble("Lat", mLocation.getLatitude()) - .putDouble("Long", mLocation.getLongitude()) - .putFloat("Acc", mLocation.getAccuracy()) + .putDouble("Lat", location.getLatitude()) + .putDouble("Long", location.getLongitude()) + .putFloat("Acc", location.getAccuracy()) .build(); diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesWorker.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesWorker.java index 5de1fdd..7dcee13 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesWorker.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUpdatesWorker.java @@ -1,7 +1,11 @@ package com.keyboardcrumbs.hassclient; +import android.app.AlarmManager; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.content.Context; import android.location.Location; +import android.os.Build; import android.os.Looper; import androidx.annotation.NonNull; @@ -25,6 +29,8 @@ import com.google.common.util.concurrent.ListenableFuture; import java.util.concurrent.TimeUnit; +import static android.content.Context.NOTIFICATION_SERVICE; + public class LocationUpdatesWorker extends ListenableWorker { private Context currentContext; @@ -78,6 +84,22 @@ public class LocationUpdatesWorker extends ListenableWorker { WorkManager .getInstance(getApplicationContext()) .enqueueUniqueWork("SendLocationUpdate", ExistingWorkPolicy.REPLACE, uploadWorkRequest); + if (LocationUtils.showNotification(currentContext)) { + NotificationManager notificationManager; + if (android.os.Build.VERSION.SDK_INT >= 23) { + notificationManager = currentContext.getSystemService(NotificationManager.class); + } else { + notificationManager = (NotificationManager)currentContext.getSystemService(Context.NOTIFICATION_SERVICE); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + CharSequence name = "Location updates"; + NotificationChannel mChannel = + new NotificationChannel(LocationUtils.WORKER_NOTIFICATION_CHANNEL_ID, name, NotificationManager.IMPORTANCE_LOW); + + notificationManager.createNotificationChannel(mChannel); + } + notificationManager.notify(LocationUtils.WORKER_NOTIFICATION_ID, LocationUtils.getNotification(currentContext, location, LocationUtils.WORKER_NOTIFICATION_CHANNEL_ID)); + } finish(); completer.set(Result.success()); } diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUtils.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUtils.java index af028a8..60521e7 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUtils.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/LocationUtils.java @@ -1,10 +1,13 @@ package com.keyboardcrumbs.hassclient; +import android.app.Notification; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.location.Location; import android.os.Build; +import androidx.core.app.NotificationCompat; import androidx.work.ExistingPeriodicWorkPolicy; import androidx.work.PeriodicWorkRequest; import androidx.work.WorkManager; @@ -18,6 +21,12 @@ class LocationUtils { static final String KEY_REQUESTING_LOCATION_UPDATES = "flutter.location-updates-state"; static final String KEY_LOCATION_UPDATE_INTERVAL = "flutter.location-updates-interval"; static final String KEY_LOCATION_UPDATE_PRIORITY = "flutter.location-updates-priority"; + static final String KEY_LOCATION_SHOW_NOTIFICATION = "flutter.location-updates-show-notification"; + + static final String WORKER_NOTIFICATION_CHANNEL_ID = "location_worker"; + static final int WORKER_NOTIFICATION_ID = 954322; + static final String SERVICE_NOTIFICATION_CHANNEL_ID = "location_service"; + static final int SERVICE_NOTIFICATION_ID = 954311; static final String LOCATION_WORK_NAME = "HALocationWorker"; @@ -40,6 +49,10 @@ class LocationUtils { return (int) context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).getLong(KEY_LOCATION_UPDATE_PRIORITY, 102); } + static boolean showNotification(Context context) { + return context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).getBoolean(KEY_LOCATION_SHOW_NOTIFICATION, true); + } + static void setLocationUpdatesState(Context context, int locationUpdatesState) { context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE) .edit() @@ -70,6 +83,25 @@ class LocationUtils { } } + static Notification getNotification(Context context, Location location, String channelId) { + CharSequence text = LocationUtils.getLocationText(location); + + PendingIntent activityPendingIntent = PendingIntent.getActivity(context, 0, + new Intent(context, MainActivity.class), 0); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId) + .addAction(R.drawable.blank_icon, "Open HA Client", + activityPendingIntent) + .setContentText(text) + .setPriority(-1) + .setContentTitle(LocationUtils.getLocationTitle(location)) + .setOngoing(true) + .setSmallIcon(R.drawable.mini_icon) + .setWhen(System.currentTimeMillis()); + + return builder.build(); + } + static void startWorker(Context context, long interval) { PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder(LocationUpdatesWorker.class, interval, TimeUnit.MILLISECONDS) .build(); diff --git a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java index e106f7c..a598237 100644 --- a/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java +++ b/android/app/src/main/java/com/keyboardcrumbs/hassclient/MainActivity.java @@ -9,10 +9,10 @@ import io.flutter.embedding.engine.FlutterEngine; import io.flutter.plugins.GeneratedPluginRegistrant; import android.Manifest; +import android.app.NotificationManager; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Bundle; @@ -82,6 +82,10 @@ public class MainActivity extends FlutterActivity { stopLocationUpdates(); result.success(""); break; + case "cancelOldLocationWorker": + WorkManager.getInstance(this).cancelAllWorkByTag("haclocation"); + result.success(""); + break; } } ); @@ -107,6 +111,13 @@ public class MainActivity extends FlutterActivity { Intent myService = new Intent(MainActivity.this, LocationUpdatesService.class); stopService(myService); WorkManager.getInstance(this).cancelUniqueWork(LocationUtils.LOCATION_WORK_NAME); + NotificationManager notificationManager; + if (android.os.Build.VERSION.SDK_INT >= 23) { + notificationManager = getSystemService(NotificationManager.class); + } else { + notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); + } + notificationManager.cancel(LocationUtils.WORKER_NOTIFICATION_ID); LocationUtils.setLocationUpdatesState(this, LocationUtils.LOCATION_UPDATES_DISABLED); } diff --git a/lib/main.dart b/lib/main.dart index c6cc319..3539c9a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -22,9 +22,6 @@ import 'package:device_info/device_info.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import 'plugins/dynamic_multi_column_layout.dart'; import 'plugins/spoiler_card.dart'; -import 'package:workmanager/workmanager.dart' as workManager; -import 'package:geolocator/geolocator.dart'; -import 'package:battery/battery.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter_webview_plugin/flutter_webview_plugin.dart' as standaloneWebview; import 'package:webview_flutter/webview_flutter.dart'; @@ -117,7 +114,6 @@ part 'pages/entity.page.dart'; part 'utils/mdi.class.dart'; part 'entity_collection.class.dart'; part 'managers/auth_manager.class.dart'; -part 'managers/location_manager.class.dart'; part 'managers/mobile_app_integration_manager.class.dart'; part 'managers/connection_manager.class.dart'; part 'managers/device_info_manager.class.dart'; @@ -230,10 +226,6 @@ class _HAClientAppState extends State { _currentTheme = event.theme; }); }); - workManager.Workmanager.initialize( - updateDeviceLocationIsolate, - isInDebugMode: false - ); super.initState(); } diff --git a/lib/managers/app_settings.dart b/lib/managers/app_settings.dart index 60ed778..9a181a9 100644 --- a/lib/managers/app_settings.dart +++ b/lib/managers/app_settings.dart @@ -8,6 +8,8 @@ class AppSettings { static const AUTH_TOKEN_KEY = 'llt'; + static const platform = const MethodChannel('com.keyboardcrumbs.hassclient/native'); + static final AppSettings _instance = AppSettings._internal(); factory AppSettings() { @@ -31,10 +33,7 @@ class AppSettings { bool nextAlarmSensorCreated = false; DisplayMode displayMode; AppTheme appTheme; - final int defaultLocationUpdateIntervalMinutes = 20; - final int defaultActiveLocationUpdateIntervalSeconds = 900; - Duration locationUpdateInterval; - bool locationTrackingEnabled = false; + final int defaultLocationUpdateIntervalSeconds = 900; bool get isAuthenticated => longLivedToken != null; bool get isTempAuthenticated => tempToken != null; @@ -50,6 +49,7 @@ class AppSettings { await Hive.openBox(DEFAULT_HIVE_BOX); Logger.d('Loading settings...'); SharedPreferences prefs = await SharedPreferences.getInstance(); + await migrate(prefs); _domain = prefs.getString('hassio-domain'); _port = prefs.getString('hassio-port'); webhookId = prefs.getString('app-webhook-id'); @@ -60,10 +60,6 @@ class AppSettings { "${prefs.getString('hassio-protocol')}://$_domain:$_port/api/websocket"; httpWebHost = "${prefs.getString('hassio-res-protocol')}://$_domain:$_port"; - locationUpdateInterval = Duration(minutes: prefs.getInt("location-interval") ?? - defaultLocationUpdateIntervalMinutes); - locationTrackingEnabled = prefs.getBool("location-enabled") ?? false; - nextAlarmSensorCreated = prefs.getBool("next-alarm-sensor-created") ?? false; longLivedToken = Hive.box(DEFAULT_HIVE_BOX).get(AUTH_TOKEN_KEY); oauthUrl = "$httpWebHost/auth/authorize?client_id=${Uri.encodeComponent( 'https://ha-client.app')}&redirect_uri=${Uri @@ -72,6 +68,37 @@ class AppSettings { } } + Future migrate(SharedPreferences prefs) async { + //Migrating to new location tracking. TODO: Remove when no version 1.2.0 (and older) in the wild + if (prefs.getBool("location-tracking-migrated") == null) { + Logger.d("[MIGRATION] Migrating to new location tracking..."); + bool oldLocationTrackingEnabled = prefs.getBool("location-enabled") ?? false; + if (oldLocationTrackingEnabled) { + await platform.invokeMethod('cancelOldLocationWorker'); + await prefs.setInt("location-updates-state", 2); //Setting new location tracking mode to worker + await prefs.setInt("location-updates-priority", 100); //Setting location accuracy to high + int oldLocationTrackingInterval = prefs.getInt("location-interval") ?? 0; + if (oldLocationTrackingInterval < 15) { + oldLocationTrackingInterval = 15; + } + await prefs.setInt("location-updates-interval", oldLocationTrackingInterval * 60); //moving old interval in minutes to new interval in seconds + try { + await platform.invokeMethod('startLocationService'); + } catch (e) { + await prefs.setInt("location-updates-state", 0); //Disabling location tracking if can't start + } + } else { + Logger.d("[MIGRATION] Old location tracking was disabled"); + await prefs.setInt("location-updates-state", 0); //Setting new location tracking mode to disabled + } + await prefs.setBool("location-tracking-migrated", true); + } + //Migrating from integration without next alarm sensor. TODO: remove when no version 1.1.2 (and older) in the wild + nextAlarmSensorCreated = prefs.getBool("next-alarm-sensor-created") ?? false; + //Done + Logger.d("[MIGRATION] Done."); + } + Future loadSingle(String key) async { SharedPreferences prefs = await SharedPreferences.getInstance(); return prefs.get('$key'); diff --git a/lib/managers/location_manager.class.dart b/lib/managers/location_manager.class.dart deleted file mode 100644 index 8bc6522..0000000 --- a/lib/managers/location_manager.class.dart +++ /dev/null @@ -1,229 +0,0 @@ -part of '../main.dart'; - -class LocationManager { - - static final LocationManager _instance = LocationManager - ._internal(); - - factory LocationManager() { - return _instance; - } - - LocationManager._internal() { - init(); - } - - final String backgroundTaskId = "haclocationtask0"; - final String backgroundTaskTag = "haclocation"; - - void init() async { - if (AppSettings().locationTrackingEnabled) { - await _startLocationService(); - } - } - - setSettings(bool enabled, int interval) async { - if (interval != AppSettings().locationUpdateInterval.inMinutes) { - await _stopLocationService(); - } - AppSettings().save({ - 'location-interval': interval, - 'location-enabled': enabled - }); - AppSettings().locationUpdateInterval = Duration(minutes: interval); - AppSettings().locationTrackingEnabled = enabled; - if (enabled && !AppSettings().locationTrackingEnabled) { - Logger.d("Starting location tracking"); - await _startLocationService(); - } else if (!enabled && AppSettings().locationTrackingEnabled) { - Logger.d("Stopping location tracking..."); - await _stopLocationService(); - } - } - - _startLocationService() async { - String webhookId = AppSettings().webhookId; - String httpWebHost = AppSettings().httpWebHost; - if (webhookId != null && webhookId.isNotEmpty) { - Duration interval; - int delayFactor; - int taskCount; - Logger.d("Starting location update for every ${AppSettings().locationUpdateInterval - .inMinutes} minutes..."); - if (AppSettings().locationUpdateInterval.inMinutes == 10) { - interval = Duration(minutes: 20); - taskCount = 2; - delayFactor = 10; - } else if (AppSettings().locationUpdateInterval.inMinutes == 5) { - interval = Duration(minutes: 15); - taskCount = 3; - delayFactor = 5; - } else { - interval = AppSettings().locationUpdateInterval; - taskCount = 1; - delayFactor = 0; - } - for (int i = 1; i <= taskCount; i++) { - int delay = i*delayFactor; - Logger.d("Scheduling location update task #$i for every ${interval.inMinutes} minutes in $delay minutes..."); - await workManager.Workmanager.registerPeriodicTask( - "$backgroundTaskId$i", - "haClientLocationTracking-0$i", - tag: backgroundTaskTag, - inputData: { - "webhookId": webhookId, - "httpWebHost": httpWebHost - }, - frequency: interval, - initialDelay: Duration(minutes: delay), - existingWorkPolicy: workManager.ExistingWorkPolicy.keep, - backoffPolicy: workManager.BackoffPolicy.linear, - backoffPolicyDelay: interval, - constraints: workManager.Constraints( - networkType: workManager.NetworkType.connected, - ), - ); - } - } - } - - _stopLocationService() async { - Logger.d("Canceling previous schedule if any..."); - await workManager.Workmanager.cancelAll(); - } - - updateDeviceLocation() async { - try { - Logger.d("[Foreground location] Started"); - Geolocator geolocator = Geolocator(); - var battery = Battery(); - String webhookId = AppSettings().webhookId; - String httpWebHost = AppSettings().httpWebHost; - if (webhookId != null && webhookId.isNotEmpty) { - Logger.d("[Foreground location] Getting battery level..."); - int batteryLevel = await battery.batteryLevel; - Logger.d("[Foreground location] Getting device location..."); - Position position = await geolocator.getCurrentPosition( - desiredAccuracy: LocationAccuracy.high, - locationPermissionLevel: GeolocationPermission.locationAlways - ); - if (position != null) { - Logger.d("[Foreground location] Location: ${position.latitude} ${position.longitude}. Accuracy: ${position.accuracy}. (${position.timestamp})"); - String url = "$httpWebHost/api/webhook/$webhookId"; - Map data = { - "type": "update_location", - "data": { - "gps": [position.latitude, position.longitude], - "gps_accuracy": position.accuracy, - "battery": batteryLevel ?? 100 - } - }; - Logger.d("[Foreground location] Sending data home..."); - http.Response response = await http.post( - url, - headers: {"Content-Type": "application/json"}, - body: json.encode(data) - ); - if (response.statusCode >= 300) { - Logger.e('Foreground location update error: ${response.body}'); - } - Logger.d("[Foreground location] Got HTTP ${response.statusCode}"); - } else { - Logger.d("[Foreground location] No location. Aborting."); - } - } - } catch (e, stack) { - Logger.e('Foreground location error: ${e.toSTring()}', stacktrace: stack); - } - } - -} - -void updateDeviceLocationIsolate() { - workManager.Workmanager.executeTask((backgroundTask, data) async { - //print("[Background $backgroundTask] Started"); - Geolocator geolocator = Geolocator(); - var battery = Battery(); - String webhookId = data["webhookId"]; - String httpWebHost = data["httpWebHost"]; - //String logData = '==> ${DateTime.now()} [Background $backgroundTask]:'; - //print("[Background $backgroundTask] Getting path for log file..."); - //final logFileDirectory = await getExternalStorageDirectory(); - //print("[Background $backgroundTask] Opening log file..."); - //File logFile = File('${logFileDirectory.path}/ha-client-background-log.txt'); - //print("[Background $backgroundTask] Log file path: ${logFile.path}"); - if (webhookId != null && webhookId.isNotEmpty) { - String url = "$httpWebHost/api/webhook/$webhookId"; - Map headers = {}; - headers["Content-Type"] = "application/json"; - Map data = { - "type": "update_location", - "data": { - "gps": [], - "gps_accuracy": 0, - "battery": 100 - } - }; - //print("[Background $backgroundTask] Getting battery level..."); - int batteryLevel; - try { - batteryLevel = await battery.batteryLevel; - //print("[Background $backgroundTask] Got battery level: $batteryLevel"); - } catch(e) { - //print("[Background $backgroundTask] Error getting battery level: $e. Setting zero"); - batteryLevel = 0; - //logData += 'Battery: error, $e'; - } - if (batteryLevel != null) { - data["data"]["battery"] = batteryLevel; - //logData += 'Battery: success, $batteryLevel'; - }/* else { - logData += 'Battery: error, level is null'; - }*/ - Position location; - try { - location = await geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high, locationPermissionLevel: GeolocationPermission.locationAlways); - if (location != null && location.latitude != null) { - //logData += ' || Location: success, ${location.latitude} ${location.longitude} (${location.timestamp})'; - data["data"]["gps"] = [location.latitude, location.longitude]; - data["data"]["gps_accuracy"] = location.accuracy; - try { - http.Response response = await http.post( - url, - headers: headers, - body: json.encode(data) - ); - /*if (response.statusCode >= 200 && response.statusCode < 300) { - logData += ' || Post: success, ${response.statusCode}'; - } else { - logData += ' || Post: error, ${response.statusCode}'; - }*/ - } catch(e) { - //logData += ' || Post: error, $e'; - } - }/* else { - logData += ' || Location: error, location is null'; - }*/ - } catch (e) { - //print("[Background $backgroundTask] Location error: $e"); - //logData += ' || Location: error, $e'; - } - }/* else { - logData += 'Not configured'; - }*/ - //print("[Background $backgroundTask] Writing log data..."); - /*try { - var fileMode; - if (logFile.existsSync() && logFile.lengthSync() < 5000000) { - fileMode = FileMode.append; - } else { - fileMode = FileMode.write; - } - await logFile.writeAsString('$logData\n', mode: fileMode); - } catch (e) { - print("[Background $backgroundTask] Error writing log: $e"); - } - print("[Background $backgroundTask] Finished.");*/ - return true; - }); -} \ No newline at end of file diff --git a/lib/pages/main/main.page.dart b/lib/pages/main/main.page.dart index d8d01aa..03de9c3 100644 --- a/lib/pages/main/main.page.dart +++ b/lib/pages/main/main.page.dart @@ -56,7 +56,6 @@ class _MainPageState extends State with WidgetsBindingObserver, Ticker SharedPreferences.getInstance().then((prefs) { HomeAssistant().currentDashboardPath = prefs.getString('lovelace_dashboard_url') ?? HomeAssistant.DEFAULT_DASHBOARD; _fetchData(useCache: true); - LocationManager(); StartupUserMessagesManager().checkMessagesToShow(); MobileAppIntegrationManager.checkAppRegistration(); }); diff --git a/lib/pages/settings/integration_settings.part.dart b/lib/pages/settings/integration_settings.part.dart index f1e4b32..972f704 100644 --- a/lib/pages/settings/integration_settings.part.dart +++ b/lib/pages/settings/integration_settings.part.dart @@ -19,12 +19,10 @@ class _IntegrationSettingsPageState extends State { 105: "Passive (last known location)", }; - int _locationInterval = AppSettings().defaultLocationUpdateIntervalMinutes; - int _activeLocationInterval = AppSettings().defaultActiveLocationUpdateIntervalSeconds; + Duration _locationInterval; bool _locationTrackingEnabled = false; - bool _foregroundLocationTrackingEnabled = false; bool _wait = false; - bool _changedHere = false; + bool _showNotification = true; int _accuracy = 102; @override @@ -39,75 +37,72 @@ class _IntegrationSettingsPageState extends State { await prefs.reload(); SharedPreferences.getInstance().then((prefs) { setState(() { - _locationTrackingEnabled = prefs.getBool("location-enabled") ?? false; _accuracy = prefs.getInt("location-updates-priority") ?? 102; - _foregroundLocationTrackingEnabled = (prefs.getInt("location-updates-state") ?? 0) > 0; - _locationInterval = prefs.getInt("location-interval") ?? - AppSettings().defaultLocationUpdateIntervalMinutes; - _activeLocationInterval = prefs.getInt("location-updates-interval") ?? - AppSettings().defaultActiveLocationUpdateIntervalSeconds; - if (_locationInterval < 15) { - _locationInterval = 15; - } else if (_locationInterval % 5 != 0) { - _locationInterval = 5 * (_locationInterval ~/ 5); - } + _locationTrackingEnabled = (prefs.getInt("location-updates-state") ?? 0) > 0; + _showNotification = prefs.getBool("location-updates-show-notification") ?? true; + _locationInterval = Duration(seconds: prefs.getInt("location-updates-interval") ?? + AppSettings().defaultLocationUpdateIntervalSeconds); }); }); } void _incLocationInterval() { - if (_locationInterval < 720) { + if (_locationInterval.inSeconds < 60) { setState(() { - _locationInterval = _locationInterval + 5; - _changedHere = true; + _locationInterval = _locationInterval + Duration(seconds: 5); + }); + } else if (_locationInterval.inMinutes < 15) { + setState(() { + _locationInterval = _locationInterval + Duration(minutes: 1); + }); + } else if (_locationInterval.inMinutes < 60) { + setState(() { + _locationInterval = _locationInterval + Duration(minutes: 5); + }); + } else if (_locationInterval.inHours < 4) { + setState(() { + _locationInterval = _locationInterval + Duration(minutes: 10); + }); + } else if (_locationInterval.inHours < 48) { + setState(() { + _locationInterval = _locationInterval + Duration(hours: 1); }); } } void _decLocationInterval() { - if (_locationInterval > 15) { - setState(() { - _locationInterval = _locationInterval - 5; - _changedHere = true; - }); - } - } - - void _incActiveLocationInterval() { - if (_activeLocationInterval < 7200) { - setState(() { - _activeLocationInterval = _activeLocationInterval + 5; - _changedHere = true; - }); - } - } - - void _decActiveLocationInterval() { - if (_activeLocationInterval > 5) { - setState(() { - _activeLocationInterval = _activeLocationInterval - 5; - _changedHere = true; - }); + if (_locationInterval.inSeconds > 5) { + if (_locationInterval.inSeconds <= 60) { + setState(() { + _locationInterval = _locationInterval - Duration(seconds: 5); + }); + } else if (_locationInterval.inMinutes <= 15) { + setState(() { + _locationInterval = _locationInterval - Duration(minutes: 1); + }); + } else if (_locationInterval.inMinutes <= 60) { + setState(() { + _locationInterval = _locationInterval - Duration(minutes: 5); + }); + } else if (_locationInterval.inHours <= 4) { + setState(() { + _locationInterval = _locationInterval - Duration(minutes: 10); + }); + } else if (_locationInterval.inHours > 4) { + setState(() { + _locationInterval = _locationInterval - Duration(hours: 1); + }); + } } } _switchLocationTrackingState(bool state) async { - if (state) { - await LocationManager().updateDeviceLocation(); - } - await LocationManager().setSettings(_locationTrackingEnabled, _locationInterval); - setState(() { - _wait = false; - }); - } - - _switchForegroundLocationTrackingState(bool state) async { - await AppSettings().save({'location-updates-interval': _activeLocationInterval, 'location-updates-priority': _accuracy}); + await AppSettings().save({'location-updates-interval': _locationInterval.inSeconds, 'location-updates-priority': _accuracy, 'location-updates-show-notification': _showNotification}); if (state) { try { await platform.invokeMethod('startLocationService'); } catch (e) { - _foregroundLocationTrackingEnabled = false; + _locationTrackingEnabled = false; } } else { await platform.invokeMethod('stopLocationService'); @@ -117,18 +112,61 @@ class _IntegrationSettingsPageState extends State { }); } + String _formatInterval() { + String result = ""; + Duration leftToShow = Duration(seconds: _locationInterval?.inSeconds ?? 0); + if (leftToShow.inHours > 0) { + result += "${leftToShow.inHours} h "; + leftToShow -= Duration(hours: leftToShow.inHours); + } + if (leftToShow.inMinutes > 0) { + result += "${leftToShow.inMinutes} m"; + leftToShow -= Duration(hours: leftToShow.inMinutes); + } + if (leftToShow.inSeconds > 0) { + result += "${leftToShow.inSeconds} s"; + leftToShow -= Duration(hours: leftToShow.inSeconds); + } + return result; + } + + Widget _getNoteWidget(String text, bool important) { + return Text( + text, + style: important ? Theme.of(context).textTheme.caption.copyWith(color: Theme.of(context).errorColor) : Theme.of(context).textTheme.caption, + softWrap: true, + ); + } + + Widget _getNotes() { + List notes = []; + if (_locationTrackingEnabled) { + notes.add(_getNoteWidget('* Stop location tracking to change settings', false)); + } + if ((_locationInterval?.inMinutes ?? 15) < 15) { + notes.add(_getNoteWidget('* Notification is mandatory for location updates with interval less than every 15 minutes', false)); + if (_accuracy < 102) { + notes.add(_getNoteWidget('* Battery consumption will be noticeable', true)); + } + } + if (notes.isEmpty) { + return Container(width: 0, height: 0); + } + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: notes, + ); + } + @override Widget build(BuildContext context) { return ListView( scrollDirection: Axis.vertical, padding: const EdgeInsets.all(20.0), children: [ - Text("Passive location tracking", style: Theme.of(context).textTheme.title), - Text("Works in background not affecting phone battery. Usually sends last known device location. Can't be more frequent than once in 15 minutes.", - style: Theme.of(context).textTheme.caption, - softWrap: true, - ), - Container(height: Sizes.rowPadding,), + Text("Location tracking", style: Theme.of(context).textTheme.title), + Container(height: Sizes.rowPadding), Row( children: [ Text("Enable"), @@ -145,64 +183,27 @@ class _IntegrationSettingsPageState extends State { ], ), Container(height: Sizes.rowPadding), - Text("Send device location every"), - Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - children: [ - //Expanded(child: Container(),), - FlatButton( - padding: EdgeInsets.all(0.0), - child: Text("-", style: Theme.of(context).textTheme.title), - onPressed: () => _decLocationInterval(), - ), - Text("$_locationInterval minutes", style: Theme.of(context).textTheme.title), - FlatButton( - padding: EdgeInsets.all(0.0), - child: Text("+", style: Theme.of(context).textTheme.title), - onPressed: () => _incLocationInterval(), - ), - ], - ), - Container(height: Sizes.rowPadding), - Text("Active location tracking", style: Theme.of(context).textTheme.title), - Container(height: Sizes.rowPadding), - Row( - children: [ - Text("Enable"), - Switch( - value: _foregroundLocationTrackingEnabled, - onChanged: _wait ? null : (value) { - setState(() { - _foregroundLocationTrackingEnabled = value; - _wait = true; - }); - _switchForegroundLocationTrackingState(value); - }, - ), - ], - ), - Container(height: Sizes.rowPadding), Text("Accuracy:", style: Theme.of(context).textTheme.body2), Container(height: Sizes.rowPadding), DropdownButton( value: _accuracy, iconSize: 30.0, isExpanded: true, + disabledHint: Text(locationAccuracy[_accuracy]), items: locationAccuracy.keys.map((value) { return new DropdownMenuItem( value: value, child: Text('${locationAccuracy[value]}'), ); }).toList(), - onChanged: _foregroundLocationTrackingEnabled ? null : (val) { + onChanged: _locationTrackingEnabled ? null : (val) { setState(() { _accuracy = val; }); }, ), Container(height: Sizes.rowPadding), - Text("Update intervals"), + Text("Update interval"), Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, @@ -210,25 +211,43 @@ class _IntegrationSettingsPageState extends State { //Expanded(child: Container(),), FlatButton( padding: EdgeInsets.all(0.0), - child: Text("-", style: Theme.of(context).textTheme.title), - onPressed: _foregroundLocationTrackingEnabled ? null : () => _decActiveLocationInterval(), + child: Text("-", style: Theme.of(context).textTheme.headline4), + onPressed: _locationTrackingEnabled ? null : () => _decLocationInterval(), + ), + Expanded( + child: Text(_formatInterval(), + textAlign: TextAlign.center, + style: _locationTrackingEnabled ? Theme.of(context).textTheme.title.copyWith(color: HAClientTheme().getDisabledStateColor(context)) : Theme.of(context).textTheme.title), ), - Text("$_activeLocationInterval seconds", - style: _foregroundLocationTrackingEnabled ? Theme.of(context).textTheme.title.copyWith(color: HAClientTheme().getDisabledStateColor(context)) : Theme.of(context).textTheme.title), FlatButton( padding: EdgeInsets.all(0.0), - child: Text("+", style: Theme.of(context).textTheme.title), - onPressed: _foregroundLocationTrackingEnabled ? null : () => _incActiveLocationInterval(), + child: Text("+", style: Theme.of(context).textTheme.headline4), + onPressed: _locationTrackingEnabled ? null : () => _incLocationInterval(), ), ], ), + Container(height: Sizes.rowPadding), + Row( + children: [ + Text("Show notification"), + Switch( + value: _showNotification, + onChanged: (_locationTrackingEnabled || (_locationInterval?.inMinutes ?? 0) < 15) ? null : (value) { + setState(() { + _showNotification = value; + }); + }, + ), + ], + ), + Container(height: Sizes.rowPadding), + _getNotes() ] ); } @override void dispose() { - LocationManager().setSettings(_locationTrackingEnabled, _locationInterval); super.dispose(); } } diff --git a/pubspec.yaml b/pubspec.yaml index 9e824df..a0b30dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: hass_client description: Home Assistant Android Client -version: 1.2.0+1200 +version: 1.3.0+1300 environment: @@ -26,9 +26,6 @@ dependencies: hive: ^1.4.1+1 hive_flutter: ^0.3.0+2 device_info: ^0.4.2+4 - geolocator: ^5.3.1 - workmanager: ^0.2.2 - battery: ^1.0.0 firebase_crashlytics: ^0.1.3+3 syncfusion_flutter_core: ^18.1.52 syncfusion_flutter_gauges: ^18.1.52