WIP #571
This commit is contained in:
		| @@ -16,7 +16,6 @@ import android.os.Handler; | ||||
| import android.os.HandlerThread; | ||||
| import android.os.IBinder; | ||||
| import android.os.Looper; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.core.app.NotificationCompat; | ||||
| import androidx.localbroadcastmanager.content.LocalBroadcastManager; | ||||
|  | ||||
| @@ -27,8 +26,6 @@ import com.google.android.gms.location.LocationCallback; | ||||
| import com.google.android.gms.location.LocationRequest; | ||||
| import com.google.android.gms.location.LocationResult; | ||||
| import com.google.android.gms.location.LocationServices; | ||||
| import com.google.android.gms.tasks.OnCompleteListener; | ||||
| import com.google.android.gms.tasks.Task; | ||||
|  | ||||
| /** | ||||
|  * A bound and started service that is promoted to a foreground service when location updates have | ||||
| @@ -47,14 +44,11 @@ import com.google.android.gms.tasks.Task; | ||||
| public class LocationUpdatesService extends Service { | ||||
|  | ||||
|     private static final String PACKAGE_NAME = | ||||
|             "com.google.android.gms.location.sample.locationupdatesforegroundservice"; | ||||
|             "com.keyboardcrumbs.hassclient"; | ||||
|  | ||||
|     private static final String TAG = LocationUpdatesService.class.getSimpleName(); | ||||
|  | ||||
|     /** | ||||
|      * The name of the channel for notifications. | ||||
|      */ | ||||
|     private static final String CHANNEL_ID = "channel_01"; | ||||
|     private static final String CHANNEL_ID = "location_service"; | ||||
|  | ||||
|     static final String ACTION_BROADCAST = PACKAGE_NAME + ".broadcast"; | ||||
|  | ||||
| @@ -64,18 +58,6 @@ public class LocationUpdatesService extends Service { | ||||
|  | ||||
|     private final IBinder mBinder = new LocalBinder(); | ||||
|  | ||||
|     /** | ||||
|      * The desired interval for location updates. Inexact. Updates may be more or less frequent. | ||||
|      */ | ||||
|     private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000; | ||||
|  | ||||
|     /** | ||||
|      * The fastest rate for active location updates. Updates will never be more frequent | ||||
|      * than this value. | ||||
|      */ | ||||
|     private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = | ||||
|             UPDATE_INTERVAL_IN_MILLISECONDS / 2; | ||||
|  | ||||
|     /** | ||||
|      * The identifier for the notification displayed for the foreground service. | ||||
|      */ | ||||
| @@ -167,7 +149,7 @@ public class LocationUpdatesService extends Service { | ||||
|  | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|         // Called when a client (MainActivity in case of this sample) comes to the foreground | ||||
|         // Called when MainActivity comes to the foreground | ||||
|         // and binds with this service. The service should cease to be a foreground service | ||||
|         // when that happens. | ||||
|         Log.i(TAG, "in onBind()"); | ||||
| @@ -178,7 +160,7 @@ public class LocationUpdatesService extends Service { | ||||
|  | ||||
|     @Override | ||||
|     public void onRebind(Intent intent) { | ||||
|         // Called when a client (MainActivity in case of this sample) returns to the foreground | ||||
|         // Called when MainActivity returns to the foreground | ||||
|         // and binds once again with this service. The service should cease to be a foreground | ||||
|         // service when that happens. | ||||
|         Log.i(TAG, "in onRebind()"); | ||||
| @@ -191,20 +173,10 @@ public class LocationUpdatesService extends Service { | ||||
|     public boolean onUnbind(Intent intent) { | ||||
|         Log.i(TAG, "Last client unbound from service"); | ||||
|  | ||||
|         // Called when the last client (MainActivity in case of this sample) unbinds from this | ||||
|         // Called when the last client (MainActivity ) unbinds from this | ||||
|         // service. If this method is called due to a configuration change in MainActivity, we | ||||
|         // do nothing. Otherwise, we make this service a foreground service. | ||||
|         if (!mChangingConfiguration && Utils.requestingLocationUpdates(this)) { | ||||
|             Log.i(TAG, "Starting foreground service"); | ||||
|             /* | ||||
|             // TODO(developer). If targeting O, use the following code. | ||||
|             if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) { | ||||
|                 mNotificationManager.startServiceInForeground(new Intent(this, | ||||
|                         LocationUpdatesService.class), NOTIFICATION_ID, getNotification()); | ||||
|             } else { | ||||
|                 startForeground(NOTIFICATION_ID, getNotification()); | ||||
|             } | ||||
|              */ | ||||
|             startForeground(NOTIFICATION_ID, getNotification()); | ||||
|         } | ||||
|         return true; // Ensures onRebind() is called when a client re-binds. | ||||
| @@ -215,10 +187,6 @@ public class LocationUpdatesService extends Service { | ||||
|         mServiceHandler.removeCallbacksAndMessages(null); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Makes a request for location updates. Note that in this sample we merely log the | ||||
|      * {@link SecurityException}. | ||||
|      */ | ||||
|     public void requestLocationUpdates() { | ||||
|         Log.i(TAG, "Requesting location updates"); | ||||
|         Utils.setRequestingLocationUpdates(this, true); | ||||
| @@ -227,15 +195,11 @@ public class LocationUpdatesService extends Service { | ||||
|             mFusedLocationClient.requestLocationUpdates(mLocationRequest, | ||||
|                     mLocationCallback, Looper.myLooper()); | ||||
|         } catch (SecurityException unlikely) { | ||||
|             //When we lost permission | ||||
|             Utils.setRequestingLocationUpdates(this, false); | ||||
|             Log.e(TAG, "Lost location permission. Could not request updates. " + unlikely); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Removes location updates. Note that in this sample we merely log the | ||||
|      * {@link SecurityException}. | ||||
|      */ | ||||
|     public void removeLocationUpdates() { | ||||
|         Log.i(TAG, "Removing location updates"); | ||||
|         try { | ||||
| @@ -243,8 +207,8 @@ public class LocationUpdatesService extends Service { | ||||
|             Utils.setRequestingLocationUpdates(this, false); | ||||
|             stopSelf(); | ||||
|         } catch (SecurityException unlikely) { | ||||
|             //When we lost permission | ||||
|             Utils.setRequestingLocationUpdates(this, true); | ||||
|             Log.e(TAG, "Lost location permission. Could not remove updates. " + unlikely); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -273,11 +237,9 @@ public class LocationUpdatesService extends Service { | ||||
|                 .addAction(R.drawable.blank_icon, "Stop", | ||||
|                         servicePendingIntent) | ||||
|                 .setContentText(text) | ||||
|                 .setContentTitle(Utils.getLocationTitle(this)) | ||||
|                 .setContentTitle(Utils.getLocationTitle(mLocation)) | ||||
|                 .setOngoing(true) | ||||
|                 .setPriority(Notification.PRIORITY_HIGH) | ||||
|                 .setSmallIcon(R.mipmap.ic_launcher) | ||||
|                 .setTicker(text) | ||||
|                 .setWhen(System.currentTimeMillis()); | ||||
|  | ||||
|         return builder.build(); | ||||
| @@ -286,14 +248,11 @@ public class LocationUpdatesService extends Service { | ||||
|     private void getLastLocation() { | ||||
|         try { | ||||
|             mFusedLocationClient.getLastLocation() | ||||
|                     .addOnCompleteListener(new OnCompleteListener<Location>() { | ||||
|                         @Override | ||||
|                         public void onComplete(@NonNull Task<Location> task) { | ||||
|                             if (task.isSuccessful() && task.getResult() != null) { | ||||
|                                 mLocation = task.getResult(); | ||||
|                             } else { | ||||
|                                 Log.w(TAG, "Failed to get location."); | ||||
|                             } | ||||
|                     .addOnCompleteListener(task -> { | ||||
|                         if (task.isSuccessful() && task.getResult() != null) { | ||||
|                             mLocation = task.getResult(); | ||||
|                         } else { | ||||
|                             Log.w(TAG, "Failed to get location."); | ||||
|                         } | ||||
|                     }); | ||||
|         } catch (SecurityException unlikely) { | ||||
| @@ -321,9 +280,10 @@ public class LocationUpdatesService extends Service { | ||||
|      * Sets the location request parameters. | ||||
|      */ | ||||
|     private void createLocationRequest() { | ||||
|         long interval = Utils.getLocationUpdateIntervals(getApplicationContext()); | ||||
|         mLocationRequest = new LocationRequest(); | ||||
|         mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS); | ||||
|         mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS); | ||||
|         mLocationRequest.setInterval(interval); | ||||
|         mLocationRequest.setFastestInterval(interval); | ||||
|         mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -16,46 +16,30 @@ import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.IntentFilter; | ||||
| import android.content.ServiceConnection; | ||||
| import android.content.SharedPreferences; | ||||
| import android.content.SharedPreferences.Editor; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.location.Location; | ||||
| import android.os.Bundle; | ||||
| import android.os.IBinder; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.util.Log; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import io.flutter.plugin.common.MethodCall; | ||||
| import io.flutter.plugin.common.MethodChannel; | ||||
|  | ||||
| import com.google.android.gms.tasks.OnCompleteListener; | ||||
| import com.google.android.gms.tasks.Task; | ||||
| import com.google.android.gms.common.GoogleApiAvailability; | ||||
| import com.google.android.gms.common.ConnectionResult; | ||||
| import com.google.firebase.iid.FirebaseInstanceId; | ||||
| import com.google.firebase.iid.InstanceIdResult; | ||||
| import com.google.firebase.messaging.FirebaseMessaging; | ||||
|  | ||||
| public class MainActivity extends FlutterActivity { | ||||
|  | ||||
|     private static final String TAG = MainActivity.class.getSimpleName(); | ||||
|  | ||||
|     private static final String CHANNEL = "com.keyboardcrumbs.hassclient/native"; | ||||
|  | ||||
|     // Used in checking for runtime permissions. | ||||
|     private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34; | ||||
|  | ||||
|     // The BroadcastReceiver used to listen from broadcasts from the service. | ||||
|     private MyReceiver myReceiver; | ||||
|  | ||||
|     // A reference to the service used to get location updates. | ||||
|     private LocationUpdatesService mService = null; | ||||
|  | ||||
|     // Tracks the bound state of the service. | ||||
|     private boolean mBound = false; | ||||
|  | ||||
|     // Monitors the state of the connection to the service. | ||||
|     private final ServiceConnection mServiceConnection = new ServiceConnection() { | ||||
|  | ||||
|         @Override | ||||
| @@ -76,42 +60,46 @@ public class MainActivity extends FlutterActivity { | ||||
|     public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { | ||||
|         GeneratedPluginRegistrant.registerWith(flutterEngine); | ||||
|         new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL).setMethodCallHandler( | ||||
|             new MethodChannel.MethodCallHandler() { | ||||
|                 @Override | ||||
|                 public void onMethodCall(MethodCall call, MethodChannel.Result result) { | ||||
|                 (call, result) -> { | ||||
|                     Context context = getActivity(); | ||||
|                     if (call.method.equals("getFCMToken")) { | ||||
|                         if (checkPlayServices()) { | ||||
|                             FirebaseInstanceId.getInstance().getInstanceId() | ||||
|                             .addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() { | ||||
|                                 @Override | ||||
|                                 public void onComplete(@NonNull Task<InstanceIdResult> task) { | ||||
|                                     if (task.isSuccessful()) { | ||||
|                                         String token = task.getResult().getToken(); | ||||
|                                         UpdateTokenTask updateTokenTask = new UpdateTokenTask(context); | ||||
|                                         updateTokenTask.execute(token); | ||||
|                                         result.success(token); | ||||
|                                     } else { | ||||
|                                         result.error("fcm_error", task.getException().getMessage(), null); | ||||
|                                     } | ||||
|                                 } | ||||
|                             }); | ||||
|                         } else { | ||||
|                             result.error("google_play_service_error", "Google Play Services unavailable", null); | ||||
|                         } | ||||
|                     } else if (call.method.equals("startLocationService")) { | ||||
|                         if (!checkPermissions()) { | ||||
|                             requestPermissions(); | ||||
|                         } else { | ||||
|                             mService.requestLocationUpdates(); | ||||
|                         } | ||||
|                         result.success(""); | ||||
|                     } else if (call.method.equals("stopLocationService")) { | ||||
|                         mService.removeLocationUpdates(); | ||||
|                         result.success(""); | ||||
|                     switch (call.method) { | ||||
|                         case "getFCMToken": | ||||
|                             if (checkPlayServices()) { | ||||
|                                 FirebaseInstanceId.getInstance().getInstanceId() | ||||
|                                         .addOnCompleteListener(task -> { | ||||
|                                             if (task.isSuccessful()) { | ||||
|                                                 String token = task.getResult().getToken(); | ||||
|                                                 UpdateTokenTask updateTokenTask = new UpdateTokenTask(context); | ||||
|                                                 updateTokenTask.execute(token); | ||||
|                                                 result.success(token); | ||||
|                                             } else { | ||||
|                                                 Exception ex = task.getException(); | ||||
|                                                 if (ex != null) { | ||||
|                                                     result.error("fcm_error", ex.getMessage(), null); | ||||
|                                                 } else { | ||||
|                                                     result.error("fcm_error", "Unknown", null); | ||||
|                                                 } | ||||
|  | ||||
|                                             } | ||||
|                                         }); | ||||
|                             } else { | ||||
|                                 result.error("google_play_service_error", "Google Play Services unavailable", null); | ||||
|                             } | ||||
|                             break; | ||||
|                         case "startLocationService": | ||||
|                             if (checkPermissions()) { | ||||
|                                 requestPermissions(); | ||||
|                             } else { | ||||
|                                 mService.requestLocationUpdates(); | ||||
|                             } | ||||
|                             result.success(""); | ||||
|                             break; | ||||
|                         case "stopLocationService": | ||||
|                             mService.removeLocationUpdates(); | ||||
|                             result.success(""); | ||||
|                             break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @@ -123,9 +111,8 @@ public class MainActivity extends FlutterActivity { | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         myReceiver = new MyReceiver(); | ||||
|         // Check that the user hasn't revoked permissions by going to Settings. | ||||
|         if (Utils.requestingLocationUpdates(this)) { | ||||
|             if (!checkPermissions()) { | ||||
|             if (checkPermissions()) { | ||||
|                 requestPermissions(); | ||||
|             } | ||||
|         } | ||||
| @@ -134,8 +121,6 @@ public class MainActivity extends FlutterActivity { | ||||
|     @Override | ||||
|     protected void onStart() { | ||||
|         super.onStart(); | ||||
|         // Bind to the service. If the service is in foreground mode, this signals to the service | ||||
|         // that since this activity is in the foreground, the service can exit foreground mode. | ||||
|         bindService(new Intent(this, LocationUpdatesService.class), mServiceConnection, | ||||
|                 Context.BIND_AUTO_CREATE); | ||||
|     } | ||||
| @@ -156,112 +141,39 @@ public class MainActivity extends FlutterActivity { | ||||
|     @Override | ||||
|     protected void onStop() { | ||||
|         if (mBound) { | ||||
|             // Unbind from the service. This signals to the service that this activity is no longer | ||||
|             // in the foreground, and the service can respond by promoting itself to a foreground | ||||
|             // service. | ||||
|             unbindService(mServiceConnection); | ||||
|             mBound = false; | ||||
|         } | ||||
|         super.onStop(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the current state of the permissions needed. | ||||
|      */ | ||||
|     private boolean checkPermissions() { | ||||
|         return  PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(this, | ||||
|         return PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, | ||||
|                 Manifest.permission.ACCESS_FINE_LOCATION); | ||||
|     } | ||||
|  | ||||
|     private void requestPermissions() { | ||||
|         boolean shouldProvideRationale = | ||||
|                 ActivityCompat.shouldShowRequestPermissionRationale(this, | ||||
|                         Manifest.permission.ACCESS_FINE_LOCATION); | ||||
|  | ||||
|         // Provide an additional rationale to the user. This would happen if the user denied the | ||||
|         // request previously, but didn't check the "Don't ask again" checkbox. | ||||
|         if (shouldProvideRationale) { | ||||
|             Log.i(TAG, "Displaying permission rationale to provide additional context."); | ||||
|             /* | ||||
|             Snackbar.make( | ||||
|                     findViewById(R.id.activity_main), | ||||
|                     R.string.permission_rationale, | ||||
|                     Snackbar.LENGTH_INDEFINITE) | ||||
|                     .setAction(R.string.ok, new View.OnClickListener() { | ||||
|                         @Override | ||||
|                         public void onClick(View view) { | ||||
|                             // Request permission | ||||
|                             ActivityCompat.requestPermissions(MainActivity.this, | ||||
|                                     new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, | ||||
|                                     REQUEST_PERMISSIONS_REQUEST_CODE); | ||||
|                         } | ||||
|                     }) | ||||
|                     .show(); | ||||
|              */ | ||||
|         } else { | ||||
|             Log.i(TAG, "Requesting permission"); | ||||
|             // Request permission. It's possible this can be auto answered if device policy | ||||
|             // sets the permission in a given state or the user denied the permission | ||||
|             // previously and checked "Never ask again". | ||||
|             ActivityCompat.requestPermissions(MainActivity.this, | ||||
|                     new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, | ||||
|                     REQUEST_PERMISSIONS_REQUEST_CODE); | ||||
|         } | ||||
|         ActivityCompat.requestPermissions(MainActivity.this, | ||||
|                 new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, | ||||
|                 REQUEST_PERMISSIONS_REQUEST_CODE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Callback received when a permissions request has been completed. | ||||
|      */ | ||||
|     @Override | ||||
|     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, | ||||
|                                            @NonNull int[] grantResults) { | ||||
|         if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) { | ||||
|             if (grantResults.length <= 0) { | ||||
|                 // If user interaction was interrupted, the permission request is cancelled and you | ||||
|                 // receive empty arrays. | ||||
|                 Log.i(TAG, "User interaction was cancelled."); | ||||
|             } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||||
|                 // Permission was granted. | ||||
|             if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||||
|                 mService.requestLocationUpdates(); | ||||
|             } else { | ||||
|                 // Permission denied. | ||||
|                 /* | ||||
|                 setButtonsState(false); | ||||
|                 Snackbar.make( | ||||
|                         findViewById(R.id.activity_main), | ||||
|                         R.string.permission_denied_explanation, | ||||
|                         Snackbar.LENGTH_INDEFINITE) | ||||
|                         .setAction(R.string.settings, new View.OnClickListener() { | ||||
|                             @Override | ||||
|                             public void onClick(View view) { | ||||
|                                 // Build intent that displays the App settings screen. | ||||
|                                 Intent intent = new Intent(); | ||||
|                                 intent.setAction( | ||||
|                                         Settings.ACTION_APPLICATION_DETAILS_SETTINGS); | ||||
|                                 Uri uri = Uri.fromParts("package", | ||||
|                                         BuildConfig.APPLICATION_ID, null); | ||||
|                                 intent.setData(uri); | ||||
|                                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|                                 startActivity(intent); | ||||
|                             } | ||||
|                         }) | ||||
|                         .show(); | ||||
|  | ||||
|                  */ | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Receiver for broadcasts sent by {@link LocationUpdatesService}. | ||||
|      */ | ||||
|     private class MyReceiver extends BroadcastReceiver { | ||||
|         @Override | ||||
|         public void onReceive(Context context, Intent intent) { | ||||
|             Location location = intent.getParcelableExtra(LocationUpdatesService.EXTRA_LOCATION); | ||||
|             if (location != null) { | ||||
|                 Toast.makeText(MainActivity.this, Utils.getLocationText(location), | ||||
|                         Toast.LENGTH_SHORT).show(); | ||||
|                 //TODO looks like we need to remove this | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -10,20 +10,16 @@ import java.util.Date; | ||||
| class Utils { | ||||
|  | ||||
|     static final String KEY_REQUESTING_LOCATION_UPDATES = "flutter.foreground-location-service"; | ||||
|     static final String KEY_LOCATION_UPDATE_INTERVAL = "flutter.active-location-interval"; | ||||
|  | ||||
|     /** | ||||
|      * Returns true if requesting location updates, otherwise returns false. | ||||
|      * | ||||
|      * @param context The {@link Context}. | ||||
|      */ | ||||
|     static boolean requestingLocationUpdates(Context context) { | ||||
|         return context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).getBoolean(KEY_REQUESTING_LOCATION_UPDATES, false); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Stores the location updates state in SharedPreferences. | ||||
|      * @param requestingLocationUpdates The location updates state. | ||||
|      */ | ||||
|     static long getLocationUpdateIntervals(Context context) { | ||||
|         return context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).getLong(KEY_LOCATION_UPDATE_INTERVAL, 90) * 1000; | ||||
|     } | ||||
|  | ||||
|     static void setRequestingLocationUpdates(Context context, boolean requestingLocationUpdates) { | ||||
|         context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE) | ||||
|                 .edit() | ||||
| @@ -31,16 +27,12 @@ class Utils { | ||||
|                 .apply(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the {@code location} object as a human readable string. | ||||
|      * @param location  The {@link Location}. | ||||
|      */ | ||||
|     static String getLocationText(Location location) { | ||||
|         return location == null ? "Unknown location" : | ||||
|                 "(" + location.getLatitude() + ", " + location.getLongitude() + ")"; | ||||
|         return location == null ? "Accuracy: unknown" : | ||||
|                 "Accuracy: " + location.getAccuracy(); | ||||
|     } | ||||
|  | ||||
|     static String getLocationTitle(Context context) { | ||||
|         return "Location updated: " + DateFormat.getDateTimeInstance().format(new Date()); | ||||
|     static String getLocationTitle(Location location) { | ||||
|         return "Location updated at " + DateFormat.getDateTimeInstance().format(new Date(location.getTime())); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -32,6 +32,7 @@ class AppSettings { | ||||
|   DisplayMode displayMode; | ||||
|   AppTheme appTheme; | ||||
|   final int defaultLocationUpdateIntervalMinutes = 20; | ||||
|   final int defaultActiveLocationUpdateIntervalSeconds = 90; | ||||
|   Duration locationUpdateInterval; | ||||
|   bool locationTrackingEnabled = false; | ||||
|  | ||||
|   | ||||
| @@ -14,6 +14,7 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> { | ||||
|   static const platform = const MethodChannel('com.keyboardcrumbs.hassclient/native'); | ||||
|  | ||||
|   int _locationInterval = AppSettings().defaultLocationUpdateIntervalMinutes; | ||||
|   int _activeLocationInterval = AppSettings().defaultActiveLocationUpdateIntervalSeconds; | ||||
|   bool _locationTrackingEnabled = false; | ||||
|   bool _foregroundLocationTrackingEnabled = false; | ||||
|   bool _wait = false; | ||||
| @@ -35,7 +36,11 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> { | ||||
|         _foregroundLocationTrackingEnabled = prefs.getBool("foreground-location-service") ?? false; | ||||
|         _locationInterval = prefs.getInt("location-interval") ?? | ||||
|           AppSettings().defaultLocationUpdateIntervalMinutes; | ||||
|         if (_locationInterval % 5 != 0) { | ||||
|         _activeLocationInterval = prefs.getInt("active-location-interval") ?? | ||||
|             AppSettings().defaultActiveLocationUpdateIntervalSeconds; | ||||
|         if (_locationInterval < 15) { | ||||
|           _locationInterval = 15; | ||||
|         } else if (_locationInterval % 5 != 0) { | ||||
|           _locationInterval = 5 * (_locationInterval ~/ 5); | ||||
|         } | ||||
|       }); | ||||
| @@ -52,7 +57,7 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> { | ||||
|   } | ||||
|  | ||||
|   void _decLocationInterval() { | ||||
|     if (_locationInterval > 5) { | ||||
|     if (_locationInterval > 15) { | ||||
|       setState(() { | ||||
|         _locationInterval = _locationInterval - 5; | ||||
|         _changedHere = true; | ||||
| @@ -60,6 +65,24 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void _incActiveLocationInterval() { | ||||
|     if (_activeLocationInterval < 7200) { | ||||
|       setState(() { | ||||
|         _activeLocationInterval = _activeLocationInterval + 5; | ||||
|         _changedHere = true; | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void _decActiveLocationInterval() { | ||||
|     if (_activeLocationInterval > 5) { | ||||
|       setState(() { | ||||
|         _activeLocationInterval = _activeLocationInterval - 5; | ||||
|         _changedHere = true; | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   _switchLocationTrackingState(bool state) async { | ||||
|     if (state) { | ||||
|       await LocationManager().updateDeviceLocation(); | ||||
| @@ -71,6 +94,7 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> { | ||||
|   } | ||||
|  | ||||
|   _switchForegroundLocationTrackingState(bool state) async { | ||||
|     await AppSettings().save({'active-location-interval': _activeLocationInterval}); | ||||
|     if (state) { | ||||
|       await platform.invokeMethod('startLocationService'); | ||||
|     } else { | ||||
| @@ -83,81 +107,96 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if (!_wait && !_changedHere) { | ||||
|       _loadSettings(); | ||||
|     } else if (_changedHere) { | ||||
|       _changedHere = false; | ||||
|     } | ||||
|     return ListView( | ||||
|       scrollDirection: Axis.vertical, | ||||
|       padding: const EdgeInsets.all(20.0), | ||||
|       children: <Widget>[ | ||||
|               Text("Location tracking", style: Theme.of(context).textTheme.title), | ||||
|               Container(height: Sizes.rowPadding,), | ||||
|               InkWell( | ||||
|                 onTap: () => Launcher.launchURLInCustomTab(context: context, url: "http://ha-client.app/docs#location-tracking"), | ||||
|                 child: Text( | ||||
|                   "Please read documentation!", | ||||
|                   style: Theme.of(context).textTheme.subhead.copyWith( | ||||
|                     color: Colors.blue, | ||||
|                     decoration: TextDecoration.underline | ||||
|                   ) | ||||
|                 ), | ||||
|               ), | ||||
|               Container(height: Sizes.rowPadding,), | ||||
|               Row( | ||||
|                 children: <Widget>[ | ||||
|                   Text("Enable location tracking"), | ||||
|                   Switch( | ||||
|                     value: _locationTrackingEnabled, | ||||
|                     onChanged: _wait ? null : (value) { | ||||
|                       setState(() { | ||||
|                         _locationTrackingEnabled = value; | ||||
|                         _wait = true; | ||||
|                       }); | ||||
|                       _switchLocationTrackingState(value); | ||||
|                     }, | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|               Container(height: Sizes.rowPadding), | ||||
|               Row( | ||||
|                 children: <Widget>[ | ||||
|                   Text("Foreground tracking"), | ||||
|                   Switch( | ||||
|                     value: _foregroundLocationTrackingEnabled, | ||||
|                     onChanged: _wait ? null : (value) { | ||||
|                       setState(() { | ||||
|                         _foregroundLocationTrackingEnabled = value; | ||||
|                         _wait = true; | ||||
|                       }); | ||||
|                       _switchForegroundLocationTrackingState(value); | ||||
|                     }, | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|               Container(height: Sizes.rowPadding), | ||||
|               Text("Location update interval in minutes:"), | ||||
|               Row( | ||||
|                 mainAxisAlignment: MainAxisAlignment.center, | ||||
|                 mainAxisSize: MainAxisSize.max, | ||||
|                 children: <Widget>[ | ||||
|                   //Expanded(child: Container(),), | ||||
|                   FlatButton( | ||||
|                     padding: EdgeInsets.all(0.0), | ||||
|                     child: Text("-", style: Theme.of(context).textTheme.title), | ||||
|                     onPressed: () => _decLocationInterval(), | ||||
|                   ), | ||||
|                   Text("$_locationInterval", style: Theme.of(context).textTheme.title), | ||||
|                   FlatButton( | ||||
|                     padding: EdgeInsets.all(0.0), | ||||
|                     child: Text("+", style: Theme.of(context).textTheme.title), | ||||
|                     onPressed: () => _incLocationInterval(), | ||||
|                   ), | ||||
|                 ], | ||||
|               ) | ||||
|             ] | ||||
|         ); | ||||
|         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,), | ||||
|         Row( | ||||
|           children: <Widget>[ | ||||
|             Text("Enable"), | ||||
|             Switch( | ||||
|               value: _locationTrackingEnabled, | ||||
|               onChanged: _wait ? null : (value) { | ||||
|                 setState(() { | ||||
|                   _locationTrackingEnabled = value; | ||||
|                   _wait = true; | ||||
|                 }); | ||||
|                 _switchLocationTrackingState(value); | ||||
|               }, | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|         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), | ||||
|         Text("Works in foreground noticeably affecting phone battery. Sends most accurate location getting it from phone if possible. Can be very frequent.", | ||||
|           style: Theme.of(context).textTheme.caption, | ||||
|           softWrap: true, | ||||
|         ), | ||||
|         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("Update 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: _foregroundLocationTrackingEnabled ? null : () => _decActiveLocationInterval(), | ||||
|             ), | ||||
|             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(), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       ] | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   | ||||
		Reference in New Issue
	
	Block a user