Resolves #571, Resolves #490, Resolves #517

This commit is contained in:
estevez-dev 2020-07-07 22:49:51 +03:00
parent 3a7f3db6cd
commit dce93966e3
11 changed files with 235 additions and 418 deletions

View File

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

View File

@ -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();

View File

@ -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());
}

View File

@ -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();

View File

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

View File

@ -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<HAClientApp> {
_currentTheme = event.theme;
});
});
workManager.Workmanager.initialize(
updateDeviceLocationIsolate,
isInDebugMode: false
);
super.initState();
}

View File

@ -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<dynamic> loadSingle(String key) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.get('$key');

View File

@ -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<String, String> 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;
});
}

View File

@ -56,7 +56,6 @@ class _MainPageState extends State<MainPage> 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();
});

View File

@ -19,12 +19,10 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
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<IntegrationSettingsPage> {
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<IntegrationSettingsPage> {
});
}
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<Widget> 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: <Widget>[
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: <Widget>[
Text("Enable"),
@ -145,64 +183,27 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
],
),
Container(height: Sizes.rowPadding),
Text("Send device location every"),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
//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: <Widget>[
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<int>(
value: _accuracy,
iconSize: 30.0,
isExpanded: true,
disabledHint: Text(locationAccuracy[_accuracy]),
items: locationAccuracy.keys.map((value) {
return new DropdownMenuItem<int>(
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<IntegrationSettingsPage> {
//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: <Widget>[
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();
}
}

View File

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