@@ -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'
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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());
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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');
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
@@ -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();
 | 
			
		||||
        });
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user