Compare commits

..

3 Commits

Author SHA1 Message Date
5211d1ff46 v. 0.5.4 2019-05-09 14:15:21 +03:00
d1c9fddba6 v. 0.5.4 2019-05-09 13:17:30 +03:00
14cc55a2c7 Resolves #373 2019-05-09 13:16:35 +03:00
251 changed files with 8259 additions and 16641 deletions

View File

@ -1,22 +0,0 @@
---
name: Bug report
about: Create a report to help improve HA Client
title: ''
labels: ''
assignees: ''
---
**HA Client version:** [Main menu -> About HA Client]
**Home Assistant version:**
**Device name:**
**Android version:**
**Description**
[Replace with description]
**Screenshots**
[Replace with screenshots]

View File

@ -1,12 +0,0 @@
---
name: Entity support request
about: Suggest to add support of any entity type
title: ''
labels: ENTITY, feature/improvement
assignees: ''
---
**Entity type:**
**Link to documentation:**

View File

@ -1,17 +0,0 @@
---
name: Feature request
about: Suggest an idea for HA Client if it is not a card or entity support
title: ''
labels: feature/improvement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -1,12 +0,0 @@
---
name: Lovelace Card support request
about: Suggest to add any Lovelace card support
title: ''
labels: CARD, feature/improvement
assignees: ''
---
**Card name:**
**Link to card repository or web page:**

View File

@ -1,11 +0,0 @@
# Configuration for probot-no-response - https://github.com/probot/no-response
# Number of days of inactivity before an Issue is closed for lack of response
daysUntilClose: 14
# Label requiring a response
responseRequiredLabel: more info needed
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
closeComment: >
This issue has been automatically closed because there has been no response
to our request for more information from the original author. If the issue still relevant
feel free to reopen this issue and add more information, or report a new one.

10
.gitignore vendored
View File

@ -9,15 +9,5 @@ build/
.flutter-plugins
.idea/
.vscode/
.theia/
.project/
.settings/
flutter_export_environment.sh
.flutter-plugins-dependencies
key.properties
.secrets.dart
pubspec.lock
google-services.json

View File

@ -1,8 +0,0 @@
FROM gitpod/workspace-full:latest
ENV ANDROID_HOME=/workspace/android-sdk \
FLUTTER_ROOT=/workspace/flutter \
FLUTTER_HOME=/workspace/flutter
RUN bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh \
&& sdk install java 8.0.242.j9-adpt"

View File

@ -1,26 +0,0 @@
image:
file: .gitpod.dockerfile
tasks:
- before: |
export PATH=$FLUTTER_HOME/bin:$FLUTTER_HOME/bin/cache/dart-sdk/bin:$ANDROID_HOME/bin:$ANDROID_HOME/platform-tools:$PATH
mkdir -p /home/gitpod/.android
touch /home/gitpod/.android/repositories.cfg
init: |
echo "Installing Flutter SDK..."
cd /workspace && wget -qO flutter_sdk.tar.xz https://storage.googleapis.com/flutter_infra/releases/stable/linux/flutter_linux_v1.12.13+hotfix.7-stable.tar.xz && tar -xf flutter_sdk.tar.xz && rm -f flutter_sdk.tar.xz
echo "Installing Android SDK..."
mkdir -p /workspace/android-sdk && cd /workspace/android-sdk && wget https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip && unzip sdk-tools-linux-4333796.zip && rm -f sdk-tools-linux-4333796.zip
/workspace/android-sdk/tools/bin/sdkmanager "platform-tools" "platforms;android-28" "build-tools;28.0.3"
echo "Init Flutter..."
cd /workspace/ha_client
flutter upgrade
flutter doctor --android-licenses
flutter pub get
command: |
echo "Ready to go!"
flutter doctor
vscode:
extensions:
- Dart-Code.dart-code@3.5.0-beta.1:Wg2nTABftVR/Dry4tqeY1w==
- Dart-Code.flutter@3.5.0:/kOacEWdiDRLyN/idUiM4A==

View File

@ -1,24 +1,12 @@
[![flutter](https://somegeeky.website/assets/badges/flutter_badge_v3.svg)](https://somegeeky.website/badges/flutter) [![dart](https://somegeeky.website/assets/badges/dart_badge_v3.svg)](https://somegeeky.website/badges/dart)
# HA Client
| # IMPORTANT! |
|:-:|
| As you may know official Home Assistant android app continue to grow and improve. Today is the day when it has so much more and better functionality that I see no any reason not to move from HA Client to official Home Assistant app. You need to make the same as it has more device sensors, better location tracking, widgets etc.
| **I'm stopping HA Client development as now the only advantage it has over the official app is native UI that is very far from completeness.** |
| **Login with Home Assistant credentials will be finally and irrevocably broken on Fabruary 1st of 2021 with the shut down of https://ha-client.app/.** |
| I need to say a HUGE THANK YOU all for a grate support you all made for HA Client. I really appreciate each and every issue reported to GitHub, each and every message from each of you and each donation made to support development. Now it is time to cancel subscription if any and migrate your HA setup to an official Home Assistant app. You can find it in [Google Play](https://play.google.com/store/apps/details?id=io.homeassistant.companion.android). |
## Native Android client for Home Assistant
### With actionable notifications, location tracking and Lovelace UI support
### With Lovelace UI support
Visit [ha-client.app](http://ha-client.app/) for more info.
Visit [homemade.systems](http://ha-client.homemade.systems/) for more info.
Download the app from [Google Play](https://play.google.com/store/apps/details?id=com.keyboardcrumbs.haclient)
Join [Google Group](https://groups.google.com/d/forum/ha-client-alpha-testing) to become an alpha tester
Discuss it on [Discord](https://discord.gg/u9vq7QE) or at [Home Assistant community](https://community.home-assistant.io/c/mobile-apps/ha-client-android)
Download the app from [Google Play](https://play.google.com/apps/testing/com.keyboardcrumbs.haclient) after joining the group
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/estevez-dev/ha_client)
#### Last release build status
[![Codemagic build status](https://api.codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5db1862025dc3f0b0288a57a/status_badge.svg)](https://codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5db1862025dc3f0b0288a57a/latest_build)
#### Projects used
- [HANotify](https://github.com/Crewski/HANotify) by [Crewski](https://github.com/Crewski)
- [hassalarm](https://github.com/Johboh/hassalarm) by [Johboh](https://github.com/Johboh) distributed under [MIT License](https://github.com/Johboh/hassalarm/blob/master/LICENSE)
Discuss it in [Home Assistant community](https://community.home-assistant.io/t/alpha-testing-ha-client-native-android-client-for-home-assistant/69912)

1
android/.gitignore vendored
View File

@ -8,4 +8,3 @@
/build
/captures
GeneratedPluginRegistrant.java
.project/

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>android</name>
<comment>Project android created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
</projectDescription>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>app</name>
<comment>Project app created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
</projectDescription>

View File

@ -50,14 +50,6 @@ android {
}
signingConfigs {
if (!System.getenv()["CI"]) {
debug {
keyAlias keystoreProperties['debugKeyAlias']
keyPassword keystoreProperties['debugKeyPassword']
storeFile file(keystoreProperties['debugStoreFile'])
storePassword keystoreProperties['debugStorePassword']
}
}
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
@ -78,15 +70,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'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
apply plugin: 'io.fabric'
apply plugin: 'com.google.gms.google-services'

View File

@ -1,15 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.keyboardcrumbs.hassclient"
android:installLocation="auto">
package="com.keyboardcrumbs.hassclient">
<uses-feature android:name="android.hardware.touchscreen"
android:required="false" />
<!-- The INTERNET permission is required for development. Specifically,
flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
@ -17,19 +13,9 @@
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="HA Client"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:usesCleartextTraffic="true">
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="ha_notify" />
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
@ -37,64 +23,17 @@
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" />
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service
android:name=".MessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service
android:name=".LocationUpdatesService"
android:enabled="true"
android:exported="false" />
<service
android:name=".LocationRequestService"
android:enabled="true"
android:exported="false" />
<receiver android:name=".NotificationActionReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
</intent-filter>
</receiver>
<receiver android:name=".NextAlarmBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
</intent-filter>
</receiver>
<receiver android:name=".RestartLocationUpdate">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
<service
android:name="io.flutter.plugins.androidalarmmanager.AlarmService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false"/>
<receiver
android:name="io.flutter.plugins.androidalarmmanager.AlarmBroadcastReceiver"
android:exported="false"/>
<receiver
android:name="io.flutter.plugins.androidalarmmanager.RebootBroadcastReceiver"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -1,146 +0,0 @@
package com.keyboardcrumbs.hassclient;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.location.Location;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.ExistingWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import com.google.android.gms.location.FusedLocationProviderClient;
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;
public class LocationRequestService extends Service {
private static final String TAG = LocationRequestService.class.getSimpleName();
private NotificationManager mNotificationManager;
private LocationRequest mLocationRequest;
private FusedLocationProviderClient mFusedLocationClient;
private LocationCallback mLocationCallback;
private Handler mServiceHandler;
public LocationRequestService() {
}
@Override
public void onCreate() {
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
onNewLocation(locationResult.getLastLocation());
}
};
mLocationRequest = new LocationRequest();
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
mServiceHandler = new Handler(handlerThread.getLooper());
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "Location requests";
NotificationChannel mChannel =
new NotificationChannel(LocationUtils.ONETIME_NOTIFICATION_CHANNEL_ID, name, NotificationManager.IMPORTANCE_LOW);
mNotificationManager.createNotificationChannel(mChannel);
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Service started. startId="+startId);
requestLocationUpdates();
return START_STICKY;
}
@Override
public void onDestroy() {
try {
mFusedLocationClient.removeLocationUpdates(mLocationCallback);
} catch (SecurityException unlikely) {
//When we lost permission
Log.i(TAG, "No location permission");
}
mServiceHandler.removeCallbacksAndMessages(null);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void requestLocationUpdates() {
Log.i(TAG, "Requesting location update in 5 seconds.");
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setInterval(5000);
startForeground(LocationUtils.ONETIME_NOTIFICATION_ID, LocationUtils.getRequestNotification(this, null, LocationUtils.ONETIME_NOTIFICATION_CHANNEL_ID));
try {
mFusedLocationClient.requestLocationUpdates(mLocationRequest,
mLocationCallback, Looper.myLooper());
} catch (SecurityException unlikely) {
stopSelf();
}
}
private void onNewLocation(Location location) {
Log.i(TAG, "New location: " + location);
mNotificationManager.notify(LocationUtils.ONETIME_NOTIFICATION_ID, LocationUtils.getRequestNotification(
this,
location,
LocationUtils.ONETIME_NOTIFICATION_CHANNEL_ID
));
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
Data locationData = new Data.Builder()
.putInt(SendDataHomeWorker.DATA_TYPE_KEY, SendDataHomeWorker.DATA_TYPE_LOCATION)
.putDouble("Lat", location.getLatitude())
.putDouble("Long", location.getLongitude())
.putFloat("Acc", location.getAccuracy())
.build();
OneTimeWorkRequest uploadWorkRequest =
new OneTimeWorkRequest.Builder(SendDataHomeWorker.class)
.setConstraints(constraints)
.setInputData(locationData)
.build();
WorkManager
.getInstance(getApplicationContext())
.enqueueUniqueWork("SendLocationUpdate", ExistingWorkPolicy.REPLACE, uploadWorkRequest);
stopSelf();
}
}

View File

@ -1,154 +0,0 @@
package com.keyboardcrumbs.hassclient;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.location.Location;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import androidx.annotation.Nullable;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.ExistingWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import android.util.Log;
import com.google.android.gms.location.FusedLocationProviderClient;
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;
public class LocationUpdatesService extends Service {
private static final String TAG = LocationUpdatesService.class.getSimpleName();
private NotificationManager mNotificationManager;
private LocationRequest mLocationRequest;
private FusedLocationProviderClient mFusedLocationClient;
private LocationCallback mLocationCallback;
private Handler mServiceHandler;
public LocationUpdatesService() {
}
@Override
public void onCreate() {
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
onNewLocation(locationResult.getLastLocation());
}
};
mLocationRequest = new LocationRequest();
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
mServiceHandler = new Handler(handlerThread.getLooper());
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "Location updates";
NotificationChannel mChannel =
new NotificationChannel(LocationUtils.SERVICE_NOTIFICATION_CHANNEL_ID, name, NotificationManager.IMPORTANCE_LOW);
mNotificationManager.createNotificationChannel(mChannel);
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Service started. startId="+startId);
requestLocationUpdates();
return START_STICKY;
}
@Override
public void onDestroy() {
try {
mFusedLocationClient.removeLocationUpdates(mLocationCallback);
} catch (SecurityException unlikely) {
//When we lost permission
Log.i(TAG, "No location permission");
}
mServiceHandler.removeCallbacksAndMessages(null);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void requestLocationUpdates() {
long requestInterval = LocationUtils.getLocationUpdateIntervals(getApplicationContext());
int priority;
if (requestInterval >= 600000) {
mLocationRequest.setFastestInterval(60000);
priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY;
} else {
priority = LocationRequest.PRIORITY_HIGH_ACCURACY;
}
Log.i(TAG, "Requesting location updates. Every " + requestInterval + "ms with priority of " + priority);
mLocationRequest.setPriority(priority);
mLocationRequest.setInterval(requestInterval);
startForeground(LocationUtils.SERVICE_NOTIFICATION_ID, LocationUtils.getNotification(this, null, LocationUtils.SERVICE_NOTIFICATION_CHANNEL_ID));
try {
mFusedLocationClient.requestLocationUpdates(mLocationRequest,
mLocationCallback, Looper.myLooper());
} catch (SecurityException unlikely) {
stopSelf();
}
}
private void onNewLocation(Location location) {
Log.i(TAG, "New location: " + location);
mNotificationManager.notify(LocationUtils.SERVICE_NOTIFICATION_ID, LocationUtils.getNotification(
this,
location,
LocationUtils.SERVICE_NOTIFICATION_CHANNEL_ID
));
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
Data locationData = new Data.Builder()
.putInt(SendDataHomeWorker.DATA_TYPE_KEY, SendDataHomeWorker.DATA_TYPE_LOCATION)
.putDouble("Lat", location.getLatitude())
.putDouble("Long", location.getLongitude())
.putFloat("Acc", location.getAccuracy())
.build();
OneTimeWorkRequest uploadWorkRequest =
new OneTimeWorkRequest.Builder(SendDataHomeWorker.class)
.setConstraints(constraints)
.setInputData(locationData)
.build();
WorkManager
.getInstance(getApplicationContext())
.enqueueUniqueWork("SendLocationUpdate", ExistingWorkPolicy.REPLACE, uploadWorkRequest);
}
}

View File

@ -1,112 +0,0 @@
package com.keyboardcrumbs.hassclient;
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;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.ExistingWorkPolicy;
import androidx.work.ListenableWorker;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.WorkerParameters;
import com.google.android.gms.location.FusedLocationProviderClient;
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.common.util.concurrent.ListenableFuture;
import java.util.concurrent.TimeUnit;
public class LocationUpdatesWorker extends ListenableWorker {
private Context currentContext;
private LocationCallback callback;
private FusedLocationProviderClient fusedLocationClient;
public LocationUpdatesWorker(Context context, WorkerParameters params) {
super(context, params);
currentContext = context;
}
private void finish() {
fusedLocationClient.removeLocationUpdates(callback);
}
@NonNull
@Override
public ListenableFuture<Result> startWork() {
return CallbackToFutureAdapter.getFuture(completer -> {
fusedLocationClient = LocationServices.getFusedLocationProviderClient(currentContext);
callback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
Location location = locationResult.getLastLocation();
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
Data locationData = new Data.Builder()
.putInt(SendDataHomeWorker.DATA_TYPE_KEY, SendDataHomeWorker.DATA_TYPE_LOCATION)
.putDouble("Lat", location.getLatitude())
.putDouble("Long", location.getLongitude())
.putFloat("Acc", location.getAccuracy())
.build();
OneTimeWorkRequest uploadWorkRequest =
new OneTimeWorkRequest.Builder(SendDataHomeWorker.class)
.setConstraints(constraints)
.setInputData(locationData)
.build();
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());
}
};
LocationRequest locationRequest = new LocationRequest();
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setInterval(5000);
try {
fusedLocationClient.requestLocationUpdates(locationRequest,
callback, Looper.myLooper());
} catch (SecurityException e) {
completer.setException(e);
}
return callback;
});
}
}

View File

@ -1,147 +0,0 @@
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.ExistingWorkPolicy;
import androidx.work.OneTimeWorkRequest;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import java.text.DateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
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_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 ONETIME_NOTIFICATION_CHANNEL_ID = "location_request";
static final int ONETIME_NOTIFICATION_ID = 954333;
static final String REQUEST_LOCATION_NOTIFICATION = "request_location_update";
static final String LOCATION_WORK_NAME = "HALocationWorker";
static final String LOCATION_REQUEST_NAME = "HALocationRequest";
static final int LOCATION_UPDATES_DISABLED = 0;
static final int LOCATION_UPDATES_SERVICE = 1;
static final int LOCATION_UPDATES_WORKER = 2;
static final int DEFAULT_LOCATION_UPDATE_INTERVAL_MS = 900000; //15 minutes
static final long MIN_WORKER_LOCATION_UPDATE_INTERVAL_MS = 900000; //15 minutes
static int getLocationUpdatesState(Context context) {
return context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).getInt(KEY_REQUESTING_LOCATION_UPDATES, LOCATION_UPDATES_DISABLED);
}
static long getLocationUpdateIntervals(Context context) {
return context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).getLong(KEY_LOCATION_UPDATE_INTERVAL, DEFAULT_LOCATION_UPDATE_INTERVAL_MS);
}
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()
.putInt(KEY_REQUESTING_LOCATION_UPDATES, locationUpdatesState)
.apply();
}
static void setLocationUpdatesSettings(Context context, long interval, boolean showNotification) {
context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
.edit()
.putBoolean(KEY_LOCATION_SHOW_NOTIFICATION, showNotification)
.putLong(KEY_LOCATION_UPDATE_INTERVAL, interval)
.apply();
}
static void startService(Context context) {
Intent myService = new Intent(context, LocationUpdatesService.class);
context.startService(myService);
}
static void startServiceFromBroadcast(Context context) {
Intent serviceIntent = new Intent(context, LocationUpdatesService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent);
} else {
context.startService(serviceIntent);
}
}
static void startWorker(Context context, long interval) {
PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder(LocationUpdatesWorker.class, interval, TimeUnit.MILLISECONDS)
.build();
WorkManager.getInstance(context).enqueueUniquePeriodicWork(LocationUtils.LOCATION_WORK_NAME, ExistingPeriodicWorkPolicy.REPLACE, periodicWork);
}
static void requestLocationOnce(Context context) {
Intent myService = new Intent(context, LocationRequestService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(myService);
} else {
context.startService(myService);
}
//OneTimeWorkRequest oneTimeWork = new OneTimeWorkRequest.Builder(LocationUpdatesWorker.class)
// .build();
//WorkManager.getInstance(context).enqueueUniqueWork(LocationUtils.LOCATION_REQUEST_NAME, ExistingWorkPolicy.REPLACE, oneTimeWork);
}
static Notification getNotification(Context context, Location location, String channelId) {
CharSequence title = "Location tracking";
CharSequence text = location == null ? "Accuracy: unknown" : "Accuracy: " + location.getAccuracy() + " m";
CharSequence bigText = location == null ? "Waiting for location..." : "Time: " + DateFormat.getDateTimeInstance().format(new Date(location.getTime())) +
System.getProperty("line.separator") + "Accuracy: " + location.getAccuracy() + " m" +
System.getProperty("line.separator") + "Location: " + location.getLatitude() + ", " + location.getLongitude();
PendingIntent activityPendingIntent = PendingIntent.getActivity(context, 0,
new Intent(context, MainActivity.class), 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId)
.setContentIntent(activityPendingIntent)
.setContentTitle(title)
.setContentText(text)
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(bigText))
.setPriority(-1)
.setOngoing(true)
.setSmallIcon(R.drawable.mini_icon_location)
.setWhen(System.currentTimeMillis());
return builder.build();
}
static Notification getRequestNotification(Context context, Location location, String channelId) {
CharSequence title = "Updating location...";
CharSequence text = location == null ? "Waiting for location..." : "Accuracy: " + location.getAccuracy() + " m";
PendingIntent activityPendingIntent = PendingIntent.getActivity(context, 0,
new Intent(context, MainActivity.class), 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId)
.setContentIntent(activityPendingIntent)
.setContentTitle(title)
.setContentText(text)
.setPriority(-1)
.setOngoing(true)
.setSmallIcon(R.drawable.mini_icon_location)
.setWhen(System.currentTimeMillis());
return builder.build();
}
}

View File

@ -1,184 +1,13 @@
package com.keyboardcrumbs.hassclient;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.work.WorkManager;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
import android.Manifest;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import io.flutter.plugin.common.MethodChannel;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.ConnectionResult;
import com.google.firebase.iid.FirebaseInstanceId;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "com.keyboardcrumbs.hassclient/native";
private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34;
private int locationUpdatesType = LocationUtils.LOCATION_UPDATES_DISABLED;
private long locationUpdatesInterval = LocationUtils.DEFAULT_LOCATION_UPDATE_INTERVAL_MS;
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL).setMethodCallHandler(
(call, result) -> {
Context context = getActivity();
switch (call.method) {
case "getFCMToken":
try {
if (checkPlayServices()) {
FirebaseInstanceId.getInstance().getInstanceId()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
String token = task.getResult().getToken();
context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).edit().putString("flutter.npush-token", token).apply();
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);
}
} catch (Exception e) {
result.error("get_token_exception", e.getMessage(), e);
}
break;
case "startLocationService":
try {
locationUpdatesInterval = ((Number)call.argument("location-updates-interval")).longValue();
boolean useForegroundService = (boolean)call.argument("foreground-location-tracking");
if (useForegroundService) {
locationUpdatesType = LocationUtils.LOCATION_UPDATES_SERVICE;
} else {
locationUpdatesType = LocationUtils.LOCATION_UPDATES_WORKER;
}
LocationUtils.setLocationUpdatesSettings(this, locationUpdatesInterval, (boolean)call.argument("location-updates-show-notification"));
if (isNoLocationPermissions()) {
requestLocationPermissions();
} else {
startLocationUpdates();
}
result.success("");
} catch (Exception e) {
result.error("location_error", e.getMessage(), e);
}
break;
case "stopLocationService":
try {
stopLocationUpdates();
result.success("");
} catch (Exception e) {
result.error("location_error", e.getMessage(), e);
}
break;
case "cancelOldLocationWorker":
WorkManager.getInstance(this).cancelAllWorkByTag("haclocation");
result.success("");
break;
}
}
);
}
private boolean checkPlayServices() {
return (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS);
}
private void startLocationUpdates() {
if (locationUpdatesType == LocationUtils.LOCATION_UPDATES_SERVICE) {
LocationUtils.startService(this);
LocationUtils.setLocationUpdatesState(this, locationUpdatesType);
} else if (locationUpdatesType == LocationUtils.LOCATION_UPDATES_WORKER) {
LocationUtils.startWorker(this, locationUpdatesInterval);
LocationUtils.setLocationUpdatesState(this, locationUpdatesType);
} else {
stopLocationUpdates();
}
}
private void stopLocationUpdates() {
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);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onStop() {
super.onStop();
}
private boolean isNoLocationPermissions() {
return PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION);
}
private void requestLocationPermissions() {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_PERMISSIONS_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startLocationUpdates();
} else {
stopLocationUpdates();
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}

View File

@ -1,165 +0,0 @@
package com.keyboardcrumbs.hassclient;
import java.util.Map;
import java.net.URL;
import java.net.URLConnection;
import java.io.InputStream;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.webkit.URLUtil;
public class MessagingService extends FirebaseMessagingService {
private static final String TAG = MessagingService.class.getSimpleName();
public static final String NOTIFICATION_ACTION_BROADCAST = "com.keyboardcrumbs.hassclient.haNotificationAction";
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
if (data.size() > 0) {
if (data.containsKey("body") || data.containsKey("title")) {
sendNotification(data);
}
}
}
@Override
public void onNewToken(@NonNull String token) {
getApplicationContext().getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).edit().putString("flutter.npush-token", token).apply();
}
private void sendNotification(Map<String, String> data) {
String channelId, messageBody, messageTitle, imageUrl, nTag, channelDescription;
boolean autoCancel;
if (!data.containsKey("body")) {
messageBody = "";
} else {
messageBody = data.get("body");
}
if (messageBody != null && messageBody.equals(LocationUtils.REQUEST_LOCATION_NOTIFICATION)) {
Log.d(TAG, "Location update request received");
LocationUtils.requestLocationOnce(this);
return;
}
String customChannelId = data.get("channelId");
if (customChannelId == null) {
channelId = "ha_notify";
channelDescription = "Default notification channel";
} else {
channelId = customChannelId;
channelDescription = channelId;
}
if (!data.containsKey("title")) {
messageTitle = "HA Client";
} else {
messageTitle = data.get("title");
}
if (!data.containsKey("tag")) {
nTag = String.valueOf(System.currentTimeMillis());
} else {
nTag = data.get("tag");
}
if (data.containsKey("dismiss")) {
try {
boolean dismiss = Boolean.parseBoolean(data.get("dismiss"));
if (dismiss) {
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(nTag, 0);
return;
}
} catch (Exception e) {
//nope
}
}
if (data.containsKey("autoDismiss")) {
try {
autoCancel = Boolean.parseBoolean(data.get("autoDismiss"));
} catch (Exception e) {
autoCancel = true;
}
} else {
autoCancel = true;
}
imageUrl = data.get("image");
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.mini_icon)
.setContentTitle(messageTitle)
.setContentText(messageBody)
.setAutoCancel(autoCancel)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
Bitmap image = null;
if (URLUtil.isValidUrl(imageUrl)) {
image = getBitmapFromURL(imageUrl);
}
if (image != null) {
notificationBuilder.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(image).bigLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.blank_icon)));
notificationBuilder.setLargeIcon(image);
} else {
notificationBuilder.setStyle(new NotificationCompat.BigTextStyle()
.bigText(messageBody));
}
for (int i = 1; i <= 3; i++) {
if (data.containsKey("action" + i)) {
Intent broadcastIntent = new Intent(this, NotificationActionReceiver.class).setAction(NOTIFICATION_ACTION_BROADCAST);
if (autoCancel) {
broadcastIntent.putExtra("tag", nTag);
}
broadcastIntent.putExtra("actionData", data.get("action" + i + "_data"));
PendingIntent actionIntent = PendingIntent.getBroadcast(this, i, broadcastIntent, PendingIntent.FLAG_CANCEL_CURRENT);
notificationBuilder.addAction(R.drawable.blank_icon, data.get("action" + i), actionIntent);
}
}
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Since android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(channelId,
channelDescription,
NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificationChannel(channel);
}
notificationManager.notify(nTag, 0 /* ID of notification */, notificationBuilder.build());
}
private Bitmap getBitmapFromURL(String imageUrl) {
try {
URL url = new URL(imageUrl);
URLConnection connection = url.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
return BitmapFactory.decodeStream(input);
} catch (Exception e) {
return null;
}
}
}

View File

@ -1,54 +0,0 @@
package com.keyboardcrumbs.hassclient;
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import androidx.work.BackoffPolicy;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.ExistingWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import java.util.concurrent.TimeUnit;
public class NextAlarmBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) {
return;
}
final boolean isBootIntent = Intent.ACTION_BOOT_COMPLETED.equalsIgnoreCase(intent.getAction());
final boolean isNextAlarmIntent = AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED.equalsIgnoreCase(intent.getAction());
if (!isBootIntent && !isNextAlarmIntent) {
return;
}
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
Data workerData = new Data.Builder()
.putInt(SendDataHomeWorker.DATA_TYPE_KEY, SendDataHomeWorker.DATA_TYPE_NEXT_ALARM)
.build();
OneTimeWorkRequest uploadWorkRequest =
new OneTimeWorkRequest.Builder(SendDataHomeWorker.class)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
10,
TimeUnit.SECONDS)
.setInputData(workerData)
.setConstraints(constraints)
.build();
WorkManager
.getInstance(context)
.enqueueUniqueWork("NextAlarmUpdate", ExistingWorkPolicy.REPLACE, uploadWorkRequest);
}
}

View File

@ -1,58 +0,0 @@
package com.keyboardcrumbs.hassclient;
import android.content.Context;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.app.NotificationManager;
import androidx.work.BackoffPolicy;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.ExistingWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import java.util.concurrent.TimeUnit;
public class NotificationActionReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) {
return;
}
String intentAction = intent.getAction();
if (intentAction == null || !intentAction.equalsIgnoreCase(MessagingService.NOTIFICATION_ACTION_BROADCAST)) {
return;
}
String rawActionData = intent.getStringExtra("actionData");
if (intent.hasExtra("tag")) {
String notificationTag = intent.getStringExtra("tag");
NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(notificationTag, 0);
}
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
Data workerData = new Data.Builder()
.putInt(SendDataHomeWorker.DATA_TYPE_KEY, SendDataHomeWorker.DATA_TYPE_NOTIFICATION_ACTION)
.putString("rawActionData", rawActionData)
.build();
OneTimeWorkRequest uploadWorkRequest =
new OneTimeWorkRequest.Builder(SendDataHomeWorker.class)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
10,
TimeUnit.SECONDS)
.setInputData(workerData)
.setConstraints(constraints)
.build();
WorkManager
.getInstance(context)
.enqueueUniqueWork("NotificationAction", ExistingWorkPolicy.APPEND, uploadWorkRequest);
}
}

View File

@ -1,15 +0,0 @@
package com.keyboardcrumbs.hassclient;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class RestartLocationUpdate extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if (LocationUtils.getLocationUpdatesState(context) == LocationUtils.LOCATION_UPDATES_SERVICE &&
(Intent.ACTION_BOOT_COMPLETED.equalsIgnoreCase(intent.getAction()) || Intent.ACTION_MY_PACKAGE_REPLACED.equalsIgnoreCase(intent.getAction()))) {
LocationUtils.startServiceFromBroadcast(context);
}
}
}

View File

@ -1,220 +0,0 @@
package com.keyboardcrumbs.hassclient;
import android.app.AlarmManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.BatteryManager;
import android.util.Log;
import android.webkit.URLUtil;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
public class SendDataHomeWorker extends Worker {
public static final String DATA_TYPE_KEY = "dataType";
public static final int DATA_TYPE_LOCATION = 1;
public static final int DATA_TYPE_NEXT_ALARM = 2;
public static final int DATA_TYPE_NOTIFICATION_ACTION = 3;
private Context currentContext;
private static final String TAG = "SendDataHomeWorker";
public static final String KEY_LAT_ARG = "Lat";
public static final String KEY_LONG_ARG = "Long";
public static final String KEY_ACC_ARG = "Acc";
private static final SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:00", Locale.ENGLISH);
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:00", Locale.ENGLISH);
public SendDataHomeWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
currentContext = context;
}
@NonNull
@Override
public Result doWork() {
Log.d(TAG, "Start sending data home");
SharedPreferences prefs = currentContext.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE);
String webhookId = prefs.getString("flutter.app-webhook-id", null);
if (webhookId != null) {
try {
String requestUrl = prefs.getString("flutter.hassio-res-protocol", "") +
"://" +
prefs.getString("flutter.hassio-domain", "") +
":" +
prefs.getString("flutter.hassio-port", "") + "/api/webhook/" + webhookId;
if (URLUtil.isValidUrl(requestUrl)) {
int dataType = getInputData().getInt(DATA_TYPE_KEY, 0);
String stringRequest;
if (dataType == DATA_TYPE_LOCATION) {
Log.d(TAG, "Location data");
stringRequest = getLocationDataToSend();
} else if (dataType == DATA_TYPE_NEXT_ALARM) {
Log.d(TAG, "Next alarm data");
stringRequest = getNextAlarmDataToSend();
} else if (dataType == DATA_TYPE_NOTIFICATION_ACTION) {
Log.d(TAG, "Notification action data");
stringRequest = getNotificationActionData();
} else {
Log.e(TAG, "doWork() unknown data type: " + dataType);
return Result.failure();
}
try {
URL url = new URL(requestUrl);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Content-Type", "application/json");
urlConnection.setDoOutput(true);
assert stringRequest != null;
byte[] outputBytes = stringRequest.getBytes(StandardCharsets.UTF_8);
OutputStream os = urlConnection.getOutputStream();
os.write(outputBytes);
int responseCode = urlConnection.getResponseCode();
urlConnection.disconnect();
if (responseCode >= 300) {
return Result.retry();
}
} catch (Exception e) {
Log.e(TAG, "Error sending data", e);
return Result.retry();
}
} else {
Log.w(TAG, "Invalid HA url");
return Result.failure();
}
} catch (Exception e) {
Log.e(TAG, "Error =(", e);
return Result.failure();
}
} else {
Log.w(TAG, "Webhook id not found");
return Result.failure();
}
return Result.success();
}
private String getLocationDataToSend() {
try {
JSONObject dataToSend = new JSONObject();
dataToSend.put("type", "update_location");
JSONObject dataObject = new JSONObject();
JSONArray gps = new JSONArray();
gps.put(0, getInputData().getDouble(KEY_LAT_ARG, 0));
gps.put(1, getInputData().getDouble(KEY_LONG_ARG, 0));
dataObject.put("gps", gps);
dataObject.put("gps_accuracy", getInputData().getFloat(KEY_ACC_ARG, 0));
BatteryManager bm;
if (android.os.Build.VERSION.SDK_INT >= 23) {
bm = currentContext.getSystemService(BatteryManager.class);
} else {
bm = (BatteryManager)currentContext.getSystemService(Context.BATTERY_SERVICE);
}
int batLevel = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
dataObject.put("battery", batLevel);
dataToSend.put("data", dataObject);
return dataToSend.toString();
} catch (Exception e) {
Log.e(TAG,"getLocationDataToSend", e);
return null;
}
}
private String getNotificationActionData() {
try {
String rawActionData = getInputData().getString("rawActionData");
if (rawActionData == null || rawActionData.length() == 0) {
Log.e(TAG,"getNotificationActionData rawAction data is empty");
return null;
}
JSONObject actionData = new JSONObject(rawActionData);
JSONObject dataToSend = new JSONObject();
JSONObject requestData = new JSONObject();
if (actionData.getString("action").equals("call-service")) {
dataToSend.put("type", "call_service");
requestData.put("domain", actionData.getString("service").split("\\.")[0]);
requestData.put("service", actionData.getString("service").split("\\.")[1]);
if (actionData.has("service_data")) {
requestData.put("service_data", actionData.get("service_data"));
}
} else {
dataToSend.put("type", "fire_event");
requestData.put("event_type", "ha_client_event");
JSONObject eventData = new JSONObject();
eventData.put("action", actionData.getString("action"));
requestData.put("event_data", eventData);
}
dataToSend.put("data", requestData);
return dataToSend.toString();
} catch (Exception e) {
Log.e(TAG,"getNotificationActionData", e);
return null;
}
}
private String getNextAlarmDataToSend() {
try {
final AlarmManager alarmManager;
if (android.os.Build.VERSION.SDK_INT >= 23) {
alarmManager = currentContext.getSystemService(AlarmManager.class);
} else {
alarmManager = (AlarmManager)currentContext.getSystemService(Context.ALARM_SERVICE);
}
final AlarmManager.AlarmClockInfo alarmClockInfo = alarmManager.getNextAlarmClock();
JSONObject dataToSend = new JSONObject();
dataToSend.put("type", "update_sensor_states");
JSONArray dataArray = new JSONArray();
JSONObject sensorData = new JSONObject();
JSONObject sensorAttrs = new JSONObject();
sensorData.put("unique_id", "next_alarm");
sensorData.put("type", "sensor");
final long triggerTimestamp;
if (alarmClockInfo != null) {
triggerTimestamp = alarmClockInfo.getTriggerTime();
final Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(triggerTimestamp);
Date date = calendar.getTime();
sensorData.put("state", DATE_TIME_FORMAT.format(date));
sensorAttrs.put("date", DATE_FORMAT.format(date));
sensorAttrs.put("time", TIME_FORMAT.format(date));
sensorAttrs.put("timestamp", triggerTimestamp);
} else {
sensorData.put("state", "");
sensorAttrs.put("date", "");
sensorAttrs.put("time", "");
sensorAttrs.put("timestamp", 0);
}
sensorData.put("icon", "mdi:alarm");
sensorData.put("attributes", sensorAttrs);
dataArray.put(0, sensorData);
dataToSend.put("data", dataArray);
return dataToSend.toString();
} catch (Exception e) {
Log.e(TAG,"getNextAlarmDataToSend", e);
return null;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 B

View File

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group>
<clip-path
android:pathData="M 0 0 H 108 V 108 H 0 V 0 Z" />
<path
android:fillColor="#709ac1"
android:fillAlpha="0"
android:pathData="M 0 0 H 108 V 108 H 0 V 0 Z" />
<path
android:fillColor="#000000"
android:fillAlpha="0.12"
android:pathData="M 70.506 38.389 L 108 72.466 L 108 108 L 77 108 L 35.066 72.466 L 38.373 63.769 L 36.268 50.216 L 43.335 44.523 L 51.841 34.578 L 63.096 42.478 L 68.586 42.478 L 70.506 38.389 Z" />
<path
android:fillColor="#000000"
android:fillAlpha="0.12"
android:pathData="M 28.979 53.708 L 47.736 67.31 L 38.373 58.563 L 36.268 51.52 L 28.979 53.708 Z" />
<path
android:fillColor="#ffffff"
android:pathData="M 77.131 54.24 L 72.878 54.24 L 72.878 72.415 L 56.339 72.415 L 56.339 64.85 L 62.931 58.511 L 64.609 58.784 C 67.349 58.784 69.57 56.649 69.57 54.013 C 69.57 51.378 67.349 49.242 64.609 49.242 C 61.868 49.242 59.647 51.378 59.647 54.013 L 59.883 55.626 L 56.339 59.079 L 56.339 46.63 C 57.898 45.812 58.938 44.244 58.938 42.427 C 58.938 39.792 56.717 37.656 53.976 37.656 C 51.236 37.656 49.015 39.792 49.015 42.427 C 49.015 44.244 50.054 45.812 51.614 46.63 L 51.614 59.079 L 48.07 55.626 L 48.306 54.013 C 48.306 51.378 46.084 49.242 43.344 49.242 C 40.604 49.242 38.383 51.378 38.383 54.013 C 38.383 56.648 40.604 58.784 43.344 58.784 L 45.022 58.511 L 51.614 64.85 L 51.614 72.415 L 35.075 72.415 L 35.075 54.24 L 30.94 54.24 C 29.948 54.24 28.979 54.24 28.979 53.763 C 29.003 53.263 29.995 52.309 31.011 51.332 L 51.614 31.522 C 52.393 30.772 53.197 30 53.976 30 C 54.756 30 55.559 30.772 56.339 31.522 L 65.79 40.609 L 65.79 38.338 L 70.515 38.338 L 70.515 45.153 L 77.084 51.469 C 78.029 52.377 78.997 53.309 79.021 53.786 C 79.021 54.24 78.076 54.24 77.131 54.24 Z M 43.344 51.969 C 43.908 51.969 44.449 52.184 44.848 52.567 C 45.247 52.951 45.471 53.471 45.471 54.013 C 45.471 54.555 45.247 55.076 44.848 55.459 C 44.449 55.842 43.908 56.058 43.344 56.058 C 42.78 56.058 42.239 55.842 41.841 55.459 C 41.442 55.076 41.218 54.555 41.218 54.013 C 41.218 53.471 41.442 52.951 41.841 52.567 C 42.239 52.184 42.78 51.969 43.344 51.969 Z M 64.609 51.969 C 65.79 51.969 66.735 52.877 66.735 54.013 C 66.735 55.149 65.79 56.058 64.609 56.058 C 64.045 56.058 63.504 55.842 63.105 55.459 C 62.706 55.076 62.482 54.555 62.482 54.013 C 62.482 53.471 62.706 52.951 63.105 52.567 C 63.504 52.184 64.045 51.969 64.609 51.969 Z M 53.976 40.382 C 55.158 40.382 56.103 41.291 56.103 42.427 C 56.103 43.563 55.158 44.472 53.976 44.472 C 52.795 44.472 51.85 43.563 51.85 42.427 C 51.85 41.291 52.795 40.382 53.976 40.382 Z" />
</group>
</vector>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/main_color" />
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 571 B

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/main_color"/>
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/main_color"/>
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,12 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="main_color">#709AC1</color>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>

View File

@ -2,15 +2,10 @@ buildscript {
repositories {
google()
jcenter()
maven {
url 'https://maven.fabric.io/public'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.2'
classpath 'com.google.gms:google-services:4.3.3'
classpath 'io.fabric.tools:gradle:1.26.1'
classpath 'com.android.tools.build:gradle:3.1.2'
}
}
@ -18,9 +13,6 @@ allprojects {
repositories {
google()
jcenter()
maven {
url 'https://maven.fabric.io/public'
}
}
}

View File

@ -1,4 +1,5 @@
org.gradle.jvmargs=-Xmx512m
org.gradle.jvmargs=-Xmx2g
org.gradle.daemon=true
org.gradle.caching=true
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
android.enableJetifier=false

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

View File

@ -1 +0,0 @@
include ':app'

View File

@ -1,61 +0,0 @@
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<style>
body {
padding: 0;
margin: 0;
widows: 100%;
height: 100%;
}
video {
width: 100%;
}
</style>
<script>
var messageChannel = '{{message_channel}}';
</script>
</head>
<body>
<video id="screen" width="100%" controls></video>
<script>
if (Hls.isSupported()) {
var video = document.getElementById('screen');
var hls = new Hls();
hls.on(Hls.Events.ERROR, function (event, data) {
if (data.fatal) {
switch(data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
// try to recover network error
console.log("fatal network error encountered, try to recover");
hls.startLoad();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.log("fatal media error encountered, try to recover");
hls.recoverMediaError();
break;
default:
// cannot recover
hls.destroy();
break;
}
}
});
// bind them together
hls.attachMedia(video);
hls.on(Hls.Events.MEDIA_ATTACHED, function () {
console.log("video and hls.js are now bound together !");
hls.loadSource("{{stream_url}}");
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
console.log("manifest loaded, found " + data.levels.length + " quality level");
video.play();
video.onloadedmetadata = function() {
window[messageChannel].postMessage(document.body.clientWidth / video.offsetHeight);
};
});
});
}
</script>
</body>
</html>

View File

@ -1,28 +0,0 @@
<html>
<head>
<style>
body {
padding: 0;
margin: 0;
widows: 100%;
height: 100%;
}
img {
width: 100%;
}
</style>
<script>
var messageChannel = '{{message_channel}}';
window.onload = function() {
var img = document.getElementById('screen');
if (img) {
window[messageChannel].postMessage(document.body.clientWidth / img.offsetHeight);
}
};
</script>
</head>
<body>
<img id="screen" src="{{stream_url}}">
</body>
</html>

View File

@ -1,37 +0,0 @@
window.externalApp = {};
window.externalApp.getExternalAuth = function(options) {
console.log("Starting external auth");
var options = JSON.parse(options);
if (options && options.callback) {
var responseData = {
access_token: "[token]",
expires_in: 1800
};
console.log("Waiting for callback to be added");
setTimeout(function(){
console.log("Calling a callback");
window[options.callback](true, responseData);
}, 900);
}
};
/*
window.externalApp.externalBus = function(message) {
console.log("External bus message: " + message);
var messageObj = JSON.parse(message);
if (messageObj.type == "config/get") {
var responseData = {
id: messageObj.id,
type: "result",
success: true,
result: {
hasSettingsScreen: true
}
};
setTimeout(function(){
window.externalBus(responseData);
}, 500);
} else if (messageObj.type == "config_screen/show") {
HAClient.postMessage('show-settings');
}
};
*/

View File

@ -1,41 +0,0 @@
Flutter crash report; please file at https://github.com/flutter/flutter/issues.
## command
flutter --no-color run --machine --track-widget-creation --device-id=89AY052S4 lib/main.dart
## exception
_InternalLinkedHashMap<String, dynamic>: {code: 105, message: Isolate must be runnable, data: {request: {method: _reloadSources, params: {pause: true, rootLibUri: file:///data/user/0/com.keyboardcrumbs.haclient/code_cache/ha_clientSYJJZI/ha_client/lib/main.dart.incremental.dill, packagesUri: file:///data/user/0/com.keyboardcrumbs.haclient/code_cache/ha_clientSYJJZI/ha_client/.packages, isolateId: isolates/68989666}}, details: Isolate must be runnable before this request is made.}}
```
null```
## flutter doctor
```
[✓] Flutter (Channel stable, v1.7.8+hotfix.4, on Linux, locale en_US.UTF-8)
• Flutter version 1.7.8+hotfix.4 at /home/estevez/sdk/flutter
• Framework revision 20e59316b8 (6 weeks ago), 2019-07-18 20:04:33 -0700
• Engine revision fee001c93f
• Dart version 2.4.0
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
• Android SDK at /home/estevez/Android/Sdk
• Android NDK location not configured (optional; useful for native profiling support)
• Platform android-29, build-tools 29.0.2
• Java binary at: /home/estevez/bin/android-studio/jre/bin/java
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
• All Android licenses accepted.
[✓] Android Studio (version 3.5)
• Android Studio at /home/estevez/bin/android-studio
• Flutter plugin version 38.2.3
• Dart plugin version 191.8423
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
[✓] Connected device (1 available)
• Pixel 3 XL • 89AY052S4 • android-arm64 • Android 9 (API 28)
• No issues found!
```

View File

@ -1,41 +0,0 @@
Flutter crash report; please file at https://github.com/flutter/flutter/issues.
## command
flutter --no-color run --machine --track-widget-creation --device-id=89AY052S4 lib/main.dart
## exception
_InternalLinkedHashMap<String, dynamic>: {code: 105, message: Isolate must be runnable, data: {request: {method: _reloadSources, params: {pause: true, rootLibUri: file:///data/user/0/com.keyboardcrumbs.haclient/code_cache/ha_clientWYMXDL/ha_client/lib/main.dart.incremental.dill, packagesUri: file:///data/user/0/com.keyboardcrumbs.haclient/code_cache/ha_clientWYMXDL/ha_client/.packages, isolateId: isolates/289688365}}, details: Isolate must be runnable before this request is made.}}
```
null```
## flutter doctor
```
[✓] Flutter (Channel stable, v1.7.8+hotfix.4, on Linux, locale en_US.UTF-8)
• Flutter version 1.7.8+hotfix.4 at /home/estevez/sdk/flutter
• Framework revision 20e59316b8 (6 weeks ago), 2019-07-18 20:04:33 -0700
• Engine revision fee001c93f
• Dart version 2.4.0
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
• Android SDK at /home/estevez/Android/Sdk
• Android NDK location not configured (optional; useful for native profiling support)
• Platform android-29, build-tools 29.0.2
• Java binary at: /home/estevez/bin/android-studio/jre/bin/java
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
• All Android licenses accepted.
[✓] Android Studio (version 3.5)
• Android Studio at /home/estevez/bin/android-studio
• Flutter plugin version 38.2.3
• Dart plugin version 191.8423
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
[✓] Connected device (1 available)
• Pixel 3 XL • 89AY052S4 • android-arm64 • Android 9 (API 28)
• No issues found!
```

View File

@ -1,41 +0,0 @@
Flutter crash report; please file at https://github.com/flutter/flutter/issues.
## command
flutter --no-color run --machine --track-widget-creation --device-id=89AY052S4 lib/main.dart
## exception
_InternalLinkedHashMap<String, dynamic>: {code: 105, message: Isolate must be runnable, data: {request: {method: _reloadSources, params: {pause: true, rootLibUri: file:///data/user/0/com.keyboardcrumbs.haclient/code_cache/ha_clientLNSJAH/ha_client/lib/main.dart.incremental.dill, packagesUri: file:///data/user/0/com.keyboardcrumbs.haclient/code_cache/ha_clientLNSJAH/ha_client/.packages, isolateId: isolates/866521062}}, details: Isolate must be runnable before this request is made.}}
```
null```
## flutter doctor
```
[✓] Flutter (Channel stable, v1.7.8+hotfix.4, on Linux, locale en_US.UTF-8)
• Flutter version 1.7.8+hotfix.4 at /home/estevez/sdk/flutter
• Framework revision 20e59316b8 (6 weeks ago), 2019-07-18 20:04:33 -0700
• Engine revision fee001c93f
• Dart version 2.4.0
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
• Android SDK at /home/estevez/Android/Sdk
• Android NDK location not configured (optional; useful for native profiling support)
• Platform android-29, build-tools 29.0.2
• Java binary at: /home/estevez/bin/android-studio/jre/bin/java
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
• All Android licenses accepted.
[✓] Android Studio (version 3.5)
• Android Studio at /home/estevez/bin/android-studio
• Flutter plugin version 38.2.3
• Dart plugin version 191.8423
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
[✓] Connected device (1 available)
• Pixel 3 XL • 89AY052S4 • android-arm64 • Android 9 (API 28)
• No issues found!
```

Binary file not shown.

Binary file not shown.

View File

@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 108 108" width="108pt" height="108pt"><defs><clipPath id="_clipPath_5ibFn1qvppfzobeihHVrZ6swuOsurOBe"><rect width="108" height="108"/></clipPath></defs><g clip-path="url(#_clipPath_5ibFn1qvppfzobeihHVrZ6swuOsurOBe)"><rect width="108" height="108" style="fill:rgb(112,154,193)" fill-opacity="0"/><g><path d=" M 70.506 38.389 L 108 72.466 L 108 108 L 77 108 L 35.066 72.466 L 38.373 63.769 L 36.268 50.216 L 43.335 44.523 L 51.841 34.578 L 63.096 42.478 L 68.586 42.478 L 70.506 38.389 Z " fill="rgb(0,0,0)" fill-opacity="0.12"/><path d=" M 28.979 53.708 L 47.736 67.31 L 38.373 58.563 L 36.268 51.52 L 28.979 53.708 Z " fill="rgb(0,0,0)" fill-opacity="0.12"/></g><path d=" M 77.131 54.24 L 72.878 54.24 L 72.878 72.415 L 56.339 72.415 L 56.339 64.85 L 62.931 58.511 L 64.609 58.784 C 67.349 58.784 69.57 56.649 69.57 54.013 C 69.57 51.378 67.349 49.242 64.609 49.242 C 61.868 49.242 59.647 51.378 59.647 54.013 L 59.883 55.626 L 56.339 59.079 L 56.339 46.63 C 57.898 45.812 58.938 44.244 58.938 42.427 C 58.938 39.792 56.717 37.656 53.976 37.656 C 51.236 37.656 49.015 39.792 49.015 42.427 C 49.015 44.244 50.054 45.812 51.614 46.63 L 51.614 59.079 L 48.07 55.626 L 48.306 54.013 C 48.306 51.378 46.084 49.242 43.344 49.242 C 40.604 49.242 38.383 51.378 38.383 54.013 C 38.383 56.648 40.604 58.784 43.344 58.784 L 45.022 58.511 L 51.614 64.85 L 51.614 72.415 L 35.075 72.415 L 35.075 54.24 L 30.94 54.24 C 29.948 54.24 28.979 54.24 28.979 53.763 C 29.003 53.263 29.995 52.309 31.011 51.332 L 51.614 31.522 C 52.393 30.772 53.197 30 53.976 30 C 54.756 30 55.559 30.772 56.339 31.522 L 65.79 40.609 L 65.79 38.338 L 70.515 38.338 L 70.515 45.153 L 77.084 51.469 C 78.029 52.377 78.997 53.309 79.021 53.786 C 79.021 54.24 78.076 54.24 77.131 54.24 Z M 43.344 51.969 C 43.908 51.969 44.449 52.184 44.848 52.567 C 45.247 52.951 45.471 53.471 45.471 54.013 C 45.471 54.555 45.247 55.076 44.848 55.459 C 44.449 55.842 43.908 56.058 43.344 56.058 C 42.78 56.058 42.239 55.842 41.841 55.459 C 41.442 55.076 41.218 54.555 41.218 54.013 C 41.218 53.471 41.442 52.951 41.841 52.567 C 42.239 52.184 42.78 51.969 43.344 51.969 Z M 64.609 51.969 C 65.79 51.969 66.735 52.877 66.735 54.013 C 66.735 55.149 65.79 56.058 64.609 56.058 C 64.045 56.058 63.504 55.842 63.105 55.459 C 62.706 55.076 62.482 54.555 62.482 54.013 C 62.482 53.471 62.706 52.951 63.105 52.567 C 63.504 52.184 64.045 51.969 64.609 51.969 Z M 53.976 40.382 C 55.158 40.382 56.103 41.291 56.103 42.427 C 56.103 43.563 55.158 44.472 53.976 44.472 C 52.795 44.472 51.85 43.563 51.85 42.427 C 51.85 41.291 52.795 40.382 53.976 40.382 Z " fill="rgb(255,255,255)"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 512 512" width="512pt" height="512pt"><defs><clipPath id="_clipPath_wSm2gq78824Wp66cqyazQcLvYNPCHkOD"><rect width="512" height="512"/></clipPath></defs><g clip-path="url(#_clipPath_wSm2gq78824Wp66cqyazQcLvYNPCHkOD)"><rect width="512" height="512" style="fill:rgb(112,154,193)" fill-opacity="0"/><g><path d=" M 383.477 134.924 L 672.91 397.984 L 672.91 672.285 L 433.606 672.285 L 109.895 397.984 L 135.43 330.847 L 119.174 226.225 L 173.731 182.275 L 239.391 105.507 L 326.273 166.491 L 368.658 166.491 L 383.477 134.924 Z " fill="rgb(0,0,0)" fill-opacity="0.12"/><path d=" M 62.91 253.182 L 207.701 358.179 L 135.43 290.655 L 119.174 236.29 L 62.91 253.182 Z " fill="rgb(0,0,0)" fill-opacity="0.12"/></g><path d=" M 434.558 257.702 L 401.728 257.702 L 401.728 398 L 274.056 398 L 274.056 339.601 L 324.943 290.672 L 337.892 292.776 C 359.049 292.776 376.194 276.291 376.194 255.948 C 376.194 235.605 359.049 219.12 337.892 219.12 C 316.739 219.12 299.591 235.608 299.591 255.948 L 301.415 268.399 L 274.056 295.056 L 274.056 198.952 C 286.094 192.638 294.119 180.538 294.119 166.508 C 294.119 146.168 276.971 129.68 255.818 129.68 C 234.664 129.68 217.516 146.168 217.516 166.508 C 217.516 180.538 225.541 192.638 237.579 198.952 L 237.579 295.056 L 210.221 268.399 L 212.045 255.948 C 212.045 235.608 194.896 219.12 173.743 219.12 C 152.59 219.12 135.442 235.608 135.442 255.948 C 135.442 276.288 152.59 292.776 173.743 292.776 L 186.693 290.672 L 237.579 339.601 L 237.579 398 L 109.907 398 L 109.907 257.702 L 77.99 257.702 C 70.329 257.702 62.851 257.702 62.851 254.019 C 63.034 250.161 70.694 242.795 78.537 235.254 L 237.579 82.329 C 243.598 76.542 249.799 70.579 255.818 70.579 C 261.836 70.579 268.038 76.542 274.056 82.329 L 347.011 152.478 L 347.011 134.941 L 383.489 134.941 L 383.489 187.553 L 434.193 236.306 C 441.488 243.321 448.966 250.511 449.149 254.194 C 449.149 257.702 441.853 257.702 434.558 257.702 Z M 173.743 240.164 C 178.097 240.164 182.272 241.827 185.35 244.787 C 188.429 247.747 190.158 251.762 190.158 255.948 C 190.158 260.134 188.429 264.149 185.35 267.109 C 182.272 270.069 178.097 271.732 173.743 271.732 C 169.39 271.732 165.214 270.069 162.136 267.109 C 159.058 264.149 157.328 260.134 157.328 255.948 C 157.328 251.762 159.058 247.747 162.136 244.787 C 165.214 241.827 169.39 240.164 173.743 240.164 Z M 337.892 240.164 C 347.011 240.164 354.307 247.179 354.307 255.948 C 354.307 264.717 347.011 271.732 337.892 271.732 C 333.539 271.732 329.363 270.069 326.285 267.109 C 323.207 264.149 321.477 260.134 321.477 255.948 C 321.477 251.762 323.207 247.747 326.285 244.787 C 329.363 241.827 333.539 240.164 337.892 240.164 Z M 255.818 150.724 C 264.937 150.724 272.233 157.739 272.233 166.508 C 272.233 175.276 264.937 182.291 255.818 182.291 C 246.698 182.291 239.403 175.276 239.403 166.508 C 239.403 157.739 246.698 150.724 255.818 150.724 Z " fill="rgb(255,255,255)"/></g></svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

View File

@ -1,105 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="33.279999"
height="33.279999"
viewBox="0 0 33.279999 33.279999"
id="svg3384"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="ha_client.svg"
inkscape:export-filename="/home/vyalovegor/Downloads/ha_client.png"
inkscape:export-xdpi="1476.92"
inkscape:export-ydpi="1476.92">
<metadata
id="metadata3392">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3390">
<mask
maskUnits="userSpaceOnUse"
id="mask4987">
<circle
style="fill:#03a9f4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.52243769px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="circle4989"
cx="13.801129"
cy="2.4147582"
r="16.640072"
clip-path="none" />
</mask>
</defs>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2132"
inkscape:window-height="1090"
id="namedview3388"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="11.313709"
inkscape:cx="-10.334868"
inkscape:cy="12.876365"
inkscape:window-x="0"
inkscape:window-y="55"
inkscape:window-maximized="1"
inkscape:current-layer="svg3384" />
<circle
style="fill:#03a9f4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.52243769px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path3394"
cx="16.640072"
cy="16.640072"
r="16.640072"
clip-path="none" />
<path
style="fill:#000000;fill-opacity:0.40119761;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 14.075572,-8.35118 5.9827712,1.0714435 4.4798722,13.280061 26.759769,26.035624 37.105621,5.4863769 c -23.030049,-13.8375569 0,0 -23.030049,-13.8375569 z"
id="path4198"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccc"
clip-path="none"
mask="url(#mask4987)"
transform="translate(2.8389435,14.225313)" />
<rect
style="fill:#626262;fill-opacity:1"
id="rect935"
width="4.895638"
height="3.3191559"
x="13.754303"
y="23.066553" />
<rect
id="rect68"
width="9.3691645"
height="10.871766"
x="11.667261"
y="12.243573"
style="fill:#fdd835;fill-opacity:1" />
<path
id="path116"
d="M 16.296894,5.2595255 3.4710675,17.036739 H 7.3188157 V 27.505374 H 25.274973 V 17.036739 h 3.847749 m -11.543244,7.851477 h -2.565166 v -1.30858 h 2.565166 m 0.64129,-3.166762 v 1.858183 h -3.847747 v -1.858183 a 3.8477482,3.9257379 0 1 1 3.847747,0 z"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;stroke-width:1.29551578" />
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M21.8,13H20V21H13V17.67L15.79,14.88L16.5,15C17.66,15 18.6,14.06 18.6,12.9C18.6,11.74 17.66,10.8 16.5,10.8A2.1,2.1 0 0,0 14.4,12.9L14.5,13.61L13,15.13V9.65C13.66,9.29 14.1,8.6 14.1,7.8A2.1,2.1 0 0,0 12,5.7A2.1,2.1 0 0,0 9.9,7.8C9.9,8.6 10.34,9.29 11,9.65V15.13L9.5,13.61L9.6,12.9A2.1,2.1 0 0,0 7.5,10.8A2.1,2.1 0 0,0 5.4,12.9A2.1,2.1 0 0,0 7.5,15L8.21,14.88L11,17.67V21H4V13H2.25C1.83,13 1.42,13 1.42,12.79C1.43,12.57 1.85,12.15 2.28,11.72L11,3C11.33,2.67 11.67,2.33 12,2.33C12.33,2.33 12.67,2.67 13,3L17,7V6H19V9L21.78,11.78C22.18,12.18 22.59,12.59 22.6,12.8C22.6,13 22.2,13 21.8,13M7.5,12A0.9,0.9 0 0,1 8.4,12.9A0.9,0.9 0 0,1 7.5,13.8A0.9,0.9 0 0,1 6.6,12.9A0.9,0.9 0 0,1 7.5,12M16.5,12C17,12 17.4,12.4 17.4,12.9C17.4,13.4 17,13.8 16.5,13.8A0.9,0.9 0 0,1 15.6,12.9A0.9,0.9 0 0,1 16.5,12M12,6.9C12.5,6.9 12.9,7.3 12.9,7.8C12.9,8.3 12.5,8.7 12,8.7C11.5,8.7 11.1,8.3 11.1,7.8C11.1,7.3 11.5,6.9 12,6.9Z" /></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M12 3L2 12H5V20H19V12H22M13 18H11V17H13M13.5 14.58V16H10.5V14.58A3 3 0 1 1 13.5 14.58Z" /></svg>

Before

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 661 KiB

BIN
images/hassio-192x192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Some files were not shown because too many files have changed in this diff Show More