Compare commits
	
		
			36 Commits
		
	
	
		
			1.0.0
			...
			foreground
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | c844e21e76 | ||
|  | 24d42c9597 | ||
|  | 9078ad81e8 | ||
|  | 7cba6c8a10 | ||
|  | c1f9c8c16d | ||
|  | 1d1d132b33 | ||
|  | e258b3bc2c | ||
|  | 13508ee92f | ||
|  | 4fbf58e707 | ||
|  | a3442f84ca | ||
|  | 6a6ab3b2cb | ||
|  | d9fa553e2f | ||
|  | cde5d9b912 | ||
|  | 3468446b5b | ||
|  | 326434273a | ||
|  | 470d3be946 | ||
|  | d1032be6a6 | ||
|  | cffac8e1f8 | ||
|  | 870bc25dd9 | ||
|  | de713024f6 | ||
|  | 4d4add4581 | ||
|  | 1670c8e505 | ||
|  | 55eb1b5125 | ||
|  | dbeaaaf91e | ||
|  | 8166d8ce6d | ||
|  | 35bcf0c1fa | ||
|  | 9c1d240962 | ||
|  | a76652b552 | ||
|  | a140f993d0 | ||
|  | ded60a2867 | ||
|  | b86602bcdb | ||
|  | 02ea45469f | ||
|  | 90105c3b09 | ||
|  | 3d828914cc | ||
|  | 8cd5776bc6 | ||
|  | 17ec73b176 | 
| @@ -6,7 +6,12 @@ | |||||||
|     <uses-permission android:name="android.permission.INTERNET"/> |     <uses-permission android:name="android.permission.INTERNET"/> | ||||||
|     <uses-permission android:name="android.permission.VIBRATE" /> |     <uses-permission android:name="android.permission.VIBRATE" /> | ||||||
|     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> |     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> | ||||||
|  |     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> | ||||||
|  |     <uses-permission android:name="android.permission.WAKE_LOCK"/> | ||||||
|  |     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> | ||||||
|  |     <!-- | ||||||
|     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> |     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> | ||||||
|  |     --> | ||||||
|     <uses-permission android:name="android.permission.WAKE_LOCK"/> |     <uses-permission android:name="android.permission.WAKE_LOCK"/> | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -53,6 +58,17 @@ | |||||||
|             </intent-filter> |             </intent-filter> | ||||||
|         </activity> |         </activity> | ||||||
|  |  | ||||||
|  |         <receiver android:name="rekab.app.background_locator.LocatorBroadcastReceiver" | ||||||
|  |             android:enabled="true" | ||||||
|  |             android:exported="true"/> | ||||||
|  |         <service android:name="rekab.app.background_locator.LocatorService" | ||||||
|  |             android:permission="android.permission.BIND_JOB_SERVICE" | ||||||
|  |             android:exported="true"/> | ||||||
|  |         <service android:name="rekab.app.background_locator.IsolateHolderService" | ||||||
|  |             android:permission="android.permission.FOREGROUND_SERVICE" | ||||||
|  |             android:exported="true"/> | ||||||
|  |  | ||||||
|  |         <!--  | ||||||
|         <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" | ||||||
| @@ -67,5 +83,6 @@ | |||||||
|                 <action android:name="android.intent.action.BOOT_COMPLETED"></action> |                 <action android:name="android.intent.action.BOOT_COMPLETED"></action> | ||||||
|             </intent-filter> |             </intent-filter> | ||||||
|         </receiver> |         </receiver> | ||||||
|  |     --> | ||||||
|     </application> |     </application> | ||||||
| </manifest> | </manifest> | ||||||
|   | |||||||
| @@ -11,9 +11,10 @@ window.externalApp.getExternalAuth = function(options) { | |||||||
|         setTimeout(function(){ |         setTimeout(function(){ | ||||||
|             console.log("Calling a callback"); |             console.log("Calling a callback"); | ||||||
|             window[options.callback](true, responseData); |             window[options.callback](true, responseData); | ||||||
|         }, 500); |         }, 900); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  | /* | ||||||
| window.externalApp.externalBus = function(message) { | window.externalApp.externalBus = function(message) { | ||||||
|     console.log("External bus message: " + message); |     console.log("External bus message: " + message); | ||||||
|     var messageObj = JSON.parse(message); |     var messageObj = JSON.parse(message); | ||||||
| @@ -33,3 +34,4 @@ window.externalApp.externalBus = function(message) { | |||||||
|         HAClient.postMessage('show-settings'); |         HAClient.postMessage('show-settings'); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  | */ | ||||||
							
								
								
									
										196
									
								
								lib/cards/badges.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								lib/cards/badges.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,196 @@ | |||||||
|  | part of '../main.dart'; | ||||||
|  |  | ||||||
|  | class Badges extends StatelessWidget { | ||||||
|  |   final BadgesData badges; | ||||||
|  |  | ||||||
|  |   const Badges({Key key, this.badges}) : super(key: key); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     List<EntityWrapper> entitiesToShow = badges.getEntitiesToShow(); | ||||||
|  |      | ||||||
|  |     if (entitiesToShow.isNotEmpty) { | ||||||
|  |       if (ConnectionManager().scrollBadges) { | ||||||
|  |         return ConstrainedBox( | ||||||
|  |           constraints: BoxConstraints.tightFor(height: 112), | ||||||
|  |           child: SingleChildScrollView( | ||||||
|  |             scrollDirection: Axis.horizontal, | ||||||
|  |             child: Row( | ||||||
|  |               mainAxisSize: MainAxisSize.min, | ||||||
|  |               mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |               children: entitiesToShow.map((entity) => | ||||||
|  |                 EntityModel( | ||||||
|  |                   entityWrapper: entity, | ||||||
|  |                   child: Padding( | ||||||
|  |                     padding: EdgeInsets.fromLTRB(5, 10, 5, 10), | ||||||
|  |                     child: BadgeWidget(), | ||||||
|  |                   ), | ||||||
|  |                   handleTap: true, | ||||||
|  |                 )).toList() | ||||||
|  |             ), | ||||||
|  |           ) | ||||||
|  |         ); | ||||||
|  |       } else { | ||||||
|  |         return Padding( | ||||||
|  |           padding: EdgeInsets.fromLTRB(5, 10, 5, 10), | ||||||
|  |           child: Wrap( | ||||||
|  |             alignment: WrapAlignment.center, | ||||||
|  |             spacing: 10.0, | ||||||
|  |             runSpacing: 5, | ||||||
|  |             children: entitiesToShow.map((entity) => | ||||||
|  |                 EntityModel( | ||||||
|  |                   entityWrapper: entity, | ||||||
|  |                   child: BadgeWidget(), | ||||||
|  |                   handleTap: true, | ||||||
|  |                 )).toList(), | ||||||
|  |           ) | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return Container(height: 0.0, width: 0.0,); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class BadgeWidget extends StatelessWidget { | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     final entityModel = EntityModel.of(context); | ||||||
|  |     Widget badgeIcon; | ||||||
|  |     String onBadgeTextValue; | ||||||
|  |     Color iconColor = HAClientTheme().getBadgeColor(entityModel.entityWrapper.entity.domain); | ||||||
|  |     switch (entityModel.entityWrapper.entity.domain) { | ||||||
|  |       case "sun": | ||||||
|  |         { | ||||||
|  |           IconData iconData; | ||||||
|  |           if (entityModel.entityWrapper.entity.state == "below_horizon") { | ||||||
|  |             iconData = MaterialDesignIcons.getIconDataFromIconCode(0xf0dc); | ||||||
|  |           } else { | ||||||
|  |             iconData = MaterialDesignIcons.getIconDataFromIconCode(0xf5a8); | ||||||
|  |           } | ||||||
|  |           badgeIcon = Padding( | ||||||
|  |             padding: EdgeInsets.all(10), | ||||||
|  |             child: Icon( | ||||||
|  |               iconData, | ||||||
|  |             ) | ||||||
|  |           ); | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |       case "camera": | ||||||
|  |       case "media_player": | ||||||
|  |       case "binary_sensor": | ||||||
|  |         { | ||||||
|  |           badgeIcon = EntityIcon( | ||||||
|  |             imagePadding: EdgeInsets.all(0.0), | ||||||
|  |             iconPadding: EdgeInsets.all(10), | ||||||
|  |             color: Theme.of(context).textTheme.body2.color | ||||||
|  |           ); | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |       case "device_tracker": | ||||||
|  |       case "person": | ||||||
|  |         { | ||||||
|  |           badgeIcon = EntityIcon( | ||||||
|  |             imagePadding: EdgeInsets.all(0.0), | ||||||
|  |             iconPadding: EdgeInsets.all(10), | ||||||
|  |             color: Theme.of(context).textTheme.body2.color | ||||||
|  |           ); | ||||||
|  |           onBadgeTextValue = entityModel.entityWrapper.entity.displayState; | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |       default: | ||||||
|  |         { | ||||||
|  |           onBadgeTextValue = entityModel.entityWrapper.unitOfMeasurement; | ||||||
|  |           badgeIcon = Padding( | ||||||
|  |             padding: EdgeInsets.all(4), | ||||||
|  |             child: Text( | ||||||
|  |               "${entityModel.entityWrapper.entity.displayState}", | ||||||
|  |               overflow: TextOverflow.fade, | ||||||
|  |               softWrap: false, | ||||||
|  |               textAlign: TextAlign.center, | ||||||
|  |               style: Theme.of(context).textTheme.body1 | ||||||
|  |             ) | ||||||
|  |           ); | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     Widget onBadgeText; | ||||||
|  |     if (onBadgeTextValue == null || onBadgeTextValue.length == 0) { | ||||||
|  |       onBadgeText = Container(width: 0.0, height: 0.0); | ||||||
|  |     } else { | ||||||
|  |       onBadgeText = Container( | ||||||
|  |         constraints: BoxConstraints(maxWidth: 50), | ||||||
|  |         padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0), | ||||||
|  |         child: Text("$onBadgeTextValue", | ||||||
|  |             style: Theme.of(context).textTheme.overline.copyWith( | ||||||
|  |               color: HAClientTheme().getOnBadgeTextColor() | ||||||
|  |             ), | ||||||
|  |             textAlign: TextAlign.center, | ||||||
|  |             softWrap: false, | ||||||
|  |             overflow: TextOverflow.ellipsis | ||||||
|  |           ), | ||||||
|  |         decoration: new BoxDecoration( | ||||||
|  |           color: iconColor, | ||||||
|  |           borderRadius: BorderRadius.circular(9.0), | ||||||
|  |         ) | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     return GestureDetector( | ||||||
|  |         child: Column( | ||||||
|  |           mainAxisSize: MainAxisSize.min, | ||||||
|  |           children: <Widget>[ | ||||||
|  |             Stack( | ||||||
|  |               overflow: Overflow.visible, | ||||||
|  |               alignment: Alignment.center, | ||||||
|  |               children: <Widget>[ | ||||||
|  |                 Container( | ||||||
|  |                   width: 45, | ||||||
|  |                   height: 45, | ||||||
|  |                   decoration: new BoxDecoration( | ||||||
|  |                     // Circle shape | ||||||
|  |                     shape: BoxShape.circle, | ||||||
|  |                     color: Theme.of(context).cardColor, | ||||||
|  |                     // The border you want | ||||||
|  |                     border: Border.all( | ||||||
|  |                       width: 2.0, | ||||||
|  |                       color: iconColor, | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |                 SizedBox( | ||||||
|  |                   width: 41, | ||||||
|  |                   height: 41, | ||||||
|  |                   child: FittedBox( | ||||||
|  |                     fit: BoxFit.contain, | ||||||
|  |                     alignment: Alignment.center, | ||||||
|  |                     child: badgeIcon, | ||||||
|  |                   ) | ||||||
|  |                 ), | ||||||
|  |                 Positioned( | ||||||
|  |                   bottom: -6, | ||||||
|  |                   child: onBadgeText | ||||||
|  |                 ) | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |             Container( | ||||||
|  |               constraints: BoxConstraints(maxWidth: 45), | ||||||
|  |               padding: EdgeInsets.only(top: 10), | ||||||
|  |               child: Text( | ||||||
|  |                 "${entityModel.entityWrapper.displayName}", | ||||||
|  |                 textAlign: TextAlign.center, | ||||||
|  |                 style: Theme.of(context).textTheme.caption.copyWith( | ||||||
|  |                   fontSize: 10 | ||||||
|  |                 ), | ||||||
|  |                 softWrap: true, | ||||||
|  |                 maxLines: 3, | ||||||
|  |                 overflow: TextOverflow.ellipsis, | ||||||
|  |               ), | ||||||
|  |             ) | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |         onTap: () => entityModel.entityWrapper.handleTap(), | ||||||
|  |         onDoubleTap: () => entityModel.entityWrapper.handleDoubleTap(), | ||||||
|  |         onLongPress: () => entityModel.entityWrapper.handleHold(), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -13,17 +13,32 @@ class CardData { | |||||||
|  |  | ||||||
|   factory CardData.parse(rawData) { |   factory CardData.parse(rawData) { | ||||||
|     try { |     try { | ||||||
|  |       if (rawData['type'] == null) { | ||||||
|  |         rawData['type'] = CardType.ENTITIES; | ||||||
|  |       } else if (!(rawData['type'] is String)) { | ||||||
|  |         return CardData(null); | ||||||
|  |       } | ||||||
|       switch (rawData['type']) { |       switch (rawData['type']) { | ||||||
|           case CardType.ENTITIES: |           case CardType.ENTITIES: | ||||||
|  |           case CardType.HISTORY_GRAPH: | ||||||
|  |           case CardType.MAP: | ||||||
|  |           case CardType.PICTURE_GLANCE: | ||||||
|  |           case CardType.SENSOR: | ||||||
|  |           case CardType.ENTITY: | ||||||
|  |           case CardType.WEATHER_FORECAST: | ||||||
|  |           case CardType.PLANT_STATUS: | ||||||
|  |             if (rawData['entity'] != null) { | ||||||
|  |               rawData['entities'] = [rawData['entity']]; | ||||||
|  |             } | ||||||
|             return EntitiesCardData(rawData); |             return EntitiesCardData(rawData); | ||||||
|             break; |             break; | ||||||
|           case CardType.ALARM_PANEL: |           case CardType.ALARM_PANEL: | ||||||
|             return AlarmPanelCardData(rawData); |             return AlarmPanelCardData(rawData); | ||||||
|             break; |             break; | ||||||
|           case CardType.BUTTON: |  | ||||||
|             return ButtonCardData(rawData); |  | ||||||
|             break; |  | ||||||
|           case CardType.ENTITY_BUTTON: |           case CardType.ENTITY_BUTTON: | ||||||
|  |           case CardType.LIGHT: | ||||||
|  |           case CardType.BUTTON: | ||||||
|  |           case CardType.PICTURE_ENTITY: | ||||||
|             return ButtonCardData(rawData); |             return ButtonCardData(rawData); | ||||||
|             break; |             break; | ||||||
|           case CardType.CONDITIONAL: |           case CardType.CONDITIONAL: | ||||||
| @@ -42,6 +57,10 @@ class CardData { | |||||||
|             return GaugeCardData(rawData); |             return GaugeCardData(rawData); | ||||||
|             break; |             break; | ||||||
|           case CardType.GLANCE: |           case CardType.GLANCE: | ||||||
|  |           case CardType.THERMOSTAT: | ||||||
|  |             if (rawData['entity'] != null) { | ||||||
|  |               rawData['entities'] = [rawData['entity']]; | ||||||
|  |             } | ||||||
|             return GlanceCardData(rawData); |             return GlanceCardData(rawData); | ||||||
|             break; |             break; | ||||||
|           case CardType.HORIZONTAL_STACK: |           case CardType.HORIZONTAL_STACK: | ||||||
| @@ -56,14 +75,11 @@ class CardData { | |||||||
|           case CardType.MEDIA_CONTROL: |           case CardType.MEDIA_CONTROL: | ||||||
|             return MediaControlCardData(rawData); |             return MediaControlCardData(rawData); | ||||||
|             break; |             break; | ||||||
|  |           case CardType.BADGES: | ||||||
|  |             return BadgesData(rawData); | ||||||
|  |             break; | ||||||
|           default: |           default: | ||||||
|             if (rawData.containsKey('entities')) { |             return CardData(null); | ||||||
|               return EntitiesCardData(rawData); |  | ||||||
|             } else if (rawData.containsKey('entity')) { |  | ||||||
|               rawData['entities'] = [rawData['entity']]; |  | ||||||
|               return EntitiesCardData(rawData); |  | ||||||
|             } |  | ||||||
|             return CardData(rawData); |  | ||||||
|         } |         } | ||||||
|     } catch (error, stacktrace) { |     } catch (error, stacktrace) { | ||||||
|       Logger.e('Error parsing card $rawData: $error', stacktrace: stacktrace); |       Logger.e('Error parsing card $rawData: $error', stacktrace: stacktrace); | ||||||
| @@ -73,7 +89,7 @@ class CardData { | |||||||
|  |  | ||||||
|   CardData(rawData) { |   CardData(rawData) { | ||||||
|     if (rawData != null && rawData is Map) { |     if (rawData != null && rawData is Map) { | ||||||
|       type = rawData['type'] ?? CardType.ENTITIES; |       type = rawData['type']; | ||||||
|       conditions = rawData['conditions'] ?? []; |       conditions = rawData['conditions'] ?? []; | ||||||
|       showEmpty = rawData['show_empty'] ?? true; |       showEmpty = rawData['show_empty'] ?? true; | ||||||
|       stateFilter = rawData['state_filter']  ?? []; |       stateFilter = rawData['state_filter']  ?? []; | ||||||
| @@ -159,6 +175,64 @@ class CardData { | |||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | class BadgesData extends CardData { | ||||||
|  |  | ||||||
|  |   String title; | ||||||
|  |   String icon; | ||||||
|  |   bool showHeaderToggle; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget buildCardWidget() { | ||||||
|  |     return Badges(badges: this); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   BadgesData(rawData) : super(rawData) { | ||||||
|  |     if (rawData['badges'] is List) { | ||||||
|  |       rawData['badges'].forEach((dynamic rawBadge) { | ||||||
|  |         if (rawBadge is String && HomeAssistant().entities.isExist(rawBadge)) {   | ||||||
|  |           entities.add(EntityWrapper(entity: HomeAssistant().entities.get(rawBadge))); | ||||||
|  |         } else if (rawBadge is Map && rawBadge.containsKey('entity') && HomeAssistant().entities.isExist(rawBadge['entity'])) { | ||||||
|  |           entities.add( | ||||||
|  |             EntityWrapper( | ||||||
|  |               entity: HomeAssistant().entities.get(rawBadge['entity']), | ||||||
|  |               overrideName: rawBadge["name"], | ||||||
|  |               overrideIcon: rawBadge["icon"], | ||||||
|  |             ) | ||||||
|  |           ); | ||||||
|  |         } else if (rawBadge is Map && rawBadge.containsKey('entities')) { | ||||||
|  |           _parseEntities(rawBadge); | ||||||
|  |         } | ||||||
|  |       });     | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void _parseEntities(rawData) { | ||||||
|  |     var rawEntities = rawData['entities'] ?? []; | ||||||
|  |     rawEntities.forEach((rawEntity) { | ||||||
|  |       if (rawEntity is String) { | ||||||
|  |         if (HomeAssistant().entities.isExist(rawEntity)) { | ||||||
|  |           entities.add(EntityWrapper( | ||||||
|  |             entity: HomeAssistant().entities.get(rawEntity), | ||||||
|  |             stateFilter: rawData['state_filter'] ?? [], | ||||||
|  |           )); | ||||||
|  |         } | ||||||
|  |       } else if (HomeAssistant().entities.isExist('${rawEntity['entity']}')) { | ||||||
|  |         Entity e = HomeAssistant().entities.get(rawEntity["entity"]); | ||||||
|  |         entities.add( | ||||||
|  |           EntityWrapper( | ||||||
|  |               entity: e, | ||||||
|  |               overrideName: rawEntity["name"], | ||||||
|  |               overrideIcon: rawEntity["icon"], | ||||||
|  |               stateFilter: rawEntity['state_filter'] ?? (rawData['state_filter'] ?? []), | ||||||
|  |               uiAction: EntityUIAction(rawEntityData: rawEntity) | ||||||
|  |           ) | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
| class EntitiesCardData extends CardData { | class EntitiesCardData extends CardData { | ||||||
|  |  | ||||||
|   String title; |   String title; | ||||||
| @@ -172,12 +246,12 @@ class EntitiesCardData extends CardData { | |||||||
|    |    | ||||||
|   EntitiesCardData(rawData) : super(rawData) { |   EntitiesCardData(rawData) : super(rawData) { | ||||||
|     //Parsing card data |     //Parsing card data | ||||||
|     title = rawData["title"]; |     title = rawData['title']; | ||||||
|     icon = rawData['icon']; |     icon = rawData['icon'] is String ? rawData['icon'] : null; | ||||||
|     stateColor = rawData['state_color'] ?? false; |     stateColor = rawData['state_color'] ?? false; | ||||||
|     showHeaderToggle = rawData['show_header_toggle'] ?? false; |     showHeaderToggle = rawData['show_header_toggle'] ?? false; | ||||||
|     //Parsing entities |     //Parsing entities | ||||||
|     var rawEntities = rawData["entities"] ?? []; |     var rawEntities = rawData['entities'] ?? []; | ||||||
|     rawEntities.forEach((rawEntity) { |     rawEntities.forEach((rawEntity) { | ||||||
|       if (rawEntity is String) { |       if (rawEntity is String) { | ||||||
|         if (HomeAssistant().entities.isExist(rawEntity)) { |         if (HomeAssistant().entities.isExist(rawEntity)) { | ||||||
| @@ -299,7 +373,7 @@ class ButtonCardData extends CardData { | |||||||
|   ButtonCardData(rawData) : super(rawData) { |   ButtonCardData(rawData) : super(rawData) { | ||||||
|     //Parsing card data |     //Parsing card data | ||||||
|     name = rawData['name']; |     name = rawData['name']; | ||||||
|     icon = rawData['icon']; |     icon = rawData['icon'] is String ? rawData['icon'] : null; | ||||||
|     showName = rawData['show_name'] ?? true; |     showName = rawData['show_name'] ?? true; | ||||||
|     showIcon = rawData['show_icon'] ?? true; |     showIcon = rawData['show_icon'] ?? true; | ||||||
|     stateColor = rawData['state_color'] ?? true; |     stateColor = rawData['state_color'] ?? true; | ||||||
| @@ -379,7 +453,7 @@ class GaugeCardData extends CardData { | |||||||
|     } |     } | ||||||
|     severity = rawData['severity']; |     severity = rawData['severity']; | ||||||
|     //Parsing entity |     //Parsing entity | ||||||
|     var entitiId = rawData["entity"]; |     var entitiId = rawData["entity"] is List ? rawData["entity"][0] : rawData["entity"]; | ||||||
|     if (entitiId != null && entitiId is String) { |     if (entitiId != null && entitiId is String) { | ||||||
|       if (HomeAssistant().entities.isExist(entitiId)) { |       if (HomeAssistant().entities.isExist(entitiId)) { | ||||||
|         entities.add(EntityWrapper( |         entities.add(EntityWrapper( | ||||||
| @@ -390,7 +464,7 @@ class GaugeCardData extends CardData { | |||||||
|         entities.add(EntityWrapper(entity: Entity.missed(entitiId))); |         entities.add(EntityWrapper(entity: Entity.missed(entitiId))); | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       entities.add(EntityWrapper(entity: Entity.missed(entitiId))); |       entities.add(EntityWrapper(entity: Entity.missed('$entitiId'))); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|   } |   } | ||||||
| @@ -460,7 +534,7 @@ class HorizontalStackCardData extends CardData { | |||||||
|   } |   } | ||||||
|    |    | ||||||
|   HorizontalStackCardData(rawData) : super(rawData) { |   HorizontalStackCardData(rawData) : super(rawData) { | ||||||
|     if (rawData.containsKey('cards')) { |     if (rawData.containsKey('cards') && rawData['cards'] is List) { | ||||||
|       childCards = rawData['cards'].map<CardData>((childCard) { |       childCards = rawData['cards'].map<CardData>((childCard) { | ||||||
|         return CardData.parse(childCard); |         return CardData.parse(childCard); | ||||||
|       }).toList(); |       }).toList(); | ||||||
| @@ -481,7 +555,7 @@ class VerticalStackCardData extends CardData { | |||||||
|   } |   } | ||||||
|    |    | ||||||
|   VerticalStackCardData(rawData) : super(rawData) { |   VerticalStackCardData(rawData) : super(rawData) { | ||||||
|     if (rawData.containsKey('cards')) { |     if (rawData.containsKey('cards') && rawData['cards'] is List) { | ||||||
|       childCards = rawData['cards'].map<CardData>((childCard) { |       childCards = rawData['cards'].map<CardData>((childCard) { | ||||||
|         return CardData.parse(childCard); |         return CardData.parse(childCard); | ||||||
|       }).toList(); |       }).toList(); | ||||||
|   | |||||||
| @@ -62,6 +62,8 @@ class EntityButtonCard extends StatelessWidget { | |||||||
|           onLongPress: () => entityWrapper.handleHold(), |           onLongPress: () => entityWrapper.handleHold(), | ||||||
|           onDoubleTap: () => entityWrapper.handleDoubleTap(), |           onDoubleTap: () => entityWrapper.handleDoubleTap(), | ||||||
|           child: Center( |           child: Center( | ||||||
|  |             child: Padding( | ||||||
|  |               padding: EdgeInsets.only(top: 5), | ||||||
|               child: Column( |               child: Column( | ||||||
|                 mainAxisSize: MainAxisSize.min, |                 mainAxisSize: MainAxisSize.min, | ||||||
|                 crossAxisAlignment: CrossAxisAlignment.stretch, |                 crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
| @@ -70,6 +72,7 @@ class EntityButtonCard extends StatelessWidget { | |||||||
|                   _buildName(context) |                   _buildName(context) | ||||||
|                 ], |                 ], | ||||||
|               ) |               ) | ||||||
|  |             ) | ||||||
|           ), |           ), | ||||||
|         ), |         ), | ||||||
|         handleTap: true |         handleTap: true | ||||||
|   | |||||||
| @@ -30,10 +30,13 @@ class GlanceCard extends StatelessWidget { | |||||||
|           start, end |           start, end | ||||||
|         ).map( |         ).map( | ||||||
|           (EntityWrapper entity){ |           (EntityWrapper entity){ | ||||||
|             return EntityModel( |             return Padding( | ||||||
|  |               padding: EdgeInsets.symmetric(vertical: Sizes.rowPadding), | ||||||
|  |               child: EntityModel( | ||||||
|                 entityWrapper: entity, |                 entityWrapper: entity, | ||||||
|                 child: _buildEntityContainer(context, entity), |                 child: _buildEntityContainer(context, entity), | ||||||
|                 handleTap: true |                 handleTap: true | ||||||
|  |               ) | ||||||
|             ); |             ); | ||||||
|           } |           } | ||||||
|         ).toList() |         ).toList() | ||||||
| @@ -50,18 +53,23 @@ class GlanceCard extends StatelessWidget { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|     return CardWrapper( |     return CardWrapper( | ||||||
|  |       child: Center( | ||||||
|  |         child: Padding( | ||||||
|  |           padding: EdgeInsets.only(bottom: Sizes.rowPadding), | ||||||
|           child: Column( |           child: Column( | ||||||
|             mainAxisSize: MainAxisSize.min, |             mainAxisSize: MainAxisSize.min, | ||||||
|             children: <Widget>[ |             children: <Widget>[ | ||||||
|           CardHeader(name: card.title), |               CardHeader( | ||||||
|           Padding( |                 name: card.title, | ||||||
|             padding: EdgeInsets.symmetric(vertical: Sizes.rowPadding), |                 emptyPadding: Sizes.rowPadding, | ||||||
|             child: Table( |               ), | ||||||
|  |               Table( | ||||||
|                 children: rows |                 children: rows | ||||||
|               ) |               ) | ||||||
|           ) |  | ||||||
|             ], |             ], | ||||||
|           ) |           ) | ||||||
|  |         ) | ||||||
|  |       ) | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -85,8 +93,7 @@ class GlanceCard extends StatelessWidget { | |||||||
|       result.add(_buildState()); |       result.add(_buildState()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return Center( |     return InkResponse( | ||||||
|       child: InkResponse( |  | ||||||
|       child: Column( |       child: Column( | ||||||
|         mainAxisSize: MainAxisSize.min, |         mainAxisSize: MainAxisSize.min, | ||||||
|         children: result, |         children: result, | ||||||
| @@ -94,7 +101,6 @@ class GlanceCard extends StatelessWidget { | |||||||
|       onTap: () => entityWrapper.handleTap(), |       onTap: () => entityWrapper.handleTap(), | ||||||
|       onLongPress: () => entityWrapper.handleHold(), |       onLongPress: () => entityWrapper.handleHold(), | ||||||
|       onDoubleTap: () => entityWrapper.handleDoubleTap(), |       onDoubleTap: () => entityWrapper.handleDoubleTap(), | ||||||
|       ), |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,11 +7,6 @@ class UnsupportedCard extends StatelessWidget { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return CardWrapper( |     return Container(); | ||||||
|       child: Padding( |  | ||||||
|         padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding), |  | ||||||
|         child: Text("'${card.type}' card is not supported yet"), |  | ||||||
|       ) |  | ||||||
|     ); |  | ||||||
|   }   |   }   | ||||||
| } | } | ||||||
| @@ -36,66 +36,6 @@ class EntityState { | |||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| class EntityUIAction { |  | ||||||
|   static const moreInfo = 'more-info'; |  | ||||||
|   static const toggle = 'toggle'; |  | ||||||
|   static const callService = 'call-service'; |  | ||||||
|   static const navigate = 'navigate'; |  | ||||||
|   static const none = 'none'; |  | ||||||
|  |  | ||||||
|   String tapAction = EntityUIAction.moreInfo; |  | ||||||
|   String tapNavigationPath; |  | ||||||
|   String tapService; |  | ||||||
|   Map<String, dynamic> tapServiceData; |  | ||||||
|   String holdAction = EntityUIAction.none; |  | ||||||
|   String holdNavigationPath; |  | ||||||
|   String holdService; |  | ||||||
|   Map<String, dynamic> holdServiceData; |  | ||||||
|   String doubleTapAction = EntityUIAction.none; |  | ||||||
|   String doubleTapNavigationPath; |  | ||||||
|   String doubleTapService; |  | ||||||
|   Map<String, dynamic> doubleTapServiceData; |  | ||||||
|  |  | ||||||
|   EntityUIAction({rawEntityData}) { |  | ||||||
|     if (rawEntityData != null) { |  | ||||||
|       if (rawEntityData["tap_action"] != null) { |  | ||||||
|         if (rawEntityData["tap_action"] is String) { |  | ||||||
|           tapAction = rawEntityData["tap_action"]; |  | ||||||
|         } else { |  | ||||||
|           tapAction = |  | ||||||
|               rawEntityData["tap_action"]["action"] ?? EntityUIAction.moreInfo; |  | ||||||
|           tapNavigationPath = rawEntityData["tap_action"]["navigation_path"]; |  | ||||||
|           tapService = rawEntityData["tap_action"]["service"]; |  | ||||||
|           tapServiceData = rawEntityData["tap_action"]["service_data"]; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       if (rawEntityData["hold_action"] != null) { |  | ||||||
|         if (rawEntityData["hold_action"] is String) { |  | ||||||
|           holdAction = rawEntityData["hold_action"]; |  | ||||||
|         } else { |  | ||||||
|           holdAction = |  | ||||||
|               rawEntityData["hold_action"]["action"] ?? EntityUIAction.none; |  | ||||||
|           holdNavigationPath = rawEntityData["hold_action"]["navigation_path"]; |  | ||||||
|           holdService = rawEntityData["hold_action"]["service"]; |  | ||||||
|           holdServiceData = rawEntityData["hold_action"]["service_data"]; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       if (rawEntityData["double_tap_action"] != null) { |  | ||||||
|         if (rawEntityData["double_tap_action"] is String) { |  | ||||||
|           doubleTapAction = rawEntityData["double_tap_action"]; |  | ||||||
|         } else { |  | ||||||
|           doubleTapAction = |  | ||||||
|               rawEntityData["double_tap_action"]["action"] ?? EntityUIAction.none; |  | ||||||
|           doubleTapNavigationPath = rawEntityData["double_tap_action"]["navigation_path"]; |  | ||||||
|           doubleTapService = rawEntityData["double_tap_action"]["service"]; |  | ||||||
|           doubleTapServiceData = rawEntityData["double_tap_action"]["service_data"]; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class CardType { | class CardType { | ||||||
|   static const HORIZONTAL_STACK = "horizontal-stack"; |   static const HORIZONTAL_STACK = "horizontal-stack"; | ||||||
|   static const VERTICAL_STACK = "vertical-stack"; |   static const VERTICAL_STACK = "vertical-stack"; | ||||||
| @@ -113,6 +53,7 @@ class CardType { | |||||||
|   static const IFRAME = "iframe"; |   static const IFRAME = "iframe"; | ||||||
|   static const GAUGE = "gauge"; |   static const GAUGE = "gauge"; | ||||||
|   static const ENTITY_BUTTON = "entity-button"; |   static const ENTITY_BUTTON = "entity-button"; | ||||||
|  |   static const ENTITY = "entity"; | ||||||
|   static const BUTTON = "button"; |   static const BUTTON = "button"; | ||||||
|   static const CONDITIONAL = "conditional"; |   static const CONDITIONAL = "conditional"; | ||||||
|   static const ALARM_PANEL = "alarm-panel"; |   static const ALARM_PANEL = "alarm-panel"; | ||||||
| @@ -120,6 +61,9 @@ class CardType { | |||||||
|   static const LIGHT = "light"; |   static const LIGHT = "light"; | ||||||
|   static const ENTITY_FILTER = "entity-filter"; |   static const ENTITY_FILTER = "entity-filter"; | ||||||
|   static const UNKNOWN = "unknown"; |   static const UNKNOWN = "unknown"; | ||||||
|  |   static const HISTORY_GRAPH = "history-graph"; | ||||||
|  |   static const PICTURE_GLANCE = "picture-glance"; | ||||||
|  |   static const BADGES = "badges"; | ||||||
| } | } | ||||||
|  |  | ||||||
| class Sizes { | class Sizes { | ||||||
|   | |||||||
| @@ -1,148 +0,0 @@ | |||||||
| part of '../main.dart'; |  | ||||||
|  |  | ||||||
| class BadgeWidget extends StatelessWidget { |  | ||||||
|   @override |  | ||||||
|   Widget build(BuildContext context) { |  | ||||||
|     final entityModel = EntityModel.of(context); |  | ||||||
|     double iconSize = 26.0; |  | ||||||
|     Widget badgeIcon; |  | ||||||
|     String onBadgeTextValue; |  | ||||||
|     Color iconColor = HAClientTheme().getBadgeColor(entityModel.entityWrapper.entity.domain); |  | ||||||
|     switch (entityModel.entityWrapper.entity.domain) { |  | ||||||
|       case "sun": |  | ||||||
|         { |  | ||||||
|           badgeIcon = entityModel.entityWrapper.entity.state == "below_horizon" |  | ||||||
|               ? Icon( |  | ||||||
|             MaterialDesignIcons.getIconDataFromIconCode(0xf0dc), |  | ||||||
|             size: iconSize, |  | ||||||
|           ) |  | ||||||
|               : Icon( |  | ||||||
|             MaterialDesignIcons.getIconDataFromIconCode(0xf5a8), |  | ||||||
|             size: iconSize, |  | ||||||
|           ); |  | ||||||
|           break; |  | ||||||
|         } |  | ||||||
|       case "camera": |  | ||||||
|       case "media_player": |  | ||||||
|       case "binary_sensor": |  | ||||||
|         { |  | ||||||
|           badgeIcon = EntityIcon( |  | ||||||
|             padding: EdgeInsets.all(0.0), |  | ||||||
|             size: iconSize, |  | ||||||
|             color: Theme.of(context).textTheme.body1.color |  | ||||||
|           ); |  | ||||||
|           break; |  | ||||||
|         } |  | ||||||
|       case "device_tracker": |  | ||||||
|       case "person": |  | ||||||
|         { |  | ||||||
|           badgeIcon = EntityIcon( |  | ||||||
|               padding: EdgeInsets.all(0.0), |  | ||||||
|               size: iconSize, |  | ||||||
|               color: Theme.of(context).textTheme.body1.color |  | ||||||
|           ); |  | ||||||
|           onBadgeTextValue = entityModel.entityWrapper.entity.displayState; |  | ||||||
|           break; |  | ||||||
|         } |  | ||||||
|       default: |  | ||||||
|         { |  | ||||||
|           double stateFontSize; |  | ||||||
|           if (entityModel.entityWrapper.entity.displayState.length <= 3) { |  | ||||||
|             stateFontSize = 18.0; |  | ||||||
|           } else if (entityModel.entityWrapper.entity.displayState.length <= 4) { |  | ||||||
|             stateFontSize = 15.0; |  | ||||||
|           } else if (entityModel.entityWrapper.entity.displayState.length <= 6) { |  | ||||||
|             stateFontSize = 10.0; |  | ||||||
|           } else if (entityModel.entityWrapper.entity.displayState.length <= 10) { |  | ||||||
|             stateFontSize = 8.0; |  | ||||||
|           } |  | ||||||
|           onBadgeTextValue = entityModel.entityWrapper.unitOfMeasurement; |  | ||||||
|           badgeIcon = Center( |  | ||||||
|             child: Text( |  | ||||||
|               "${entityModel.entityWrapper.entity.displayState}", |  | ||||||
|               overflow: TextOverflow.fade, |  | ||||||
|               softWrap: false, |  | ||||||
|               textAlign: TextAlign.center, |  | ||||||
|               style: Theme.of(context).textTheme.body1.copyWith( |  | ||||||
|                 fontSize: stateFontSize |  | ||||||
|               ) |  | ||||||
|             ), |  | ||||||
|           ); |  | ||||||
|           break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     Widget onBadgeText; |  | ||||||
|     if (onBadgeTextValue == null || onBadgeTextValue.length == 0) { |  | ||||||
|       onBadgeText = Container(width: 0.0, height: 0.0); |  | ||||||
|     } else { |  | ||||||
|       onBadgeText = Container( |  | ||||||
|           padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0), |  | ||||||
|           child: Text("$onBadgeTextValue", |  | ||||||
|               style: Theme.of(context).textTheme.overline.copyWith( |  | ||||||
|                 color: HAClientTheme().getOnBadgeTextColor() |  | ||||||
|               ), |  | ||||||
|               textAlign: TextAlign.center, |  | ||||||
|               softWrap: false, |  | ||||||
|               overflow: TextOverflow.fade), |  | ||||||
|           decoration: new BoxDecoration( |  | ||||||
|             // Circle shape |  | ||||||
|             //shape: BoxShape.circle, |  | ||||||
|             color: iconColor, |  | ||||||
|             borderRadius: BorderRadius.circular(9.0), |  | ||||||
|           )); |  | ||||||
|     } |  | ||||||
|     return GestureDetector( |  | ||||||
|         child: Column( |  | ||||||
|           children: <Widget>[ |  | ||||||
|             Container( |  | ||||||
|               margin: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0), |  | ||||||
|               width: 50.0, |  | ||||||
|               height: 50.0, |  | ||||||
|               decoration: new BoxDecoration( |  | ||||||
|                 // Circle shape |  | ||||||
|                 shape: BoxShape.circle, |  | ||||||
|                 color: Theme.of(context).cardColor, |  | ||||||
|                 // The border you want |  | ||||||
|                 border: new Border.all( |  | ||||||
|                   width: 2.0, |  | ||||||
|                   color: iconColor, |  | ||||||
|                 ), |  | ||||||
|               ), |  | ||||||
|               child: Stack( |  | ||||||
|                 overflow: Overflow.visible, |  | ||||||
|                 children: <Widget>[ |  | ||||||
|                   Positioned( |  | ||||||
|                     width: 46.0, |  | ||||||
|                     height: 46.0, |  | ||||||
|                     top: 0.0, |  | ||||||
|                     left: 0.0, |  | ||||||
|                     child: badgeIcon, |  | ||||||
|                   ), |  | ||||||
|                   Positioned( |  | ||||||
|                     //width: 50.0, |  | ||||||
|                       bottom: -9.0, |  | ||||||
|                       left: -10.0, |  | ||||||
|                       right: -10.0, |  | ||||||
|                       child: Center( |  | ||||||
|                         child: onBadgeText, |  | ||||||
|                       )) |  | ||||||
|                 ], |  | ||||||
|               ), |  | ||||||
|             ), |  | ||||||
|             Container( |  | ||||||
|               width: 60.0, |  | ||||||
|               child: Text( |  | ||||||
|                 "${entityModel.entityWrapper.displayName}", |  | ||||||
|                 textAlign: TextAlign.center, |  | ||||||
|                 style: Theme.of(context).textTheme.caption, |  | ||||||
|                 softWrap: true, |  | ||||||
|                 maxLines: 3, |  | ||||||
|                 overflow: TextOverflow.ellipsis, |  | ||||||
|               ), |  | ||||||
|             ), |  | ||||||
|           ], |  | ||||||
|         ), |  | ||||||
|         onTap: () => |  | ||||||
|             eventBus.fire(new ShowEntityPageEvent(entityId: entityModel.entityWrapper.entity.entityId))); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -11,7 +11,7 @@ class ModeSelectorWidget extends StatelessWidget { | |||||||
|   ModeSelectorWidget({ |   ModeSelectorWidget({ | ||||||
|     Key key, |     Key key, | ||||||
|     @required this.caption, |     @required this.caption, | ||||||
|     @required this.options, |     this.options: const [], | ||||||
|     this.value, |     this.value, | ||||||
|     @required this.onChange, |     @required this.onChange, | ||||||
|     this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0), |     this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0), | ||||||
|   | |||||||
| @@ -79,7 +79,8 @@ class Entity { | |||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   String get displayName => |   String get displayName => | ||||||
|       attributes["friendly_name"] ?? (attributes["name"] ?? entityId.split(".")[1].replaceAll("_", " ")); |       attributes["friendly_name"] ?? | ||||||
|  |       (attributes["name"] ?? (entityId != null && entityId.contains('.')) ? entityId.split(".")[1].replaceAll("_", " ") : ""); | ||||||
|  |  | ||||||
|   bool get isView => |   bool get isView => | ||||||
|       (domain == "group") && |       (domain == "group") && | ||||||
| @@ -209,14 +210,6 @@ class Entity { | |||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Widget buildBadgeWidget(BuildContext context) { |  | ||||||
|     return EntityModel( |  | ||||||
|       entityWrapper: EntityWrapper(entity: this), |  | ||||||
|       child: BadgeWidget(), |  | ||||||
|       handleTap: true, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   String getAttribute(String attributeName) { |   String getAttribute(String attributeName) { | ||||||
|     if (attributes != null) { |     if (attributes != null) { | ||||||
|       return attributes["$attributeName"].toString(); |       return attributes["$attributeName"].toString(); | ||||||
|   | |||||||
| @@ -3,10 +3,12 @@ part of '../main.dart'; | |||||||
| class EntityIcon extends StatelessWidget { | class EntityIcon extends StatelessWidget { | ||||||
|  |  | ||||||
|   final EdgeInsetsGeometry padding; |   final EdgeInsetsGeometry padding; | ||||||
|  |   final EdgeInsetsGeometry iconPadding; | ||||||
|  |   final EdgeInsetsGeometry imagePadding; | ||||||
|   final double size; |   final double size; | ||||||
|   final Color color; |   final Color color; | ||||||
|  |  | ||||||
|   const EntityIcon({Key key, this.color, this.size: Sizes.iconSize, this.padding: const EdgeInsets.all(0.0)}) : super(key: key); |   const EntityIcon({Key key, this.color, this.size: Sizes.iconSize, this.padding: const EdgeInsets.all(0.0), this.iconPadding, this.imagePadding}) : super(key: key); | ||||||
|  |  | ||||||
|   int getDefaultIconByEntityId(String entityId, String deviceClass, String state) { |   int getDefaultIconByEntityId(String entityId, String deviceClass, String state) { | ||||||
|     if (entityId == null) { |     if (entityId == null) { | ||||||
| @@ -26,13 +28,27 @@ class EntityIcon extends StatelessWidget { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Widget buildIcon(BuildContext context, EntityWrapper data, Color color) { |   @override | ||||||
|     Widget result; |   Widget build(BuildContext context) { | ||||||
|     if (data == null) { |     final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper; | ||||||
|       return null; |     Color iconColor; | ||||||
|  |     if (color != null) { | ||||||
|  |       iconColor = color; | ||||||
|  |     } else if (entityWrapper.stateColor) { | ||||||
|  |       iconColor = HAClientTheme().getColorByEntityState(entityWrapper.entity.state, context); | ||||||
|  |     } else { | ||||||
|  |       iconColor = HAClientTheme().getOffStateColor(context); | ||||||
|     } |     } | ||||||
|     if (data.entityPicture != null) { |     Widget iconWidget; | ||||||
|       result = Container( |     bool isPicture = false; | ||||||
|  |     if (entityWrapper == null) { | ||||||
|  |       iconWidget = Container( | ||||||
|  |         width: size, | ||||||
|  |         height: size, | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       if (entityWrapper.entityPicture != null) { | ||||||
|  |         iconWidget = Container( | ||||||
|           height: size+12, |           height: size+12, | ||||||
|           width: size+12, |           width: size+12, | ||||||
|           decoration: BoxDecoration( |           decoration: BoxDecoration( | ||||||
| @@ -40,36 +56,34 @@ class EntityIcon extends StatelessWidget { | |||||||
|               image: DecorationImage( |               image: DecorationImage( | ||||||
|                 fit:BoxFit.cover, |                 fit:BoxFit.cover, | ||||||
|                 image: CachedNetworkImageProvider( |                 image: CachedNetworkImageProvider( | ||||||
|                 "${data.entityPicture}" |                   "${entityWrapper.entityPicture}" | ||||||
|                 ), |                 ), | ||||||
|               ) |               ) | ||||||
|           ), |           ), | ||||||
|         ); |         ); | ||||||
|  |         isPicture = true; | ||||||
|       } else { |       } else { | ||||||
|       String iconName = data.icon; |         String iconName = entityWrapper.icon; | ||||||
|         int iconCode = 0; |         int iconCode = 0; | ||||||
|         if (iconName.length > 0) { |         if (iconName.length > 0) { | ||||||
|           iconCode = MaterialDesignIcons.getIconCodeByIconName(iconName); |           iconCode = MaterialDesignIcons.getIconCodeByIconName(iconName); | ||||||
|         } else { |         } else { | ||||||
|         iconCode = getDefaultIconByEntityId(data.entity.entityId, |           iconCode = getDefaultIconByEntityId(entityWrapper.entity.entityId, | ||||||
|             data.entity.deviceClass, data.entity.state); // |               entityWrapper.entity.deviceClass, entityWrapper.entity.state); // | ||||||
|         } |         } | ||||||
|       result = Icon( |         if (entityWrapper.entity is LightEntity && | ||||||
|  |           (entityWrapper.entity as LightEntity).supportColor && | ||||||
|  |           (entityWrapper.entity as LightEntity).color != null && | ||||||
|  |           (entityWrapper.entity as LightEntity).color.toColor() != Colors.white | ||||||
|  |           ) { | ||||||
|  |           Color lightColor = (entityWrapper.entity as LightEntity).color.toColor();   | ||||||
|  |           iconWidget = Stack( | ||||||
|  |             children: <Widget>[ | ||||||
|  |               Icon( | ||||||
|                 IconData(iconCode, fontFamily: 'Material Design Icons'), |                 IconData(iconCode, fontFamily: 'Material Design Icons'), | ||||||
|                 size: size, |                 size: size, | ||||||
|         color: color, |                 color: iconColor, | ||||||
|       ); |               ), | ||||||
|       if (data.entity is LightEntity && |  | ||||||
|         (data.entity as LightEntity).supportColor && |  | ||||||
|         (data.entity as LightEntity).color != null |  | ||||||
|         ) { |  | ||||||
|         Color lightColor = (data.entity as LightEntity).color.toColor(); |  | ||||||
|         if (lightColor == Colors.white) { |  | ||||||
|           return result; |  | ||||||
|         }   |  | ||||||
|         result = Stack( |  | ||||||
|           children: <Widget>[ |  | ||||||
|             result, |  | ||||||
|               Positioned( |               Positioned( | ||||||
|                 bottom: 0, |                 bottom: 0, | ||||||
|                 right: 0, |                 right: 0, | ||||||
| @@ -91,29 +105,26 @@ class EntityIcon extends StatelessWidget { | |||||||
|               ) |               ) | ||||||
|             ], |             ], | ||||||
|           ); |           ); | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     return result; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Widget build(BuildContext context) { |  | ||||||
|     final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper; |  | ||||||
|     Color iconColor; |  | ||||||
|     if (color != null) { |  | ||||||
|       iconColor = color; |  | ||||||
|     } else if (entityWrapper.stateColor) { |  | ||||||
|       iconColor = HAClientTheme().getColorByEntityState(entityWrapper.entity.state, context); |  | ||||||
|         } else { |         } else { | ||||||
|       iconColor = HAClientTheme().getOffStateColor(context); |           iconWidget = Icon( | ||||||
|     } |             IconData(iconCode, fontFamily: 'Material Design Icons'), | ||||||
|     return Padding( |             size: size, | ||||||
|       padding: padding, |             color: iconColor, | ||||||
|       child: buildIcon( |           ); | ||||||
|         context, |         } | ||||||
|         entityWrapper, |       } | ||||||
|         iconColor  |     } | ||||||
|       ), |     EdgeInsetsGeometry computedPadding; | ||||||
|  |     if (isPicture && imagePadding != null) { | ||||||
|  |       computedPadding = imagePadding; | ||||||
|  |     } else if (!isPicture && iconPadding != null) { | ||||||
|  |       computedPadding = iconPadding; | ||||||
|  |     } else { | ||||||
|  |       computedPadding = padding; | ||||||
|  |     } | ||||||
|  |     return Padding( | ||||||
|  |       padding: computedPadding, | ||||||
|  |       child: iconWidget, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -155,3 +155,63 @@ class EntityWrapper { | |||||||
|   } |   } | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | class EntityUIAction { | ||||||
|  |   static const moreInfo = 'more-info'; | ||||||
|  |   static const toggle = 'toggle'; | ||||||
|  |   static const callService = 'call-service'; | ||||||
|  |   static const navigate = 'navigate'; | ||||||
|  |   static const none = 'none'; | ||||||
|  |  | ||||||
|  |   String tapAction = EntityUIAction.moreInfo; | ||||||
|  |   String tapNavigationPath; | ||||||
|  |   String tapService; | ||||||
|  |   Map<String, dynamic> tapServiceData; | ||||||
|  |   String holdAction = EntityUIAction.moreInfo; | ||||||
|  |   String holdNavigationPath; | ||||||
|  |   String holdService; | ||||||
|  |   Map<String, dynamic> holdServiceData; | ||||||
|  |   String doubleTapAction = EntityUIAction.none; | ||||||
|  |   String doubleTapNavigationPath; | ||||||
|  |   String doubleTapService; | ||||||
|  |   Map<String, dynamic> doubleTapServiceData; | ||||||
|  |  | ||||||
|  |   EntityUIAction({rawEntityData}) { | ||||||
|  |     if (rawEntityData != null) { | ||||||
|  |       if (rawEntityData["tap_action"] != null) { | ||||||
|  |         if (rawEntityData["tap_action"] is String) { | ||||||
|  |           tapAction = rawEntityData["tap_action"]; | ||||||
|  |         } else { | ||||||
|  |           tapAction = | ||||||
|  |               rawEntityData["tap_action"]["action"] ?? EntityUIAction.moreInfo; | ||||||
|  |           tapNavigationPath = rawEntityData["tap_action"]["navigation_path"]; | ||||||
|  |           tapService = rawEntityData["tap_action"]["service"]; | ||||||
|  |           tapServiceData = rawEntityData["tap_action"]["service_data"]; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if (rawEntityData["hold_action"] != null) { | ||||||
|  |         if (rawEntityData["hold_action"] is String) { | ||||||
|  |           holdAction = rawEntityData["hold_action"]; | ||||||
|  |         } else { | ||||||
|  |           holdAction = | ||||||
|  |               rawEntityData["hold_action"]["action"] ?? EntityUIAction.none; | ||||||
|  |           holdNavigationPath = rawEntityData["hold_action"]["navigation_path"]; | ||||||
|  |           holdService = rawEntityData["hold_action"]["service"]; | ||||||
|  |           holdServiceData = rawEntityData["hold_action"]["service_data"]; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if (rawEntityData["double_tap_action"] != null) { | ||||||
|  |         if (rawEntityData["double_tap_action"] is String) { | ||||||
|  |           doubleTapAction = rawEntityData["double_tap_action"]; | ||||||
|  |         } else { | ||||||
|  |           doubleTapAction = | ||||||
|  |               rawEntityData["double_tap_action"]["action"] ?? EntityUIAction.none; | ||||||
|  |           doubleTapNavigationPath = rawEntityData["double_tap_action"]["navigation_path"]; | ||||||
|  |           doubleTapService = rawEntityData["double_tap_action"]["service"]; | ||||||
|  |           doubleTapServiceData = rawEntityData["double_tap_action"]["service_data"]; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -26,11 +26,11 @@ class TimerEntity extends Entity { | |||||||
|               seconds: int.tryParse(durationList[2]) ?? 0 |               seconds: int.tryParse(durationList[2]) ?? 0 | ||||||
|           ); |           ); | ||||||
|         } else { |         } else { | ||||||
|           Logger.e("Strange $entityId duration format: $durationSource"); |           Logger.e("Strange timer duration format: $durationSource"); | ||||||
|           duration = Duration(seconds: 0); |           duration = Duration(seconds: 0); | ||||||
|         } |         } | ||||||
|       } catch (e, stacktrace) { |       } catch (e, stacktrace) { | ||||||
|         Logger.e("Error parsing duration for $entityId: $e", stacktrace: stacktrace); |         Logger.e("Error parsing timer duration \'$durationSource\': $e", stacktrace: stacktrace); | ||||||
|         duration = Duration(seconds: 0); |         duration = Duration(seconds: 0); | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|   | |||||||
| @@ -140,9 +140,9 @@ class HomeAssistant { | |||||||
|  |  | ||||||
|   Future _getConfig(SharedPreferences sharedPrefs) async { |   Future _getConfig(SharedPreferences sharedPrefs) async { | ||||||
|     _instanceConfig?.clear(); |     _instanceConfig?.clear(); | ||||||
|     if (sharedPrefs != null) { |     if (sharedPrefs != null && sharedPrefs.containsKey('cached_config')) { | ||||||
|       try { |       try { | ||||||
|         var data = json.decode(sharedPrefs.getString('cached_config') ?? '{}'); |         var data = json.decode(sharedPrefs.getString('cached_config')); | ||||||
|         _parseConfig(data); |         _parseConfig(data); | ||||||
|       } catch (e, stacktrace) { |       } catch (e, stacktrace) { | ||||||
|        Logger.e('Error gettong config from cache: $e', stacktrace: stacktrace); |        Logger.e('Error gettong config from cache: $e', stacktrace: stacktrace); | ||||||
| @@ -160,10 +160,10 @@ class HomeAssistant { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future _getStates(SharedPreferences sharedPrefs) async { |   Future _getStates(SharedPreferences sharedPrefs) async { | ||||||
|     if (sharedPrefs != null) { |     if (sharedPrefs != null && sharedPrefs.containsKey('cached_states')) { | ||||||
|       try { |       try { | ||||||
|         var data = json.decode(sharedPrefs.getString('cached_states') ?? '[]'); |         var data = json.decode(sharedPrefs.getString('cached_states')); | ||||||
|         _parseStates(data); |         _parseStates(data ?? []); | ||||||
|       } catch (e, stacktrace) { |       } catch (e, stacktrace) { | ||||||
|         Logger.e('Error getting cached states: $e', stacktrace: stacktrace); |         Logger.e('Error getting cached states: $e', stacktrace: stacktrace); | ||||||
|       } |       } | ||||||
| @@ -183,9 +183,9 @@ class HomeAssistant { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future _getLovelace(SharedPreferences sharedPrefs) { |   Future _getLovelace(SharedPreferences sharedPrefs) { | ||||||
|     if (sharedPrefs != null) { |     if (sharedPrefs != null && sharedPrefs.containsKey('cached_lovelace')) { | ||||||
|       try { |       try { | ||||||
|         var data = json.decode(sharedPrefs.getString('cached_lovelace') ?? '{}'); |         var data = json.decode(sharedPrefs.getString('cached_lovelace')); | ||||||
|         _rawLovelaceData = data; |         _rawLovelaceData = data; | ||||||
|       } catch (e) { |       } catch (e) { | ||||||
|         autoUi = true; |         autoUi = true; | ||||||
| @@ -194,7 +194,7 @@ class HomeAssistant { | |||||||
|     } else { |     } else { | ||||||
|       Completer completer = Completer(); |       Completer completer = Completer(); | ||||||
|       var additionalData; |       var additionalData; | ||||||
|       if (_lovelaceDashbordUrl != HomeAssistant.DEFAULT_DASHBOARD) { |       if (ConnectionManager().haVersion >= 107 && _lovelaceDashbordUrl != HomeAssistant.DEFAULT_DASHBOARD) { | ||||||
|         additionalData = { |         additionalData = { | ||||||
|           'url_path': _lovelaceDashbordUrl |           'url_path': _lovelaceDashbordUrl | ||||||
|         }; |         }; | ||||||
| @@ -221,10 +221,10 @@ class HomeAssistant { | |||||||
|  |  | ||||||
|   Future _getServices(SharedPreferences prefs) async { |   Future _getServices(SharedPreferences prefs) async { | ||||||
|     services?.clear();	 |     services?.clear();	 | ||||||
|     if (prefs != null) { |     if (prefs != null && prefs.containsKey('cached_services')) { | ||||||
|       try { |       try { | ||||||
|         var data = json.decode(prefs.getString('cached_services') ?? '{}'); |         var data = json.decode(prefs.getString('cached_services')); | ||||||
|         _parseServices(data); |         _parseServices(data ?? {}); | ||||||
|       } catch (e, stacktrace) { |       } catch (e, stacktrace) { | ||||||
|        Logger.e(e, stacktrace: stacktrace);   |        Logger.e(e, stacktrace: stacktrace);   | ||||||
|       } |       } | ||||||
| @@ -240,10 +240,20 @@ class HomeAssistant { | |||||||
|  |  | ||||||
|   Future _getUserInfo(SharedPreferences sharedPrefs) async { |   Future _getUserInfo(SharedPreferences sharedPrefs) async { | ||||||
|     _userName = null; |     _userName = null; | ||||||
|  |     if (sharedPrefs != null && sharedPrefs.containsKey('cached_user')) { | ||||||
|  |       try { | ||||||
|  |         var data = json.decode(sharedPrefs.getString('cached_user')); | ||||||
|  |         _parseUserInfo(data ?? {}); | ||||||
|  |       } catch (e, stacktrace) { | ||||||
|  |         Logger.e('Error getting cached user info: $e', stacktrace: stacktrace); | ||||||
|  |       } | ||||||
|  |       return Future.value(); | ||||||
|  |     } else { | ||||||
|       await ConnectionManager().sendSocketMessage(type: "auth/current_user").then((data) => _parseUserInfo(data)).catchError((e) { |       await ConnectionManager().sendSocketMessage(type: "auth/current_user").then((data) => _parseUserInfo(data)).catchError((e) { | ||||||
|         Logger.e('auth/current_user error: $e'); |         Logger.e('auth/current_user error: $e'); | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   void _parseUserInfo(data) { |   void _parseUserInfo(data) { | ||||||
|     _rawUserInfo = data; |     _rawUserInfo = data; | ||||||
| @@ -251,10 +261,10 @@ class HomeAssistant { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future _getPanels(SharedPreferences sharedPrefs) async { |   Future _getPanels(SharedPreferences sharedPrefs) async { | ||||||
|     if (sharedPrefs != null) { |     if (sharedPrefs != null && sharedPrefs.containsKey('cached_panels')) { | ||||||
|       try { |       try { | ||||||
|         var data = json.decode(sharedPrefs.getString('cached_panels') ?? '{}'); |         var data = json.decode(sharedPrefs.getString('cached_panels')); | ||||||
|         _parsePanels(data); |         _parsePanels(data ?? {}); | ||||||
|       } catch (e, stacktrace) { |       } catch (e, stacktrace) { | ||||||
|         Logger.e(e, stacktrace: stacktrace); |         Logger.e(e, stacktrace: stacktrace); | ||||||
|         panels.clear(); |         panels.clear(); | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| import 'dart:convert'; | import 'dart:convert'; | ||||||
| import 'dart:async'; | import 'dart:async'; | ||||||
|  | import 'dart:isolate'; | ||||||
|  | import 'dart:ui'; | ||||||
| import 'dart:math' as math; | import 'dart:math' as math; | ||||||
| import 'package:flutter/foundation.dart'; | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:flutter/rendering.dart'; | import 'package:flutter/rendering.dart'; | ||||||
| @@ -23,8 +25,11 @@ 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'; | ||||||
| import 'package:workmanager/workmanager.dart' as workManager; | //import 'package:workmanager/workmanager.dart' as workManager; | ||||||
| import 'package:geolocator/geolocator.dart'; | //import 'package:geolocator/geolocator.dart'; | ||||||
|  | import 'package:background_locator/background_locator.dart'; | ||||||
|  | import 'package:background_locator/location_dto.dart'; | ||||||
|  | import 'package:background_locator/location_settings.dart'; | ||||||
| import 'package:battery/battery.dart'; | import 'package:battery/battery.dart'; | ||||||
| import 'package:firebase_crashlytics/firebase_crashlytics.dart'; | import 'package:firebase_crashlytics/firebase_crashlytics.dart'; | ||||||
| import 'package:flutter_webview_plugin/flutter_webview_plugin.dart' as standaloneWebview; | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart' as standaloneWebview; | ||||||
| @@ -58,7 +63,6 @@ part 'entities/fan/fan_entity.class.dart'; | |||||||
| part 'entities/automation/automation_entity.class.dart'; | part 'entities/automation/automation_entity.class.dart'; | ||||||
| part 'entities/camera/camera_entity.class.dart'; | part 'entities/camera/camera_entity.class.dart'; | ||||||
| part 'entities/alarm_control_panel/alarm_control_panel_entity.class.dart'; | part 'entities/alarm_control_panel/alarm_control_panel_entity.class.dart'; | ||||||
| part 'entities/badge.widget.dart'; |  | ||||||
| part 'entities/entity_model.widget.dart'; | part 'entities/entity_model.widget.dart'; | ||||||
| part 'entities/default_entity_container.widget.dart'; | part 'entities/default_entity_container.widget.dart'; | ||||||
| part 'entities/missed_entity.widget.dart'; | part 'entities/missed_entity.widget.dart'; | ||||||
| @@ -153,14 +157,16 @@ part 'entities/media_player/widgets/media_player_progress_bar.widget.dart'; | |||||||
| part 'pages/whats_new.page.dart'; | part 'pages/whats_new.page.dart'; | ||||||
| part 'pages/fullscreen.page.dart'; | part 'pages/fullscreen.page.dart'; | ||||||
| part 'popups.dart'; | part 'popups.dart'; | ||||||
|  | part 'cards/badges.dart'; | ||||||
|  |  | ||||||
| EventBus eventBus = new EventBus(); | EventBus eventBus = new EventBus(); | ||||||
| final FirebaseMessaging _firebaseMessaging = FirebaseMessaging(); | final FirebaseMessaging _firebaseMessaging = FirebaseMessaging(); | ||||||
| FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin(); | FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin(); | ||||||
| const String appName = "HA Client"; | const String appName = 'HA Client'; | ||||||
| const appVersionNumber = "1.0.0"; | const appVersionNumber = '1.0.1'; | ||||||
| const appVersionAdd = ""; | final String appVersionAdd = secrets['version_type'] ?? ''; | ||||||
| const appVersion = "$appVersionNumber$appVersionAdd"; | final String appVersion = '$appVersionNumber${appVersionAdd.isNotEmpty ? '-' : ''}$appVersionAdd'; | ||||||
|  | const whatsNewUrl = 'http://ha-client.app/service/whats_new_1.0.1.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. | ||||||
| @@ -213,8 +219,11 @@ class _HAClientAppState extends State<HAClientApp> { | |||||||
|   StreamSubscription _themeChangeSubscription; |   StreamSubscription _themeChangeSubscription; | ||||||
|   AppTheme _currentTheme = AppTheme.defaultTheme; |   AppTheme _currentTheme = AppTheme.defaultTheme; | ||||||
|  |  | ||||||
|  |   ReceivePort port = ReceivePort(); | ||||||
|  |    | ||||||
|   @override |   @override | ||||||
|   void initState() { |   void initState() { | ||||||
|  |  | ||||||
|     InAppPurchaseConnection.enablePendingPurchases(); |     InAppPurchaseConnection.enablePendingPurchases(); | ||||||
|     final Stream purchaseUpdates = |     final Stream purchaseUpdates = | ||||||
|         InAppPurchaseConnection.instance.purchaseUpdatedStream; |         InAppPurchaseConnection.instance.purchaseUpdatedStream; | ||||||
| @@ -227,11 +236,18 @@ class _HAClientAppState extends State<HAClientApp> { | |||||||
|         _currentTheme = event.theme; |         _currentTheme = event.theme; | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|  |     /* | ||||||
|     workManager.Workmanager.initialize( |     workManager.Workmanager.initialize( | ||||||
|       updateDeviceLocationIsolate, |       updateDeviceLocationIsolate, | ||||||
|       isInDebugMode: false |       isInDebugMode: false | ||||||
|     ); |     ); | ||||||
|  |     */ | ||||||
|     super.initState(); |     super.initState(); | ||||||
|  |     IsolateNameServer.registerPortWithName(port.sendPort, LocationManager.isolateName); | ||||||
|  |     port.listen((dynamic data) { | ||||||
|  |       // do something with data | ||||||
|  |     }); | ||||||
|  |     initPlatformState(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void _handlePurchaseUpdates(purchase) { |   void _handlePurchaseUpdates(purchase) { | ||||||
| @@ -252,6 +268,10 @@ class _HAClientAppState extends State<HAClientApp> { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   Future<void> initPlatformState() async { | ||||||
|  |     await BackgroundLocator.initialize(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // This widget is the root of your application. |   // This widget is the root of your application. | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|   | |||||||
| @@ -19,6 +19,8 @@ class ConnectionManager { | |||||||
|   String _tempToken; |   String _tempToken; | ||||||
|   String oauthUrl; |   String oauthUrl; | ||||||
|   String webhookId; |   String webhookId; | ||||||
|  |   double haVersion; | ||||||
|  |   bool scrollBadges; | ||||||
|   String mobileAppDeviceName; |   String mobileAppDeviceName; | ||||||
|   bool settingsLoaded = false; |   bool settingsLoaded = false; | ||||||
|   int appIntegrationVersion; |   int appIntegrationVersion; | ||||||
| @@ -47,6 +49,7 @@ class ConnectionManager { | |||||||
|       webhookId = prefs.getString('app-webhook-id'); |       webhookId = prefs.getString('app-webhook-id'); | ||||||
|       appIntegrationVersion = prefs.getInt('app-integration-version') ?? 0; |       appIntegrationVersion = prefs.getInt('app-integration-version') ?? 0; | ||||||
|       mobileAppDeviceName = prefs.getString('app-integration-device-name'); |       mobileAppDeviceName = prefs.getString('app-integration-device-name'); | ||||||
|  |       scrollBadges = prefs.getBool('scroll-badges') ?? true; | ||||||
|       displayHostname = "$_domain:$_port"; |       displayHostname = "$_domain:$_port"; | ||||||
|       _webSocketAPIEndpoint = |       _webSocketAPIEndpoint = | ||||||
|       "${prefs.getString('hassio-protocol')}://$_domain:$_port/api/websocket"; |       "${prefs.getString('hassio-protocol')}://$_domain:$_port/api/websocket"; | ||||||
| @@ -154,7 +157,12 @@ class ConnectionManager { | |||||||
|                     if (!connecting.isCompleted) connecting.completeError(e); |                     if (!connecting.isCompleted) connecting.completeError(e); | ||||||
|                   }); |                   }); | ||||||
|                 } else if (data["type"] == "auth_ok") { |                 } else if (data["type"] == "auth_ok") { | ||||||
|                   Logger.d("[Received] <== ${data.toString()}"); |                   String v = data["ha_version"]; | ||||||
|  |                   if (v != null && v.isNotEmpty) { | ||||||
|  |                     haVersion = double.tryParse(v.replaceFirst('0.','')) ?? 0; | ||||||
|  |                   } | ||||||
|  |                   Logger.d("Home assistant version: $v ($haVersion)"); | ||||||
|  |                   Crashlytics.instance.setString('ha_version', v); | ||||||
|                   Logger.d("[Connection] Subscribing to events"); |                   Logger.d("[Connection] Subscribing to events"); | ||||||
|                   sendSocketMessage( |                   sendSocketMessage( | ||||||
|                     type: "subscribe_events", |                     type: "subscribe_events", | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ class LocationManager { | |||||||
|         defaultUpdateIntervalMinutes); |         defaultUpdateIntervalMinutes); | ||||||
|     _isRunning = prefs.getBool("location-enabled") ?? false; |     _isRunning = prefs.getBool("location-enabled") ?? false; | ||||||
|     if (_isRunning) { |     if (_isRunning) { | ||||||
|       await _startLocationService(); |       //await _startLocationService(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -57,6 +57,20 @@ class LocationManager { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   _startLocationService() async { |   _startLocationService() async { | ||||||
|  |     Logger.d('Starting location tracking'); | ||||||
|  |     BackgroundLocator.registerLocationUpdate( | ||||||
|  |         locationCallback, | ||||||
|  |         //optional | ||||||
|  |         androidNotificationCallback: locationNotificationCallback, | ||||||
|  |         settings: LocationSettings( | ||||||
|  |             notificationTitle: "HA Client location tracking", | ||||||
|  |             notificationMsg: "HA Client is updating your device location", | ||||||
|  |             wakeLockTime: 20, | ||||||
|  |             autoStop: false, | ||||||
|  |             interval: 10 | ||||||
|  |         ), | ||||||
|  |     ); | ||||||
|  |     /* | ||||||
|     String webhookId = ConnectionManager().webhookId; |     String webhookId = ConnectionManager().webhookId; | ||||||
|     String httpWebHost = ConnectionManager().httpWebHost; |     String httpWebHost = ConnectionManager().httpWebHost; | ||||||
|     if (webhookId != null && webhookId.isNotEmpty) { |     if (webhookId != null && webhookId.isNotEmpty) { | ||||||
| @@ -100,14 +114,82 @@ class LocationManager { | |||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |     */ | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   _stopLocationService() async { |   _stopLocationService() async { | ||||||
|     Logger.d("Canceling previous schedule if any..."); |     Logger.d('Stopping location tracking'); | ||||||
|     await workManager.Workmanager.cancelAll(); |     IsolateNameServer.removePortNameMapping(isolateName); | ||||||
|  |     BackgroundLocator.unRegisterLocationUpdate(); | ||||||
|  |     /*Logger.d("Canceling previous schedule if any..."); | ||||||
|  |     await workManager.Workmanager.cancelAll();*/ | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static const String isolateName = "HAClientLocatorIsolate"; | ||||||
|  |  | ||||||
|  |   static void locationCallback(LocationDto locationDto) async { | ||||||
|  |     print('[Background location] Got location: $locationDto'); | ||||||
|  |     sendLocationData(locationDto); | ||||||
|  |     final SendPort send = IsolateNameServer.lookupPortByName(isolateName); | ||||||
|  |     send?.send(locationDto); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static Future<void> sendLocationData(LocationDto location) async { | ||||||
|  |     print('[Background location] Loading settings...'); | ||||||
|  |     SharedPreferences prefs = await SharedPreferences.getInstance(); | ||||||
|  |     String domain = prefs.getString('hassio-domain'); | ||||||
|  |     String port = prefs.getString('hassio-port'); | ||||||
|  |     String webhookId = prefs.getString('app-webhook-id'); | ||||||
|  |     String httpWebHost = | ||||||
|  |       "${prefs.getString('hassio-res-protocol')}://$domain:$port"; | ||||||
|  |     if (webhookId != null && webhookId.isNotEmpty) { | ||||||
|  |       String url = "$httpWebHost/api/webhook/$webhookId"; | ||||||
|  |       Map<String, String> headers = {}; | ||||||
|  |       headers["Content-Type"] = "application/json"; | ||||||
|  |       Map data = { | ||||||
|  |         "type": "update_location", | ||||||
|  |         "data": { | ||||||
|  |           "gps": [], | ||||||
|  |           "gps_accuracy": 0, | ||||||
|  |           "battery": 100 | ||||||
|  |         } | ||||||
|  |       }; | ||||||
|  |       try { | ||||||
|  |         if (location.longitude != null && location.latitude != null) { | ||||||
|  |           data["data"]["gps"] = [location.latitude, location.longitude]; | ||||||
|  |           data["data"]["gps_accuracy"] = location.accuracy; | ||||||
|  |           print('[Background location] Sending...'); | ||||||
|  |           try { | ||||||
|  |             http.Response response = await http.post( | ||||||
|  |                 url, | ||||||
|  |                 headers: headers, | ||||||
|  |                 body: json.encode(data) | ||||||
|  |             ); | ||||||
|  |             if (response.statusCode >= 200 && response.statusCode < 300) { | ||||||
|  |               print('[Background location] Success!'); | ||||||
|  |             } else { | ||||||
|  |               print('[Background location] Error sending data: ${response.statusCode}'); | ||||||
|  |             } | ||||||
|  |           } catch(e) { | ||||||
|  |             print('[Background location] Error sending data: $e'); | ||||||
|  |           } | ||||||
|  |         } else { | ||||||
|  |           print('[Background location] Error. Location is null'); | ||||||
|  |         } | ||||||
|  |       } catch (e) { | ||||||
|  |         print('[Background location] Error: $e'); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   static void locationNotificationCallback() { | ||||||
|  |     print('[Background location] User clicked on the notification'); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   updateDeviceLocation() async { |   updateDeviceLocation() async { | ||||||
|  |     /* | ||||||
|  |     try { | ||||||
|       Logger.d("[Foreground location] Started"); |       Logger.d("[Foreground location] Started"); | ||||||
|       Geolocator geolocator = Geolocator(); |       Geolocator geolocator = Geolocator(); | ||||||
|       var battery = Battery(); |       var battery = Battery(); | ||||||
| @@ -133,20 +215,28 @@ class LocationManager { | |||||||
|               } |               } | ||||||
|             }; |             }; | ||||||
|             Logger.d("[Foreground location] Sending data home..."); |             Logger.d("[Foreground location] Sending data home..."); | ||||||
|           var response = await http.post( |             http.Response response = await http.post( | ||||||
|               url, |               url, | ||||||
|               headers: {"Content-Type": "application/json"}, |               headers: {"Content-Type": "application/json"}, | ||||||
|               body: json.encode(data) |               body: json.encode(data) | ||||||
|             ); |             ); | ||||||
|  |             if (response.statusCode >= 300) { | ||||||
|  |               Logger.e('Foreground location update error: ${response.body}'); | ||||||
|  |             } | ||||||
|             Logger.d("[Foreground location] Got HTTP ${response.statusCode}"); |             Logger.d("[Foreground location] Got HTTP ${response.statusCode}"); | ||||||
|           } else { |           } else { | ||||||
|             Logger.d("[Foreground location] No location. Aborting."); |             Logger.d("[Foreground location] No location. Aborting."); | ||||||
|           } |           } | ||||||
|       } |       } | ||||||
|  |     } catch (e, stack) { | ||||||
|  |       Logger.e('Foreground location error: ${e.toSTring()}', stacktrace: stack); | ||||||
|  |     } | ||||||
|  |     */ | ||||||
|   } |   } | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* | ||||||
| void updateDeviceLocationIsolate() { | void updateDeviceLocationIsolate() { | ||||||
|   workManager.Workmanager.executeTask((backgroundTask, data) async { |   workManager.Workmanager.executeTask((backgroundTask, data) async { | ||||||
|     //print("[Background $backgroundTask] Started"); |     //print("[Background $backgroundTask] Started"); | ||||||
| @@ -235,3 +325,4 @@ void updateDeviceLocationIsolate() { | |||||||
|     return true; |     return true; | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  | */ | ||||||
| @@ -31,12 +31,16 @@ class MobileAppIntegrationManager { | |||||||
|       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); | ||||||
|       registrationData.addAll({ |       registrationData.addAll({ | ||||||
|         "device_id": "${DeviceInfoManager().unicDeviceId}", |  | ||||||
|         "app_id": "ha_client", |         "app_id": "ha_client", | ||||||
|         "app_name": "$appName", |         "app_name": "$appName", | ||||||
|         "os_name": DeviceInfoManager().osName, |         "os_name": DeviceInfoManager().osName, | ||||||
|         "supports_encryption": false, |         "supports_encryption": false, | ||||||
|       }); |       }); | ||||||
|  |       if (ConnectionManager().haVersion >= 104) { | ||||||
|  |         registrationData.addAll({ | ||||||
|  |           "device_id": "${DeviceInfoManager().unicDeviceId}" | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|       ConnectionManager().sendHTTPPost( |       ConnectionManager().sendHTTPPost( | ||||||
|           endPoint: "/api/mobile_app/registrations", |           endPoint: "/api/mobile_app/registrations", | ||||||
|           includeAuthHeader: true, |           includeAuthHeader: true, | ||||||
|   | |||||||
| @@ -14,13 +14,13 @@ class StartupUserMessagesManager { | |||||||
|   bool _supportAppDevelopmentMessageShown; |   bool _supportAppDevelopmentMessageShown; | ||||||
|   bool _whatsNewMessageShown; |   bool _whatsNewMessageShown; | ||||||
|   static final _supportAppDevelopmentMessageKey = "user-message-shown-support-development_3"; |   static final _supportAppDevelopmentMessageKey = "user-message-shown-support-development_3"; | ||||||
|   static final _whatsNewMessageKey = "user-message-shown-whats-new-1006"; |   static final _whatsNewMessageKey = "user-msg-whats-new-url"; | ||||||
|  |  | ||||||
|   void checkMessagesToShow() async { |   void checkMessagesToShow() async { | ||||||
|     SharedPreferences prefs = await SharedPreferences.getInstance(); |     SharedPreferences prefs = await SharedPreferences.getInstance(); | ||||||
|     await prefs.reload(); |     await prefs.reload(); | ||||||
|     _supportAppDevelopmentMessageShown = prefs.getBool(_supportAppDevelopmentMessageKey) ?? false; |     _supportAppDevelopmentMessageShown = prefs.getBool(_supportAppDevelopmentMessageKey) ?? false; | ||||||
|     _whatsNewMessageShown = prefs.getBool(_whatsNewMessageKey) ?? false; |     _whatsNewMessageShown = '${prefs.getString(_whatsNewMessageKey)}' == whatsNewUrl; | ||||||
|     if (!_whatsNewMessageShown) { |     if (!_whatsNewMessageShown) { | ||||||
|       _showWhatsNewMessage(); |       _showWhatsNewMessage(); | ||||||
|     } else if (!_supportAppDevelopmentMessageShown) { |     } else if (!_supportAppDevelopmentMessageShown) { | ||||||
| @@ -52,7 +52,7 @@ class StartupUserMessagesManager { | |||||||
|  |  | ||||||
|   void _showWhatsNewMessage() { |   void _showWhatsNewMessage() { | ||||||
|     SharedPreferences.getInstance().then((prefs) { |     SharedPreferences.getInstance().then((prefs) { | ||||||
|       prefs.setBool(_whatsNewMessageKey, true); |       prefs.setString(_whatsNewMessageKey, whatsNewUrl); | ||||||
|       eventBus.fire(ShowPageEvent(path: "/whats-new")); |       eventBus.fire(ShowPageEvent(path: "/whats-new")); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -53,9 +53,11 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   _switchLocationTrackingState(bool state) async { |   _switchLocationTrackingState(bool state) async { | ||||||
|  |     /* | ||||||
|     if (state) { |     if (state) { | ||||||
|       await LocationManager().updateDeviceLocation(); |       await LocationManager().updateDeviceLocation(); | ||||||
|     } |     } | ||||||
|  |     */ | ||||||
|     await LocationManager().setSettings(_locationTrackingEnabled, _locationInterval); |     await LocationManager().setSettings(_locationTrackingEnabled, _locationInterval); | ||||||
|     setState(() { |     setState(() { | ||||||
|       _wait = false; |       _wait = false; | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ class LookAndFeelSettingsPage extends StatefulWidget { | |||||||
| class _LookAndFeelSettingsPageState extends State<LookAndFeelSettingsPage> { | class _LookAndFeelSettingsPageState extends State<LookAndFeelSettingsPage> { | ||||||
|  |  | ||||||
|   AppTheme _currentTheme; |   AppTheme _currentTheme; | ||||||
|   bool _changed = false; |   bool _scrollBadges = false; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void initState() { |   void initState() { | ||||||
| @@ -26,13 +26,15 @@ class _LookAndFeelSettingsPageState extends State<LookAndFeelSettingsPage> { | |||||||
|     SharedPreferences.getInstance().then((prefs) { |     SharedPreferences.getInstance().then((prefs) { | ||||||
|       setState(() { |       setState(() { | ||||||
|         _currentTheme = AppTheme.values[prefs.getInt("app-theme") ?? AppTheme.defaultTheme.index]; |         _currentTheme = AppTheme.values[prefs.getInt("app-theme") ?? AppTheme.defaultTheme.index]; | ||||||
|  |         _scrollBadges = prefs.getBool('scroll-badges') ?? true; | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   _saveSettings(AppTheme theme) { |   _saveTheme(AppTheme theme) { | ||||||
|     SharedPreferences.getInstance().then((prefs) { |     SharedPreferences.getInstance().then((prefs) { | ||||||
|       prefs.setInt('app-theme', theme.index); |       prefs.setInt('app-theme', theme.index); | ||||||
|  |       prefs.setBool('scroll-badges', _scrollBadges); | ||||||
|       setState(() { |       setState(() { | ||||||
|         _currentTheme = theme; |         _currentTheme = theme; | ||||||
|         eventBus.fire(ChangeThemeEvent(_currentTheme)); |         eventBus.fire(ChangeThemeEvent(_currentTheme)); | ||||||
| @@ -40,6 +42,12 @@ class _LookAndFeelSettingsPageState extends State<LookAndFeelSettingsPage> { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   Future _saveOther() async { | ||||||
|  |     SharedPreferences prefs = await SharedPreferences.getInstance(); | ||||||
|  |     ConnectionManager().scrollBadges = _scrollBadges; | ||||||
|  |     await prefs.setBool('scroll-badges', _scrollBadges); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   Map appThemeName = { |   Map appThemeName = { | ||||||
|     AppTheme.defaultTheme: 'Default', |     AppTheme.defaultTheme: 'Default', | ||||||
|     AppTheme.haTheme: 'Home Assistant theme', |     AppTheme.haTheme: 'Home Assistant theme', | ||||||
| @@ -59,15 +67,35 @@ class _LookAndFeelSettingsPageState extends State<LookAndFeelSettingsPage> { | |||||||
|             iconSize: 30.0, |             iconSize: 30.0, | ||||||
|             isExpanded: true, |             isExpanded: true, | ||||||
|             style: Theme.of(context).textTheme.title, |             style: Theme.of(context).textTheme.title, | ||||||
|             //hint: Text("Select ${caption.toLowerCase()}"), |  | ||||||
|             items: AppTheme.values.map((value) { |             items: AppTheme.values.map((value) { | ||||||
|               return new DropdownMenuItem<AppTheme>( |               return new DropdownMenuItem<AppTheme>( | ||||||
|                 value: value, |                 value: value, | ||||||
|                 child: Text('${appThemeName[value]}'), |                 child: Text('${appThemeName[value]}'), | ||||||
|               ); |               ); | ||||||
|             }).toList(), |             }).toList(), | ||||||
|             onChanged: (theme) => _saveSettings(theme), |             onChanged: (theme) => _saveTheme(theme), | ||||||
|           ) |           ), | ||||||
|  |           Container(height: Sizes.doubleRowPadding), | ||||||
|  |           Text("Badges display:", style: Theme.of(context).textTheme.body2), | ||||||
|  |           Container(height: Sizes.rowPadding), | ||||||
|  |           DropdownButton<bool>( | ||||||
|  |             value: _scrollBadges, | ||||||
|  |             iconSize: 30.0, | ||||||
|  |             isExpanded: true, | ||||||
|  |             style: Theme.of(context).textTheme.title, | ||||||
|  |             items: [true, false].map((value) { | ||||||
|  |               return new DropdownMenuItem<bool>( | ||||||
|  |                 value: value, | ||||||
|  |                 child: Text('${value ? 'Horizontal scroll' : 'In rows'}'), | ||||||
|  |               ); | ||||||
|  |             }).toList(), | ||||||
|  |             onChanged: (val) { | ||||||
|  |               setState(() { | ||||||
|  |                 _scrollBadges = val; | ||||||
|  |               }); | ||||||
|  |               _saveOther(); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|         ] |         ] | ||||||
|       ); |       ); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ class _WhatsNewPageState extends State<WhatsNewPage> { | |||||||
|       error = ""; |       error = ""; | ||||||
|     }); |     }); | ||||||
|     http.Response response; |     http.Response response; | ||||||
|     response = await http.get("http://ha-client.app/service/whats_new_1.0.0_stable.md"); |     response = await http.get(whatsNewUrl); | ||||||
|     if (response.statusCode == 200) { |     if (response.statusCode == 200) { | ||||||
|       setState(() { |       setState(() { | ||||||
|         data = response.body; |         data = response.body; | ||||||
|   | |||||||
| @@ -54,7 +54,7 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> { | |||||||
|           }); |           }); | ||||||
|         } |         } | ||||||
|       }).catchError((e) { |       }).catchError((e) { | ||||||
|         Logger.e("Error loading $entityId history: $e"); |         Logger.e("Error loading $entityId history: $e", skipCrashlytics: true); | ||||||
|         if (!_disposed) { |         if (!_disposed) { | ||||||
|           setState(() { |           setState(() { | ||||||
|             _history = []; |             _history = []; | ||||||
|   | |||||||
| @@ -18,8 +18,8 @@ class Logger { | |||||||
|     print(data); |     print(data); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static void e(String message, {dynamic stacktrace, bool skipCrashlytics: false}) { |   static void e(dynamic message, {dynamic stacktrace, bool skipCrashlytics: false}) { | ||||||
|     _writeToLog(ErrorLevel.ERROR, message, stacktrace, skipCrashlytics); |     _writeToLog(ErrorLevel.ERROR, message.toString(), stacktrace, skipCrashlytics); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static void w(String message) { |   static void w(String message) { | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ part of 'main.dart'; | |||||||
|  |  | ||||||
| class HAView { | class HAView { | ||||||
|   List<CardData> cards = []; |   List<CardData> cards = []; | ||||||
|   List<Entity> badges = []; |   CardData badges; | ||||||
|   Entity linkedEntity; |   Entity linkedEntity; | ||||||
|   String name; |   String name; | ||||||
|   String id; |   String id; | ||||||
| @@ -16,28 +16,16 @@ class HAView { | |||||||
|     iconName = rawData['icon']; |     iconName = rawData['icon']; | ||||||
|     isPanel = rawData['panel'] ?? false; |     isPanel = rawData['panel'] ?? false; | ||||||
|  |  | ||||||
|     if (rawData['badges'] != null && rawData['badges'] is List) { |     if (rawData['badges'] != null && !isPanel) { | ||||||
|         rawData['badges'].forEach((entity) { |         badges = CardData.parse({ | ||||||
|           if (entity is String) { |           'type': CardType.BADGES, | ||||||
|             if (HomeAssistant().entities.isExist(entity)) { |           'badges': rawData['badges'] | ||||||
|               Entity e = HomeAssistant().entities.get(entity); |  | ||||||
|               badges.add(e); |  | ||||||
|             } |  | ||||||
|           } else { |  | ||||||
|             String eId = '${entity['entity']}'; |  | ||||||
|             if (HomeAssistant().entities.isExist(eId)) { |  | ||||||
|               Entity e = HomeAssistant().entities.get(eId); |  | ||||||
|               badges.add(e); |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       (rawData["cards"] ?? []).forEach((rawCardData) { |       (rawData['cards'] ?? []).forEach((rawCardData) { | ||||||
|         cards.add(CardData.parse(rawCardData)); |         cards.add(CardData.parse(rawCardData)); | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|       //cards.addAll(_createLovelaceCards(rawData["cards"] ?? [], 1)); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Widget buildTab() { |   Widget buildTab() { | ||||||
|   | |||||||
| @@ -18,6 +18,12 @@ class ViewWidget extends StatelessWidget { | |||||||
|       ); |       ); | ||||||
|     } else { |     } else { | ||||||
|       Widget cardsContainer; |       Widget cardsContainer; | ||||||
|  |       Widget badgesContainer; | ||||||
|  |       if (this.view.badges != null && this.view.badges is BadgesData) { | ||||||
|  |         badgesContainer = this.view.badges.buildCardWidget(); | ||||||
|  |       } else { | ||||||
|  |         badgesContainer = Container(width: 0, height: 0); | ||||||
|  |       } | ||||||
|       if (this.view.cards.isNotEmpty) { |       if (this.view.cards.isNotEmpty) { | ||||||
|         cardsContainer = DynamicMultiColumnLayout( |         cardsContainer = DynamicMultiColumnLayout( | ||||||
|           minColumnWidth: Sizes.minViewColumnWidth, |           minColumnWidth: Sizes.minViewColumnWidth, | ||||||
| @@ -44,13 +50,15 @@ class ViewWidget extends StatelessWidget { | |||||||
|       } else { |       } else { | ||||||
|         cardsContainer = Container(); |         cardsContainer = Container(); | ||||||
|       } |       } | ||||||
|       return ListView( |       return SingleChildScrollView( | ||||||
|           shrinkWrap: true, |  | ||||||
|           padding: EdgeInsets.all(0), |           padding: EdgeInsets.all(0), | ||||||
|  |           child: Column( | ||||||
|  |             mainAxisSize: MainAxisSize.min, | ||||||
|             children: <Widget>[ |             children: <Widget>[ | ||||||
|             _buildBadges(context), |               badgesContainer, | ||||||
|               cardsContainer |               cardsContainer | ||||||
|           ] |             ], | ||||||
|  |           ), | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -63,18 +71,4 @@ class ViewWidget extends StatelessWidget { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Widget _buildBadges(BuildContext context) { |  | ||||||
|     if (this.view.badges.isNotEmpty) { |  | ||||||
|       return Wrap( |  | ||||||
|         alignment: WrapAlignment.center, |  | ||||||
|         spacing: 10.0, |  | ||||||
|         runSpacing: 1.0, |  | ||||||
|         children: this.view.badges.map((badge) => |  | ||||||
|             badge.buildBadgeWidget(context)).toList(), |  | ||||||
|       ); |  | ||||||
|     } else { |  | ||||||
|       return Container(width: 0, height: 0,); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
| } | } | ||||||
							
								
								
									
										11
									
								
								pubspec.yaml
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								pubspec.yaml
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | |||||||
| name: hass_client | name: hass_client | ||||||
| description: Home Assistant Android Client | description: Home Assistant Android Client | ||||||
|  |  | ||||||
| version: 1.0.0+1008 | version: 1.1.0+1100 | ||||||
|  |  | ||||||
|  |  | ||||||
| environment: | environment: | ||||||
| @@ -27,12 +27,13 @@ dependencies: | |||||||
|   flutter_secure_storage: ^3.3.3 |   flutter_secure_storage: ^3.3.3 | ||||||
|   device_info: ^0.4.1+4 |   device_info: ^0.4.1+4 | ||||||
|   flutter_local_notifications: ^1.1.6 |   flutter_local_notifications: ^1.1.6 | ||||||
|   geolocator: ^5.3.1 |   #geolocator: ^5.3.1 | ||||||
|   workmanager: ^0.2.2 |   background_locator: ^1.1.3+1 | ||||||
|  |   #workmanager: ^0.2.2 | ||||||
|   battery: ^1.0.0 |   battery: ^1.0.0 | ||||||
|   firebase_crashlytics: ^0.1.3+3 |   firebase_crashlytics: ^0.1.3+3 | ||||||
|   syncfusion_flutter_core: ^18.1.43 |   syncfusion_flutter_core: ^18.1.48 | ||||||
|   syncfusion_flutter_gauges: ^18.1.43 |   syncfusion_flutter_gauges: ^18.1.48 | ||||||
|    |    | ||||||
|  |  | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import 'dart:io'; | |||||||
| Future<void> main() async { | Future<void> main() async { | ||||||
|   final config = { |   final config = { | ||||||
|     'syncfusion_license_key': Platform.environment['SYNCFUSION_LICENSE_KEY'], |     'syncfusion_license_key': Platform.environment['SYNCFUSION_LICENSE_KEY'], | ||||||
|  |     'version_type': Platform.environment['HA_CLIENT_VERSION_TYPE'] ?? '' | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   final filename = 'lib/.secrets.dart'; |   final filename = 'lib/.secrets.dart'; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user