Compare commits
	
		
			17 Commits
		
	
	
		
			1.1.0-beta
			...
			1.2.0-beta
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | f84728b948 | ||
|  | 26a62d341e | ||
|  | 772bddeb9a | ||
|  | 5b55940ccf | ||
|  | 7683d18e81 | ||
|  | d09afc37b5 | ||
|  | 1c686402d0 | ||
|  | 5f4a3fbdfc | ||
|  | 312ed99e9f | ||
|  | 25e6d51c17 | ||
|  | b501574bab | ||
|  | 53b31d8e90 | ||
|  | 6d80420a9b | ||
|  | e977054139 | ||
|  | 6367d38524 | ||
|  | f9b2d7d84c | ||
|  | 44c28ad106 | 
| @@ -13,6 +13,6 @@ Discuss it on [Discord](https://discord.gg/u9vq7QE) or at [Home Assistant commun | ||||
| #### Last release build status | ||||
| [](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/) | ||||
| #### 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) | ||||
|   | ||||
| @@ -62,7 +62,12 @@ | ||||
|                 <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> | ||||
|         <service | ||||
|             android:name="io.flutter.plugins.androidalarmmanager.AlarmService" | ||||
|             android:permission="android.permission.BIND_JOB_SERVICE" | ||||
| @@ -74,7 +79,7 @@ | ||||
|             android:name="io.flutter.plugins.androidalarmmanager.RebootBroadcastReceiver" | ||||
|             android:enabled="false"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.BOOT_COMPLETED"></action> | ||||
|                 <action android:name="android.intent.action.BOOT_COMPLETED"/> | ||||
|             </intent-filter> | ||||
|         </receiver> | ||||
|     </application> | ||||
|   | ||||
| @@ -145,7 +145,7 @@ public class MessagingService extends FirebaseMessagingService { | ||||
|             connection.connect(); | ||||
|             InputStream input = connection.getInputStream(); | ||||
|             return BitmapFactory.decodeStream(input); | ||||
|         } catch (IOException e) { | ||||
|         } catch (Exception e) { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
| import android.app.AlarmManager; | ||||
| import android.content.Context; | ||||
| import androidx.annotation.NonNull; | ||||
| import android.util.Log; | ||||
| import android.content.BroadcastReceiver; | ||||
| import android.content.Intent; | ||||
| @@ -15,10 +15,14 @@ import android.content.SharedPreferences; | ||||
|  | ||||
| public class NotificationActionReceiver extends BroadcastReceiver { | ||||
|  | ||||
|     private static final String TAG = "NotificationActionReceiver"; | ||||
|     private static final String TAG = "NotificationAction"; | ||||
|  | ||||
|     @Override | ||||
|     public void onReceive(Context context, Intent intent) { | ||||
|         if (intent == null) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         String rawActionData = intent.getStringExtra("actionData"); | ||||
|         if (intent.hasExtra("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=-Xmx2g | ||||
| org.gradle.daemon=true | ||||
| org.gradle.caching=true | ||||
| org.gradle.jvmargs=-Xmx512m | ||||
| android.useAndroidX=true | ||||
| android.enableJetifier=true | ||||
| android.enableR8=true | ||||
|   | ||||
| @@ -90,6 +90,12 @@ class CardData { | ||||
|             return BadgesData(rawData); | ||||
|             break; | ||||
|           default: | ||||
|             if (rawData.containsKey('entity')) { | ||||
|               rawData['entities'] = [rawData['entity']];  | ||||
|             } | ||||
|             if (rawData.containsKey('entities') && rawData['entities'] is List) { | ||||
|               return EntitiesCardData(rawData);  | ||||
|             } | ||||
|             return CardData(null); | ||||
|         } | ||||
|     } catch (error, stacktrace) { | ||||
| @@ -103,7 +109,11 @@ class CardData { | ||||
|       type = rawData['type']; | ||||
|       conditions = rawData['conditions'] ?? []; | ||||
|       showEmpty = rawData['show_empty'] ?? true; | ||||
|       stateFilter = rawData['state_filter']  ?? []; | ||||
|       if (rawData.containsKey('state_filter') && rawData['state_filter'] is List) { | ||||
|         stateFilter = rawData['state_filter']; | ||||
|       } else { | ||||
|         stateFilter = []; | ||||
|       } | ||||
|     } else { | ||||
|       type = CardType.UNKNOWN; | ||||
|       conditions = []; | ||||
| @@ -374,8 +384,14 @@ class LightCardData extends CardData { | ||||
|    | ||||
|   @override | ||||
|   Widget buildCardWidget() { | ||||
|     if (this.entity != null && this.entity.entity is LightEntity) { | ||||
|       return LightCard(card: this); | ||||
|     } | ||||
|     return ErrorCard( | ||||
|       errorText: 'Specify an entity from within the light domain.', | ||||
|       showReportButton: false, | ||||
|     ); | ||||
|   } | ||||
|    | ||||
|   LightCardData(rawData) : super(rawData) { | ||||
|     //Parsing card data | ||||
|   | ||||
| @@ -2,12 +2,21 @@ part of '../main.dart'; | ||||
|  | ||||
| class ErrorCard extends StatelessWidget { | ||||
|   final ErrorCardData card; | ||||
|   final String errorText; | ||||
|   final bool showReportButton; | ||||
|  | ||||
|   const ErrorCard({Key key, this.card}) : super(key: key); | ||||
|   const ErrorCard({Key key, this.card, this.errorText, this.showReportButton: true}) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     String error; | ||||
|     if (errorText == null) { | ||||
|       error = 'There was an error showing ${card?.type}'; | ||||
|     } else { | ||||
|       error = errorText; | ||||
|     } | ||||
|     return CardWrapper( | ||||
|       color: Theme.of(context).errorColor, | ||||
|       child: Padding( | ||||
|         padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding), | ||||
|         child: Column( | ||||
| @@ -15,21 +24,25 @@ class ErrorCard extends StatelessWidget { | ||||
|           crossAxisAlignment: CrossAxisAlignment.center, | ||||
|           children: <Widget>[ | ||||
|             Text( | ||||
|               'There was an error rendering card: ${card.type}. Please copy card config to clipboard and report this issue. Thanks!', | ||||
|               error, | ||||
|               textAlign: TextAlign.center, | ||||
|             ), | ||||
|             card != null ? | ||||
|             RaisedButton( | ||||
|               onPressed: () { | ||||
|                 Clipboard.setData(new ClipboardData(text: card.cardConfig)); | ||||
|               }, | ||||
|               child: Text('Copy card config'), | ||||
|             ), | ||||
|             ) : | ||||
|             Container(width: 0, height: 0), | ||||
|             showReportButton ? | ||||
|             RaisedButton( | ||||
|               onPressed: () { | ||||
|                 Launcher.launchURLInBrowser("https://github.com/estevez-dev/ha_client/issues/new?assignees=&labels=&template=bug_report.md&title="); | ||||
|               }, | ||||
|               child: Text('Report issue'), | ||||
|             ) | ||||
|             ) : | ||||
|             Container(width: 0, height: 0) | ||||
|           ], | ||||
|         ), | ||||
|       ) | ||||
|   | ||||
| @@ -7,6 +7,6 @@ class UnsupportedCard extends StatelessWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Container(); | ||||
|     return Container(height: 20); | ||||
|   }   | ||||
| } | ||||
| @@ -4,12 +4,14 @@ class CardWrapper extends StatelessWidget { | ||||
|    | ||||
|   final Widget child; | ||||
|   final EdgeInsets padding; | ||||
|   final Color color; | ||||
|  | ||||
|   const CardWrapper({Key key, this.child, this.padding: const EdgeInsets.all(0)}) : super(key: key); | ||||
|   const CardWrapper({Key key, this.child, this.color, this.padding: const EdgeInsets.all(0)}) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Card( | ||||
|       color: color, | ||||
|       child: Padding( | ||||
|         padding: padding, | ||||
|         child: child | ||||
|   | ||||
| @@ -40,8 +40,8 @@ class CoverEntity extends Entity { | ||||
|       CoverEntity.SUPPORT_SET_TILT_POSITION); | ||||
|  | ||||
|  | ||||
|   double get currentPosition => _getDoubleAttributeValue('current_position'); | ||||
|   double get currentTiltPosition => _getDoubleAttributeValue('current_tilt_position'); | ||||
|   double get currentPosition => _getDoubleAttributeValue('current_position') ?? 0; | ||||
|   double get currentTiltPosition => _getDoubleAttributeValue('current_tilt_position') ?? 0; | ||||
|   bool get canBeOpened => ((state != EntityState.opening) && (state != EntityState.open)) || (state == EntityState.open && currentPosition != null && currentPosition > 0.0 && currentPosition < 100.0); | ||||
|   bool get canBeClosed => ((state != EntityState.closing) && (state != EntityState.closed)); | ||||
|   bool get canTiltBeOpened => currentTiltPosition < 100; | ||||
|   | ||||
| @@ -8,8 +8,8 @@ class TimerEntity extends Entity { | ||||
|   @override | ||||
|   void update(Map rawData, String webHost) { | ||||
|     super.update(rawData, webHost); | ||||
|     if (attributes.containsKey('duration')) { | ||||
|       String durationSource = "${attributes["duration"]}"; | ||||
|     if (durationSource != null && durationSource.isNotEmpty) { | ||||
|       try { | ||||
|         List<String> durationList = durationSource.split(":"); | ||||
|         if (durationList.length == 1) { | ||||
|   | ||||
| @@ -149,7 +149,7 @@ class EntityCollection { | ||||
|   } | ||||
|  | ||||
|   bool isExist(String entityId) { | ||||
|     return _allEntities[entityId] != null; | ||||
|     return _allEntities.containsKey(entityId); | ||||
|   } | ||||
|  | ||||
|   List<Entity> getByDomains({List<String> includeDomains: const [], List<String> excludeDomains: const [], List<String> stateFiler}) { | ||||
|   | ||||
| @@ -221,7 +221,7 @@ class HomeAssistant { | ||||
|         var data = json.decode(prefs.getString('cached_services')); | ||||
|         _parseServices(data ?? {}); | ||||
|       } catch (e, stacktrace) { | ||||
|        Logger.e(e, stacktrace: stacktrace);   | ||||
|        Logger.e(e, stacktrace: stacktrace, skipCrashlytics: true);   | ||||
|       } | ||||
|     } | ||||
|     await ConnectionManager().sendSocketMessage(type: "get_services").then((data) => _parseServices(data)).catchError((e) {	 | ||||
| @@ -261,7 +261,7 @@ class HomeAssistant { | ||||
|         var data = json.decode(sharedPrefs.getString('cached_panels')); | ||||
|         _parsePanels(data ?? {}); | ||||
|       } catch (e, stacktrace) { | ||||
|         Logger.e(e, stacktrace: stacktrace); | ||||
|         Logger.e(e, stacktrace: stacktrace, skipCrashlytics: true); | ||||
|         panels.clear(); | ||||
|       } | ||||
|     } else { | ||||
|   | ||||
| @@ -156,11 +156,9 @@ part 'cards/badges.dart'; | ||||
| part 'managers/app_settings.dart'; | ||||
|  | ||||
| EventBus eventBus = new EventBus(); | ||||
| //final FirebaseMessaging _firebaseMessaging = FirebaseMessaging(); | ||||
| //FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin(); | ||||
| const String appName = 'HA Client'; | ||||
| 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 { | ||||
|     // Print the exception to the console. | ||||
| @@ -249,6 +247,7 @@ class _HAClientAppState extends State<HAClientApp> { | ||||
|             positiveText: "Ok" | ||||
|           ) | ||||
|         )); | ||||
|         InAppPurchaseConnection.instance.completePurchase(purchase[0]); | ||||
|       } else { | ||||
|         Logger.d("Purchase change handler: ${purchase[0].status}"); | ||||
|       } | ||||
|   | ||||
| @@ -28,6 +28,7 @@ class AppSettings { | ||||
|   String webhookId; | ||||
|   double haVersion; | ||||
|   bool scrollBadges; | ||||
|   bool nextAlarmSensorCreated = false; | ||||
|   DisplayMode displayMode; | ||||
|   AppTheme appTheme; | ||||
|   final int defaultLocationUpdateIntervalMinutes = 20; | ||||
| @@ -61,6 +62,7 @@ class AppSettings { | ||||
|       locationUpdateInterval = Duration(minutes: prefs.getInt("location-interval") ?? | ||||
|         defaultLocationUpdateIntervalMinutes); | ||||
|       locationTrackingEnabled = prefs.getBool("location-enabled") ?? false; | ||||
|       nextAlarmSensorCreated = prefs.getBool("next-alarm-sensor-created") ?? false; | ||||
|       longLivedToken = Hive.box(DEFAULT_HIVE_BOX).get(AUTH_TOKEN_KEY); | ||||
|       oauthUrl = "$httpWebHost/auth/authorize?client_id=${Uri.encodeComponent( | ||||
|           'https://ha-client.app')}&redirect_uri=${Uri | ||||
|   | ||||
| @@ -5,9 +5,9 @@ class MobileAppIntegrationManager { | ||||
|   static final _appRegistrationData = { | ||||
|     "device_name": "", | ||||
|     "app_version": "$appVersion", | ||||
|     "manufacturer": DeviceInfoManager().manufacturer, | ||||
|     "model": DeviceInfoManager().model, | ||||
|     "os_version": DeviceInfoManager().osVersion, | ||||
|     "manufacturer": DeviceInfoManager().manufacturer ?? "unknown", | ||||
|     "model": DeviceInfoManager().model ?? "unknown", | ||||
|     "os_version": DeviceInfoManager().osVersion ?? "0", | ||||
|     "app_data": { | ||||
|       "push_token": "", | ||||
|       "push_url": "https://us-central1-ha-client-c73c4.cloudfunctions.net/pushNotifyV3" | ||||
| @@ -62,12 +62,13 @@ class MobileAppIntegrationManager { | ||||
|           includeAuthHeader: true, | ||||
|           data: json.encode(registrationData) | ||||
|       ).then((response) { | ||||
|         Logger.d("Processing registration responce..."); | ||||
|         Logger.d("Processing registration response..."); | ||||
|         var responseObject = json.decode(response); | ||||
|         AppSettings().webhookId = responseObject["webhook_id"]; | ||||
|         AppSettings().save({ | ||||
|           'app-webhook-id': responseObject["webhook_id"] | ||||
|         }).then((prefs) { | ||||
|         }).then((_) { | ||||
|           _createNextAlarmSensor(true); | ||||
|           completer.complete(); | ||||
|           eventBus.fire(ShowPopupEvent( | ||||
|             popup: Popup( | ||||
| @@ -112,6 +113,7 @@ class MobileAppIntegrationManager { | ||||
|           _askToRegisterApp(); | ||||
|         } else { | ||||
|           Logger.d('App registration works fine'); | ||||
|           _createNextAlarmSensor(false); | ||||
|         } | ||||
|         completer.complete(); | ||||
|       }).catchError((e) { | ||||
| @@ -131,6 +133,42 @@ class MobileAppIntegrationManager { | ||||
|     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() { | ||||
|     eventBus.fire(ShowPopupEvent( | ||||
|       popup: Popup( | ||||
|   | ||||
| @@ -63,7 +63,18 @@ class _PurchasePageState extends State<PurchasePage> { | ||||
|   } | ||||
|  | ||||
|   List<Widget> _buildProducts() { | ||||
|     List<Widget> productWidgets = []; | ||||
|     List<Widget> productWidgets = [ | ||||
|       Card( | ||||
|         child: Padding( | ||||
|           padding: EdgeInsets.all(15), | ||||
|           child: Text( | ||||
|               'This will not unlock any additional functionality. This is only a donation to the HA Client open source project.', | ||||
|               style: Theme.of(context).textTheme.headline5, | ||||
|               textAlign: TextAlign.center, | ||||
|           ) | ||||
|         ) | ||||
|       ) | ||||
|     ]; | ||||
|     for (ProductDetails product in _products) { | ||||
|       productWidgets.add( | ||||
|         ProductPurchase( | ||||
|   | ||||
| @@ -15,7 +15,7 @@ class ProductPurchase extends StatelessWidget { | ||||
|     String buttonText = ''; | ||||
|     String buttonTextInactive = ''; | ||||
|     if (product.id.contains("year")) { | ||||
|       period += "/ year"; | ||||
|       period += "once a year"; | ||||
|       buttonText = "Subscribe"; | ||||
|       buttonTextInactive = "Already"; | ||||
|       priceColor = Colors.amber; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| name: hass_client | ||||
| description: Home Assistant Android Client | ||||
|  | ||||
| version: 1.1.0+1156 | ||||
| version: 1.1.2+1161 | ||||
|  | ||||
|  | ||||
| environment: | ||||
| @@ -19,13 +19,13 @@ dependencies: | ||||
|   date_format: ^1.0.8 | ||||
|   charts_flutter: ^0.8.1 | ||||
|   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_webview_plugin: ^0.3.10+1 | ||||
|   webview_flutter: ^0.3.19+7 | ||||
|   hive: ^1.4.1+1 | ||||
|   hive_flutter: ^0.3.0+2 | ||||
|   device_info: ^0.4.1+4 | ||||
|   device_info: ^0.4.2+4 | ||||
|   geolocator: ^5.3.1 | ||||
|   workmanager: ^0.2.2 | ||||
|   battery: ^1.0.0 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user