Compare commits
	
		
			6 Commits
		
	
	
		
			1.1.1
			...
			1.2.0-beta
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | f84728b948 | ||
|  | 26a62d341e | ||
|  | 772bddeb9a | ||
|  | 5b55940ccf | ||
|  | 7683d18e81 | ||
|  | d09afc37b5 | 
| @@ -13,6 +13,6 @@ 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 | #### Projects used | ||||||
| - [Crewski](https://github.com/Crewski) for his [HANotify](https://github.com/Crewski/HANotify) | - [HANotify](https://github.com/Crewski/HANotify) by [Crewski](https://github.com/Crewski) | ||||||
| - [Home Assistant](https://github.com/home-assistant) for some support and [Home Assistant](https://www.home-assistant.io/) | - [hassalarm](https://github.com/Johboh/hassalarm) by [Johboh](https://github.com/Johboh) distributed under [MIT License](https://github.com/Johboh/hassalarm/blob/master/LICENSE) | ||||||
|   | |||||||
| @@ -62,7 +62,12 @@ | |||||||
|                 <action android:name="android.intent.action.INPUT_METHOD_CHANGED" /> |                 <action android:name="android.intent.action.INPUT_METHOD_CHANGED" /> | ||||||
|             </intent-filter> |             </intent-filter> | ||||||
|         </receiver> |         </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> | ||||||
|         <service |         <service | ||||||
|             android:name="io.flutter.plugins.androidalarmmanager.AlarmService" |             android:name="io.flutter.plugins.androidalarmmanager.AlarmService" | ||||||
|             android:permission="android.permission.BIND_JOB_SERVICE" |             android:permission="android.permission.BIND_JOB_SERVICE" | ||||||
| @@ -74,7 +79,7 @@ | |||||||
|             android:name="io.flutter.plugins.androidalarmmanager.RebootBroadcastReceiver" |             android:name="io.flutter.plugins.androidalarmmanager.RebootBroadcastReceiver" | ||||||
|             android:enabled="false"> |             android:enabled="false"> | ||||||
|             <intent-filter> |             <intent-filter> | ||||||
|                 <action android:name="android.intent.action.BOOT_COMPLETED"></action> |                 <action android:name="android.intent.action.BOOT_COMPLETED"/> | ||||||
|             </intent-filter> |             </intent-filter> | ||||||
|         </receiver> |         </receiver> | ||||||
|     </application> |     </application> | ||||||
|   | |||||||
| @@ -0,0 +1,51 @@ | |||||||
|  | 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.ExistingWorkPolicy; | ||||||
|  | import androidx.work.NetworkType; | ||||||
|  | import androidx.work.OneTimeWorkRequest; | ||||||
|  | import androidx.work.WorkManager; | ||||||
|  | import androidx.work.WorkRequest; | ||||||
|  |  | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | public class NextAlarmBroadcastReceiver extends BroadcastReceiver { | ||||||
|  |  | ||||||
|  |     private static final String TAG = "NextAlarmReceiver"; | ||||||
|  |  | ||||||
|  |     @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(); | ||||||
|  |  | ||||||
|  |         OneTimeWorkRequest uploadWorkRequest = | ||||||
|  |                 new OneTimeWorkRequest.Builder(UpdateNextAlarmWorker.class) | ||||||
|  |                         .setBackoffCriteria( | ||||||
|  |                                 BackoffPolicy.EXPONENTIAL, | ||||||
|  |                                 10, | ||||||
|  |                                 TimeUnit.SECONDS) | ||||||
|  |                         .setConstraints(constraints) | ||||||
|  |                         .build(); | ||||||
|  |  | ||||||
|  |         WorkManager | ||||||
|  |                 .getInstance(context) | ||||||
|  |                 .enqueueUniqueWork("NextAlarmUpdate", ExistingWorkPolicy.REPLACE, uploadWorkRequest); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| package com.keyboardcrumbs.hassclient; | package com.keyboardcrumbs.hassclient; | ||||||
|  |  | ||||||
|  | import android.app.AlarmManager; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import androidx.annotation.NonNull; |  | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.content.BroadcastReceiver; | import android.content.BroadcastReceiver; | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| @@ -15,10 +15,14 @@ import android.content.SharedPreferences; | |||||||
|  |  | ||||||
| public class NotificationActionReceiver extends BroadcastReceiver { | public class NotificationActionReceiver extends BroadcastReceiver { | ||||||
|  |  | ||||||
|     private static final String TAG = "NotificationActionReceiver"; |     private static final String TAG = "NotificationAction"; | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onReceive(Context context, Intent intent) { |     public void onReceive(Context context, Intent intent) { | ||||||
|  |         if (intent == null) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         String rawActionData = intent.getStringExtra("actionData"); |         String rawActionData = intent.getStringExtra("actionData"); | ||||||
|         if (intent.hasExtra("tag")) { |         if (intent.hasExtra("tag")) { | ||||||
|             String notificationTag = intent.getStringExtra("tag"); |             String notificationTag = intent.getStringExtra("tag"); | ||||||
|   | |||||||
| @@ -0,0 +1,119 @@ | |||||||
|  | package com.keyboardcrumbs.hassclient; | ||||||
|  |  | ||||||
|  | import android.app.AlarmManager; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.SharedPreferences; | ||||||
|  | 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.text.SimpleDateFormat; | ||||||
|  | import java.util.Calendar; | ||||||
|  | import java.util.Locale; | ||||||
|  |  | ||||||
|  | public class UpdateNextAlarmWorker extends Worker { | ||||||
|  |  | ||||||
|  |     private Context currentContext; | ||||||
|  |     private static final String TAG = "NextAlarmWorker"; | ||||||
|  |     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 UpdateNextAlarmWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { | ||||||
|  |         super(context, workerParams); | ||||||
|  |         currentContext = context; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @NonNull | ||||||
|  |     @Override | ||||||
|  |     public Result doWork() { | ||||||
|  |         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(); | ||||||
|  |  | ||||||
|  |         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; | ||||||
|  |                 JSONObject dataToSend = new JSONObject(); | ||||||
|  |                 if (URLUtil.isValidUrl(requestUrl)) { | ||||||
|  |                     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); | ||||||
|  |                         sensorData.put("state", DATE_TIME_FORMAT.format(calendar.getTime())); | ||||||
|  |                         sensorAttrs.put("date", DATE_FORMAT.format(calendar.getTime())); | ||||||
|  |                         sensorAttrs.put("time", TIME_FORMAT.format(calendar.getTime())); | ||||||
|  |                         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); | ||||||
|  |  | ||||||
|  |                     String stringRequest = dataToSend.toString(); | ||||||
|  |                     try { | ||||||
|  |                         URL url = new URL(requestUrl); | ||||||
|  |                         HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); | ||||||
|  |                         urlConnection.setRequestMethod("POST"); | ||||||
|  |                         urlConnection.setRequestProperty("Content-Type", "application/json"); | ||||||
|  |                         urlConnection.setDoOutput(true); | ||||||
|  |                         byte[] outputBytes = stringRequest.getBytes("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 setting next alarm", e); | ||||||
|  |                 return Result.failure(); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             Log.w(TAG, "Webhook id not found"); | ||||||
|  |             return Result.failure(); | ||||||
|  |         } | ||||||
|  |         return Result.success(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,6 +1,4 @@ | |||||||
| org.gradle.jvmargs=-Xmx1g | org.gradle.jvmargs=-Xmx512m | ||||||
| org.gradle.daemon=true |  | ||||||
| org.gradle.caching=true |  | ||||||
| android.useAndroidX=true | android.useAndroidX=true | ||||||
| android.enableJetifier=true | android.enableJetifier=true | ||||||
| android.enableR8=true | android.enableR8=true | ||||||
|   | |||||||
| @@ -156,11 +156,9 @@ part 'cards/badges.dart'; | |||||||
| part 'managers/app_settings.dart'; | part 'managers/app_settings.dart'; | ||||||
|  |  | ||||||
| EventBus eventBus = new EventBus(); | EventBus eventBus = new EventBus(); | ||||||
| //final FirebaseMessaging _firebaseMessaging = FirebaseMessaging(); |  | ||||||
| //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-b2.md'; | const whatsNewUrl = 'http://ha-client.app/service/whats_new_1.2.0.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. | ||||||
| @@ -249,6 +247,7 @@ class _HAClientAppState extends State<HAClientApp> { | |||||||
|             positiveText: "Ok" |             positiveText: "Ok" | ||||||
|           ) |           ) | ||||||
|         )); |         )); | ||||||
|  |         InAppPurchaseConnection.instance.completePurchase(purchase[0]); | ||||||
|       } else { |       } else { | ||||||
|         Logger.d("Purchase change handler: ${purchase[0].status}"); |         Logger.d("Purchase change handler: ${purchase[0].status}"); | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ class AppSettings { | |||||||
|   String webhookId; |   String webhookId; | ||||||
|   double haVersion; |   double haVersion; | ||||||
|   bool scrollBadges; |   bool scrollBadges; | ||||||
|  |   bool nextAlarmSensorCreated = false; | ||||||
|   DisplayMode displayMode; |   DisplayMode displayMode; | ||||||
|   AppTheme appTheme; |   AppTheme appTheme; | ||||||
|   final int defaultLocationUpdateIntervalMinutes = 20; |   final int defaultLocationUpdateIntervalMinutes = 20; | ||||||
| @@ -61,6 +62,7 @@ class AppSettings { | |||||||
|       locationUpdateInterval = Duration(minutes: prefs.getInt("location-interval") ?? |       locationUpdateInterval = Duration(minutes: prefs.getInt("location-interval") ?? | ||||||
|         defaultLocationUpdateIntervalMinutes); |         defaultLocationUpdateIntervalMinutes); | ||||||
|       locationTrackingEnabled = prefs.getBool("location-enabled") ?? false; |       locationTrackingEnabled = prefs.getBool("location-enabled") ?? false; | ||||||
|  |       nextAlarmSensorCreated = prefs.getBool("next-alarm-sensor-created") ?? false; | ||||||
|       longLivedToken = Hive.box(DEFAULT_HIVE_BOX).get(AUTH_TOKEN_KEY); |       longLivedToken = Hive.box(DEFAULT_HIVE_BOX).get(AUTH_TOKEN_KEY); | ||||||
|       oauthUrl = "$httpWebHost/auth/authorize?client_id=${Uri.encodeComponent( |       oauthUrl = "$httpWebHost/auth/authorize?client_id=${Uri.encodeComponent( | ||||||
|           'https://ha-client.app')}&redirect_uri=${Uri |           'https://ha-client.app')}&redirect_uri=${Uri | ||||||
|   | |||||||
| @@ -62,12 +62,13 @@ class MobileAppIntegrationManager { | |||||||
|           includeAuthHeader: true, |           includeAuthHeader: true, | ||||||
|           data: json.encode(registrationData) |           data: json.encode(registrationData) | ||||||
|       ).then((response) { |       ).then((response) { | ||||||
|         Logger.d("Processing registration responce..."); |         Logger.d("Processing registration response..."); | ||||||
|         var responseObject = json.decode(response); |         var responseObject = json.decode(response); | ||||||
|         AppSettings().webhookId = responseObject["webhook_id"]; |         AppSettings().webhookId = responseObject["webhook_id"]; | ||||||
|         AppSettings().save({ |         AppSettings().save({ | ||||||
|           'app-webhook-id': responseObject["webhook_id"] |           'app-webhook-id': responseObject["webhook_id"] | ||||||
|         }).then((prefs) { |         }).then((_) { | ||||||
|  |           _createNextAlarmSensor(true); | ||||||
|           completer.complete(); |           completer.complete(); | ||||||
|           eventBus.fire(ShowPopupEvent( |           eventBus.fire(ShowPopupEvent( | ||||||
|             popup: Popup( |             popup: Popup( | ||||||
| @@ -112,6 +113,7 @@ class MobileAppIntegrationManager { | |||||||
|           _askToRegisterApp(); |           _askToRegisterApp(); | ||||||
|         } else { |         } else { | ||||||
|           Logger.d('App registration works fine'); |           Logger.d('App registration works fine'); | ||||||
|  |           _createNextAlarmSensor(false); | ||||||
|         } |         } | ||||||
|         completer.complete(); |         completer.complete(); | ||||||
|       }).catchError((e) { |       }).catchError((e) { | ||||||
| @@ -131,6 +133,42 @@ class MobileAppIntegrationManager { | |||||||
|     return completer.future; |     return completer.future; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   static _createNextAlarmSensor(bool force) { | ||||||
|  |     if (AppSettings().nextAlarmSensorCreated && !force) { | ||||||
|  |       Logger.d("Next alarm sensor was previously created"); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     Logger.d("Creating next alarm sensor..."); | ||||||
|  |     ConnectionManager().sendHTTPPost( | ||||||
|  |         endPoint: "/api/webhook/${AppSettings().webhookId}", | ||||||
|  |         includeAuthHeader: false, | ||||||
|  |         data: json.encode( | ||||||
|  |             { | ||||||
|  |               "data": { | ||||||
|  |                 "device_class": "timestamp", | ||||||
|  |                 "icon": "mdi:alarm", | ||||||
|  |                 "name": "Next Alarm", | ||||||
|  |                 "state": "", | ||||||
|  |                 "type": "sensor", | ||||||
|  |                 "unique_id": "next_alarm" | ||||||
|  |               }, | ||||||
|  |               "type": "register_sensor" | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |     ).then((_){ | ||||||
|  |       AppSettings().nextAlarmSensorCreated = true; | ||||||
|  |       AppSettings().save({ | ||||||
|  |         'next-alarm-sensor-created': true | ||||||
|  |       }); | ||||||
|  |     }).catchError((e) { | ||||||
|  |       if (e is http.Response) { | ||||||
|  |         Logger.e("Error creating next alarm sensor: ${e.statusCode}: ${e.body}"); | ||||||
|  |       } else { | ||||||
|  |         Logger.e("Error creating next alarm sensor: ${e?.toString()}"); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   static void _showError() { |   static void _showError() { | ||||||
|     eventBus.fire(ShowPopupEvent( |     eventBus.fire(ShowPopupEvent( | ||||||
|       popup: Popup( |       popup: Popup( | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| name: hass_client | name: hass_client | ||||||
| description: Home Assistant Android Client | description: Home Assistant Android Client | ||||||
|  |  | ||||||
| version: 1.1.1+1158 | version: 1.1.2+1161 | ||||||
|  |  | ||||||
|  |  | ||||||
| environment: | environment: | ||||||
| @@ -19,7 +19,7 @@ dependencies: | |||||||
|   date_format: ^1.0.8 |   date_format: ^1.0.8 | ||||||
|   charts_flutter: ^0.8.1 |   charts_flutter: ^0.8.1 | ||||||
|   flutter_markdown: ^0.3.3 |   flutter_markdown: ^0.3.3 | ||||||
|   in_app_purchase: ^0.3.0+3 |   in_app_purchase: ^0.3.4 | ||||||
|   flutter_custom_tabs: ^0.6.0 |   flutter_custom_tabs: ^0.6.0 | ||||||
|   flutter_webview_plugin: ^0.3.10+1 |   flutter_webview_plugin: ^0.3.10+1 | ||||||
|   webview_flutter: ^0.3.19+7 |   webview_flutter: ^0.3.19+7 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user