commit
1080076e3b
@ -1,6 +1,6 @@
|
|||||||
# HA Client
|
# HA Client
|
||||||
## Native Android client for Home Assistant
|
## Native Android client for Home Assistant
|
||||||
### With notifications and Lovelace UI support
|
### With actionable notifications, location tracking and Lovelace UI support
|
||||||
|
|
||||||
Visit [ha-client.app](http://ha-client.app/) for more info.
|
Visit [ha-client.app](http://ha-client.app/) for more info.
|
||||||
|
|
||||||
@ -12,3 +12,7 @@ Discuss it on [Discord](https://discord.gg/u9vq7QE) or at [Home Assistant commun
|
|||||||
|
|
||||||
#### Last release build status
|
#### Last release build status
|
||||||
[](https://codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5db1862025dc3f0b0288a57a/latest_build)
|
[](https://codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5db1862025dc3f0b0288a57a/latest_build)
|
||||||
|
|
||||||
|
#### Special thanks to
|
||||||
|
- [Crewski](https://github.com/Crewski) for his [HANotify](https://github.com/Crewski/HANotify)
|
||||||
|
- [Home Assistant](https://github.com/home-assistant) for some support and [Home Assistant](https://www.home-assistant.io/)
|
@ -11,6 +11,9 @@ import android.content.SharedPreferences;
|
|||||||
import android.content.SharedPreferences.Editor;
|
import android.content.SharedPreferences.Editor;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
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.OnCompleteListener;
|
||||||
import com.google.android.gms.tasks.Task;
|
import com.google.android.gms.tasks.Task;
|
||||||
import com.google.firebase.iid.FirebaseInstanceId;
|
import com.google.firebase.iid.FirebaseInstanceId;
|
||||||
@ -19,27 +22,40 @@ import com.google.firebase.messaging.FirebaseMessaging;
|
|||||||
|
|
||||||
public class MainActivity extends FlutterActivity {
|
public class MainActivity extends FlutterActivity {
|
||||||
|
|
||||||
|
private static final String CHANNEL = "com.keyboardcrumbs.hassclient/native";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
|
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
|
||||||
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
||||||
|
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL).setMethodCallHandler(
|
||||||
|
new MethodChannel.MethodCallHandler() {
|
||||||
|
@Override
|
||||||
|
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
|
||||||
|
if (call.method.equals("getFCMToken")) {
|
||||||
|
FirebaseInstanceId.getInstance().getInstanceId()
|
||||||
|
.addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
|
||||||
|
@Override
|
||||||
|
public void onComplete(@NonNull Task<InstanceIdResult> task) {
|
||||||
|
if (task.isSuccessful()) {
|
||||||
|
Context context = getActivity();
|
||||||
|
String token = task.getResult().getToken();
|
||||||
|
UpdateTokenTask updateTokenTask = new UpdateTokenTask(context);
|
||||||
|
updateTokenTask.execute(token);
|
||||||
|
result.success(token);
|
||||||
|
} else {
|
||||||
|
result.error("fcm_error", task.getException().getMessage(), task.getException());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
FirebaseInstanceId.getInstance().getInstanceId()
|
|
||||||
.addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
|
|
||||||
@Override
|
|
||||||
public void onComplete(@NonNull Task<InstanceIdResult> task) {
|
|
||||||
if (task.isSuccessful()) {
|
|
||||||
Context context = getActivity();
|
|
||||||
SharedPreferences.Editor editor = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE).edit();
|
|
||||||
String token = task.getResult().getToken();
|
|
||||||
editor.putString("flutter.fcm-token", token);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ import android.media.RingtoneManager;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.google.firebase.messaging.FirebaseMessagingService;
|
import com.google.firebase.messaging.FirebaseMessagingService;
|
||||||
import com.google.firebase.messaging.RemoteMessage;
|
import com.google.firebase.messaging.RemoteMessage;
|
||||||
@ -31,10 +30,8 @@ public class MessagingService extends FirebaseMessagingService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessageReceived(RemoteMessage remoteMessage) {
|
public void onMessageReceived(RemoteMessage remoteMessage) {
|
||||||
Log.d(TAG, "From: " + remoteMessage.getFrom());
|
|
||||||
Map<String, String> data = remoteMessage.getData();
|
Map<String, String> data = remoteMessage.getData();
|
||||||
if (data.size() > 0) {
|
if (data.size() > 0) {
|
||||||
Log.d(TAG, "Message data payload: " + data);
|
|
||||||
if (data.containsKey("body") || data.containsKey("title")) {
|
if (data.containsKey("body") || data.containsKey("title")) {
|
||||||
sendNotification(data);
|
sendNotification(data);
|
||||||
}
|
}
|
||||||
@ -43,17 +40,19 @@ public class MessagingService extends FirebaseMessagingService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewToken(String token) {
|
public void onNewToken(String token) {
|
||||||
Log.d(TAG, "Refreshed token: " + token);
|
UpdateTokenTask updateTokenTask = new UpdateTokenTask(this);
|
||||||
//TODO update token
|
updateTokenTask.execute(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendNotification(Map<String, String> data) {
|
private void sendNotification(Map<String, String> data) {
|
||||||
String channelId, messageBody, messageTitle, imageUrl;
|
String channelId, messageBody, messageTitle, imageUrl, nTag, channelDescription;
|
||||||
String nTag;
|
boolean autoCancel;
|
||||||
if (!data.containsKey("channelId")) {
|
if (!data.containsKey("channelId")) {
|
||||||
channelId = "ha_notify";
|
channelId = "ha_notify";
|
||||||
|
channelDescription = "Default notification channel";
|
||||||
} else {
|
} else {
|
||||||
channelId = data.get("channelId");
|
channelId = data.get("channelId");
|
||||||
|
channelDescription = channelId;
|
||||||
}
|
}
|
||||||
if (!data.containsKey("body")) {
|
if (!data.containsKey("body")) {
|
||||||
messageBody = "";
|
messageBody = "";
|
||||||
@ -70,7 +69,28 @@ public class MessagingService extends FirebaseMessagingService {
|
|||||||
} else {
|
} else {
|
||||||
nTag = data.get("tag");
|
nTag = data.get("tag");
|
||||||
}
|
}
|
||||||
Log.d(TAG, "Notification tag: " + nTag);
|
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");
|
imageUrl = data.get("image");
|
||||||
Intent intent = new Intent(this, MainActivity.class);
|
Intent intent = new Intent(this, MainActivity.class);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
@ -82,7 +102,7 @@ public class MessagingService extends FirebaseMessagingService {
|
|||||||
.setSmallIcon(R.drawable.mini_icon)
|
.setSmallIcon(R.drawable.mini_icon)
|
||||||
.setContentTitle(messageTitle)
|
.setContentTitle(messageTitle)
|
||||||
.setContentText(messageBody)
|
.setContentText(messageBody)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(autoCancel)
|
||||||
.setSound(defaultSoundUri)
|
.setSound(defaultSoundUri)
|
||||||
.setContentIntent(pendingIntent);
|
.setContentIntent(pendingIntent);
|
||||||
if (URLUtil.isValidUrl(imageUrl)) {
|
if (URLUtil.isValidUrl(imageUrl)) {
|
||||||
@ -95,10 +115,11 @@ public class MessagingService extends FirebaseMessagingService {
|
|||||||
for (int i = 1; i <= 3; i++) {
|
for (int i = 1; i <= 3; i++) {
|
||||||
if (data.containsKey("action" + i)) {
|
if (data.containsKey("action" + i)) {
|
||||||
Intent broadcastIntent = new Intent(this, NotificationActionReceiver.class);
|
Intent broadcastIntent = new Intent(this, NotificationActionReceiver.class);
|
||||||
Log.d(TAG, "Putting a tag to the action: " + nTag);
|
if (autoCancel) {
|
||||||
broadcastIntent.putExtra("tag", nTag);
|
broadcastIntent.putExtra("tag", nTag);
|
||||||
|
}
|
||||||
broadcastIntent.putExtra("actionData", data.get("action" + i + "_data"));
|
broadcastIntent.putExtra("actionData", data.get("action" + i + "_data"));
|
||||||
PendingIntent actionIntent = PendingIntent.getBroadcast(this, i, broadcastIntent, 0);
|
PendingIntent actionIntent = PendingIntent.getBroadcast(this, i, broadcastIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
notificationBuilder.addAction(R.drawable.mini_icon, data.get("action" + i), actionIntent);
|
notificationBuilder.addAction(R.drawable.mini_icon, data.get("action" + i), actionIntent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,8 +129,8 @@ public class MessagingService extends FirebaseMessagingService {
|
|||||||
// Since android Oreo notification channel is needed.
|
// Since android Oreo notification channel is needed.
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
NotificationChannel channel = new NotificationChannel(channelId,
|
NotificationChannel channel = new NotificationChannel(channelId,
|
||||||
"Home Assistant notifications",
|
channelDescription,
|
||||||
NotificationManager.IMPORTANCE_DEFAULT);
|
NotificationManager.IMPORTANCE_HIGH);
|
||||||
notificationManager.createNotificationChannel(channel);
|
notificationManager.createNotificationChannel(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,23 +20,21 @@ public class NotificationActionReceiver extends BroadcastReceiver {
|
|||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
String rawActionData = intent.getStringExtra("actionData");
|
String rawActionData = intent.getStringExtra("actionData");
|
||||||
String notificationTag = intent.getStringExtra("tag");
|
if (intent.hasExtra("tag")) {
|
||||||
Log.d(TAG, "Has 'tag': " + intent.hasExtra("tag"));
|
String notificationTag = intent.getStringExtra("tag");
|
||||||
Log.d(TAG, "Canceling notification by tag: " + notificationTag);
|
NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
|
notificationManager.cancel(notificationTag, 0);
|
||||||
notificationManager.cancel(notificationTag, 0);
|
}
|
||||||
SharedPreferences prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE);
|
SharedPreferences prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE);
|
||||||
String webhookId = prefs.getString("flutter.app-webhook-id", null);
|
String webhookId = prefs.getString("flutter.app-webhook-id", null);
|
||||||
if (webhookId != null) {
|
if (webhookId != null) {
|
||||||
try {
|
try {
|
||||||
Log.d(TAG, "Got webhook id");
|
|
||||||
String requestUrl = prefs.getString("flutter.hassio-res-protocol", "") +
|
String requestUrl = prefs.getString("flutter.hassio-res-protocol", "") +
|
||||||
"://" +
|
"://" +
|
||||||
prefs.getString("flutter.hassio-domain", "") +
|
prefs.getString("flutter.hassio-domain", "") +
|
||||||
":" +
|
":" +
|
||||||
prefs.getString("flutter.hassio-port", "") + "/api/webhook/" + webhookId;
|
prefs.getString("flutter.hassio-port", "") + "/api/webhook/" + webhookId;
|
||||||
JSONObject actionData = new JSONObject(rawActionData);
|
JSONObject actionData = new JSONObject(rawActionData);
|
||||||
Log.d(TAG, "request url: " + requestUrl);
|
|
||||||
if (URLUtil.isValidUrl(requestUrl)) {
|
if (URLUtil.isValidUrl(requestUrl)) {
|
||||||
JSONObject dataToSend = new JSONObject();
|
JSONObject dataToSend = new JSONObject();
|
||||||
JSONObject requestData = new JSONObject();
|
JSONObject requestData = new JSONObject();
|
||||||
@ -50,20 +48,22 @@ public class NotificationActionReceiver extends BroadcastReceiver {
|
|||||||
} else {
|
} else {
|
||||||
dataToSend.put("type", "fire_event");
|
dataToSend.put("type", "fire_event");
|
||||||
requestData.put("event_type", "ha_client_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);
|
dataToSend.put("data", requestData);
|
||||||
String stringRequest = dataToSend.toString();
|
String stringRequest = dataToSend.toString();
|
||||||
Log.d(TAG, "Data to send home: " + stringRequest);
|
|
||||||
SendTask sendTask = new SendTask();
|
SendTask sendTask = new SendTask();
|
||||||
sendTask.execute(requestUrl, stringRequest);
|
sendTask.execute(requestUrl, stringRequest);
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Invalid url");
|
Log.w(TAG, "Invalid HA url");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Error handling notification action", e);
|
Log.e(TAG, "Error handling notification action", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Webhook id not found");
|
Log.w(TAG, "Webhook id not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,7 +26,6 @@ public class SendTask extends AsyncTask<String, String, String> {
|
|||||||
String data = params[1];
|
String data = params[1];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Log.d(TAG, "Connecting and sending...");
|
|
||||||
URL url = new URL(urlString);
|
URL url = new URL(urlString);
|
||||||
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||||
urlConnection.setRequestMethod("POST");
|
urlConnection.setRequestMethod("POST");
|
||||||
@ -38,7 +37,6 @@ public class SendTask extends AsyncTask<String, String, String> {
|
|||||||
|
|
||||||
int responseCode = urlConnection.getResponseCode();
|
int responseCode = urlConnection.getResponseCode();
|
||||||
|
|
||||||
Log.d(TAG, "responseCode: " + responseCode);
|
|
||||||
urlConnection.disconnect();
|
urlConnection.disconnect();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Error sending data", e);
|
Log.e(TAG, "Error sending data", e);
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package com.keyboardcrumbs.hassclient;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import android.webkit.URLUtil;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.Context;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
|
||||||
|
public class UpdateTokenTask extends AsyncTask<String, String, String> {
|
||||||
|
|
||||||
|
private static final String TAG = "UpdateTokenTask";
|
||||||
|
|
||||||
|
private WeakReference<Context> contextRef;
|
||||||
|
|
||||||
|
public UpdateTokenTask(Context context){
|
||||||
|
contextRef = new WeakReference<>(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
super.onPreExecute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String doInBackground(String... params) {
|
||||||
|
Log.d(TAG, "Updating push token");
|
||||||
|
Context context = contextRef.get();
|
||||||
|
if (context != null) {
|
||||||
|
String token = params[0];
|
||||||
|
SharedPreferences prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putString("flutter.npush-token", token);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -16,10 +16,8 @@ import 'package:http/http.dart' as http;
|
|||||||
import 'package:charts_flutter/flutter.dart' as charts;
|
import 'package:charts_flutter/flutter.dart' as charts;
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||||
//import 'package:firebase_messaging/firebase_messaging.dart';
|
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:device_info/device_info.dart';
|
import 'package:device_info/device_info.dart';
|
||||||
//import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
||||||
import 'package:in_app_purchase/in_app_purchase.dart';
|
import 'package:in_app_purchase/in_app_purchase.dart';
|
||||||
import 'plugins/dynamic_multi_column_layout.dart';
|
import 'plugins/dynamic_multi_column_layout.dart';
|
||||||
import 'plugins/spoiler_card.dart';
|
import 'plugins/spoiler_card.dart';
|
||||||
@ -161,7 +159,7 @@ EventBus eventBus = new EventBus();
|
|||||||
//FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
|
//FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
|
||||||
const String appName = 'HA Client';
|
const String appName = 'HA Client';
|
||||||
const String appVersion = String.fromEnvironment('versionName', defaultValue: '0.0.0');
|
const String appVersion = String.fromEnvironment('versionName', defaultValue: '0.0.0');
|
||||||
const whatsNewUrl = 'http://ha-client.app/service/whats_new_1.1.0.md';
|
const whatsNewUrl = 'http://ha-client.app/service/whats_new_1.1.0-b2.md';
|
||||||
|
|
||||||
Future<void> _reportError(dynamic error, dynamic stackTrace) async {
|
Future<void> _reportError(dynamic error, dynamic stackTrace) async {
|
||||||
// Print the exception to the console.
|
// Print the exception to the console.
|
||||||
|
@ -20,10 +20,8 @@ class AppSettings {
|
|||||||
String tempToken;
|
String tempToken;
|
||||||
String oauthUrl;
|
String oauthUrl;
|
||||||
String webhookId;
|
String webhookId;
|
||||||
String fcmToken;
|
|
||||||
double haVersion;
|
double haVersion;
|
||||||
bool scrollBadges;
|
bool scrollBadges;
|
||||||
int appIntegrationVersion;
|
|
||||||
AppTheme appTheme;
|
AppTheme appTheme;
|
||||||
final int defaultLocationUpdateIntervalMinutes = 20;
|
final int defaultLocationUpdateIntervalMinutes = 20;
|
||||||
Duration locationUpdateInterval;
|
Duration locationUpdateInterval;
|
||||||
@ -41,12 +39,10 @@ class AppSettings {
|
|||||||
if (full) {
|
if (full) {
|
||||||
Logger.d('Loading settings...');
|
Logger.d('Loading settings...');
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
fcmToken = prefs.getString('fcm-token');
|
|
||||||
_domain = prefs.getString('hassio-domain');
|
_domain = prefs.getString('hassio-domain');
|
||||||
_port = prefs.getString('hassio-port');
|
_port = prefs.getString('hassio-port');
|
||||||
webhookId = prefs.getString('app-webhook-id');
|
webhookId = prefs.getString('app-webhook-id');
|
||||||
mobileAppDeviceName = prefs.getString('app-integration-device-name');
|
mobileAppDeviceName = prefs.getString('app-integration-device-name');
|
||||||
appIntegrationVersion = prefs.getInt('app-integration-version') ?? 0;
|
|
||||||
scrollBadges = prefs.getBool('scroll-badges') ?? true;
|
scrollBadges = prefs.getBool('scroll-badges') ?? true;
|
||||||
displayHostname = "$_domain:$_port";
|
displayHostname = "$_domain:$_port";
|
||||||
webSocketAPIEndpoint =
|
webSocketAPIEndpoint =
|
||||||
@ -71,6 +67,11 @@ class AppSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<dynamic> loadSingle(String key) async {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.get('$key');
|
||||||
|
}
|
||||||
|
|
||||||
Future save(Map<String, dynamic> settings) async {
|
Future save(Map<String, dynamic> settings) async {
|
||||||
if (settings != null && settings.isNotEmpty) {
|
if (settings != null && settings.isNotEmpty) {
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
@ -2,8 +2,6 @@ part of '../main.dart';
|
|||||||
|
|
||||||
class MobileAppIntegrationManager {
|
class MobileAppIntegrationManager {
|
||||||
|
|
||||||
static const INTEGRATION_VERSION = 3;
|
|
||||||
|
|
||||||
static final _appRegistrationData = {
|
static final _appRegistrationData = {
|
||||||
"device_name": "",
|
"device_name": "",
|
||||||
"app_version": "$appVersion",
|
"app_version": "$appVersion",
|
||||||
@ -23,10 +21,28 @@ class MobileAppIntegrationManager {
|
|||||||
return '${HomeAssistant().userName}\'s ${DeviceInfoManager().model}';
|
return '${HomeAssistant().userName}\'s ${DeviceInfoManager().model}';
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future checkAppRegistration() {
|
static const platform = const MethodChannel('com.keyboardcrumbs.hassclient/native');
|
||||||
|
|
||||||
|
static Future checkAppRegistration() async {
|
||||||
|
String fcmToken = await AppSettings().loadSingle('npush-token');
|
||||||
|
if (fcmToken != null) {
|
||||||
|
Logger.d("[MobileAppIntegrationManager] token exist");
|
||||||
|
await _doCheck(fcmToken);
|
||||||
|
} else {
|
||||||
|
Logger.d("[MobileAppIntegrationManager] no fcm token. Requesting...");
|
||||||
|
try {
|
||||||
|
fcmToken = await platform.invokeMethod('getFCMToken');
|
||||||
|
await _doCheck(fcmToken);
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
Logger.e('[MobileAppIntegrationManager] Failed to get FCM token from native: ${e.message}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future _doCheck(String fcmToken) {
|
||||||
Completer completer = Completer();
|
Completer completer = Completer();
|
||||||
_appRegistrationData["device_name"] = AppSettings().mobileAppDeviceName ?? getDefaultDeviceName();
|
_appRegistrationData["device_name"] = AppSettings().mobileAppDeviceName ?? getDefaultDeviceName();
|
||||||
(_appRegistrationData["app_data"] as Map)["push_token"] = "${AppSettings().fcmToken}";
|
(_appRegistrationData["app_data"] as Map)["push_token"] = "$fcmToken";
|
||||||
if (AppSettings().webhookId == null) {
|
if (AppSettings().webhookId == null) {
|
||||||
Logger.d("Mobile app was not registered yet. Registering...");
|
Logger.d("Mobile app was not registered yet. Registering...");
|
||||||
var registrationData = Map.from(_appRegistrationData);
|
var registrationData = Map.from(_appRegistrationData);
|
||||||
@ -49,10 +65,8 @@ class MobileAppIntegrationManager {
|
|||||||
Logger.d("Processing registration responce...");
|
Logger.d("Processing registration responce...");
|
||||||
var responseObject = json.decode(response);
|
var responseObject = json.decode(response);
|
||||||
AppSettings().webhookId = responseObject["webhook_id"];
|
AppSettings().webhookId = responseObject["webhook_id"];
|
||||||
AppSettings().appIntegrationVersion = INTEGRATION_VERSION;
|
|
||||||
AppSettings().save({
|
AppSettings().save({
|
||||||
'app-webhook-id': responseObject["webhook_id"],
|
'app-webhook-id': responseObject["webhook_id"]
|
||||||
'app-integration-version': INTEGRATION_VERSION
|
|
||||||
}).then((prefs) {
|
}).then((prefs) {
|
||||||
completer.complete();
|
completer.complete();
|
||||||
eventBus.fire(ShowPopupEvent(
|
eventBus.fire(ShowPopupEvent(
|
||||||
@ -76,7 +90,6 @@ class MobileAppIntegrationManager {
|
|||||||
}
|
}
|
||||||
_showError();
|
_showError();
|
||||||
});
|
});
|
||||||
return completer.future;
|
|
||||||
} else {
|
} else {
|
||||||
Logger.d("App was previously registered. Checking...");
|
Logger.d("App was previously registered. Checking...");
|
||||||
var updateData = {
|
var updateData = {
|
||||||
@ -98,12 +111,7 @@ class MobileAppIntegrationManager {
|
|||||||
Logger.w("No registration data in response. MobileApp integration was removed or broken");
|
Logger.w("No registration data in response. MobileApp integration was removed or broken");
|
||||||
_askToRegisterApp();
|
_askToRegisterApp();
|
||||||
} else {
|
} else {
|
||||||
if (INTEGRATION_VERSION > AppSettings().appIntegrationVersion) {
|
Logger.d('App registration works fine');
|
||||||
Logger.d('App registration needs to be updated');
|
|
||||||
_askToRemoveAndRegisterApp();
|
|
||||||
} else {
|
|
||||||
Logger.d('App registration works fine');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
completer.complete();
|
completer.complete();
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
@ -119,8 +127,8 @@ class MobileAppIntegrationManager {
|
|||||||
}
|
}
|
||||||
completer.complete();
|
completer.complete();
|
||||||
});
|
});
|
||||||
return completer.future;
|
|
||||||
}
|
}
|
||||||
|
return completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _showError() {
|
static void _showError() {
|
||||||
@ -137,20 +145,6 @@ class MobileAppIntegrationManager {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _askToRemoveAndRegisterApp() {
|
|
||||||
eventBus.fire(ShowPopupEvent(
|
|
||||||
popup: Popup(
|
|
||||||
title: "Mobile app integration needs to be updated",
|
|
||||||
body: "You need to update HA Client integration to continue using notifications and location tracking. Please remove 'Mobile App' integration for this device from your Home Assistant and restart Home Assistant. Then go back to HA Client to create app integration again.",
|
|
||||||
positiveText: "Ok",
|
|
||||||
negativeText: "Report an issue",
|
|
||||||
onNegative: () {
|
|
||||||
Launcher.launchURLInBrowser("https://github.com/estevez-dev/ha_client/issues/new");
|
|
||||||
},
|
|
||||||
)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _askToRegisterApp() {
|
static void _askToRegisterApp() {
|
||||||
eventBus.fire(ShowPopupEvent(
|
eventBus.fire(ShowPopupEvent(
|
||||||
popup: RegisterAppPopup(
|
popup: RegisterAppPopup(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
name: hass_client
|
name: hass_client
|
||||||
description: Home Assistant Android Client
|
description: Home Assistant Android Client
|
||||||
|
|
||||||
version: 0.0.0+1146
|
version: 0.0.0+1151
|
||||||
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
Reference in New Issue
Block a user