Compare commits
	
		
			11 Commits
		
	
	
		
			v0.0.10-al
			...
			v0.1.0-alp
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 739e659a81 | ||
|  | 2fb8d8e26b | ||
|  | 2db432ccd2 | ||
|  | 4ad728d369 | ||
|  | e77c7df3e2 | ||
|  | cc0278ee55 | ||
|  | 1133a996b9 | ||
|  | 0d690e7630 | ||
|  | 4b0d857e9c | ||
|  | d2d1398f89 | ||
|  | de5321565d | 
| @@ -37,10 +37,10 @@ android { | |||||||
|  |  | ||||||
|     defaultConfig { |     defaultConfig { | ||||||
|         applicationId "com.keyboardcrumbs.haclient" |         applicationId "com.keyboardcrumbs.haclient" | ||||||
|         minSdkVersion 16 |         minSdkVersion 21 | ||||||
|         targetSdkVersion 27 |         targetSdkVersion 27 | ||||||
|         versionCode 15 |         versionCode 18 | ||||||
|         versionName "0.0.10-alpha1" |         versionName "0.1.0-alpha" | ||||||
|         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" |         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ class HassioDataModel { | |||||||
|   String _hassioPassword; |   String _hassioPassword; | ||||||
|   String _hassioAuthType; |   String _hassioAuthType; | ||||||
|   IOWebSocketChannel _hassioChannel; |   IOWebSocketChannel _hassioChannel; | ||||||
|   int _currentMssageId = 0; |   int _currentMessageId = 0; | ||||||
|   int _statesMessageId = 0; |   int _statesMessageId = 0; | ||||||
|   int _servicesMessageId = 0; |   int _servicesMessageId = 0; | ||||||
|   int _subscriptionMessageId = 0; |   int _subscriptionMessageId = 0; | ||||||
| @@ -31,6 +31,7 @@ class HassioDataModel { | |||||||
|   Completer _servicesCompleter; |   Completer _servicesCompleter; | ||||||
|   Completer _configCompleter; |   Completer _configCompleter; | ||||||
|   Timer _fetchingTimer; |   Timer _fetchingTimer; | ||||||
|  |   List _topBadgeDomains = ["alarm_control_panel", "binary_sensor", "device_tracker", "updater", "sun", "timer", "sensor"]; | ||||||
|  |  | ||||||
|   Map get entities => _entitiesData; |   Map get entities => _entitiesData; | ||||||
|   Map get services => _servicesData; |   Map get services => _servicesData; | ||||||
| @@ -127,8 +128,8 @@ class HassioDataModel { | |||||||
|         _parseEntities(data); |         _parseEntities(data); | ||||||
|       } else if (data["id"] == _servicesMessageId) { |       } else if (data["id"] == _servicesMessageId) { | ||||||
|         _parseServices(data); |         _parseServices(data); | ||||||
|       } else if (data["id"] == _currentMssageId) { |       } else if (data["id"] == _currentMessageId) { | ||||||
|         debugPrint("Request id:$_currentMssageId was successful"); |         debugPrint("Request id:$_currentMessageId was successful"); | ||||||
|       } else { |       } else { | ||||||
|         debugPrint("Skipped message due to messageId:"); |         debugPrint("Skipped message due to messageId:"); | ||||||
|         debugPrint(message); |         debugPrint(message); | ||||||
| @@ -149,14 +150,14 @@ class HassioDataModel { | |||||||
|  |  | ||||||
|   void _sendSubscribe() { |   void _sendSubscribe() { | ||||||
|     _incrementMessageId(); |     _incrementMessageId(); | ||||||
|     _subscriptionMessageId = _currentMssageId; |     _subscriptionMessageId = _currentMessageId; | ||||||
|     _sendMessageRaw('{"id": $_subscriptionMessageId, "type": "subscribe_events", "event_type": "state_changed"}'); |     _sendMessageRaw('{"id": $_subscriptionMessageId, "type": "subscribe_events", "event_type": "state_changed"}'); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future _getConfig() { |   Future _getConfig() { | ||||||
|     _configCompleter = new Completer(); |     _configCompleter = new Completer(); | ||||||
|     _incrementMessageId(); |     _incrementMessageId(); | ||||||
|     _configMessageId = _currentMssageId; |     _configMessageId = _currentMessageId; | ||||||
|     _sendMessageRaw('{"id": $_configMessageId, "type": "get_config"}'); |     _sendMessageRaw('{"id": $_configMessageId, "type": "get_config"}'); | ||||||
|  |  | ||||||
|     return _configCompleter.future; |     return _configCompleter.future; | ||||||
| @@ -165,7 +166,7 @@ class HassioDataModel { | |||||||
|   Future _getStates() { |   Future _getStates() { | ||||||
|     _statesCompleter = new Completer(); |     _statesCompleter = new Completer(); | ||||||
|     _incrementMessageId(); |     _incrementMessageId(); | ||||||
|     _statesMessageId = _currentMssageId; |     _statesMessageId = _currentMessageId; | ||||||
|     _sendMessageRaw('{"id": $_statesMessageId, "type": "get_states"}'); |     _sendMessageRaw('{"id": $_statesMessageId, "type": "get_states"}'); | ||||||
|  |  | ||||||
|     return _statesCompleter.future; |     return _statesCompleter.future; | ||||||
| @@ -174,14 +175,14 @@ class HassioDataModel { | |||||||
|   Future _getServices() { |   Future _getServices() { | ||||||
|     _servicesCompleter = new Completer(); |     _servicesCompleter = new Completer(); | ||||||
|     _incrementMessageId(); |     _incrementMessageId(); | ||||||
|     _servicesMessageId = _currentMssageId; |     _servicesMessageId = _currentMessageId; | ||||||
|     _sendMessageRaw('{"id": $_servicesMessageId, "type": "get_services"}'); |     _sendMessageRaw('{"id": $_servicesMessageId, "type": "get_services"}'); | ||||||
|  |  | ||||||
|     return _servicesCompleter.future; |     return _servicesCompleter.future; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   _incrementMessageId() { |   _incrementMessageId() { | ||||||
|     _currentMssageId += 1; |     _currentMessageId += 1; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   _sendMessageRaw(message) { |   _sendMessageRaw(message) { | ||||||
| @@ -244,24 +245,10 @@ class HassioDataModel { | |||||||
|       composedEntity["display_name"] = "${entity["attributes"]!=null ? entity["attributes"]["friendly_name"] ?? entity["attributes"]["name"] : "_"}"; |       composedEntity["display_name"] = "${entity["attributes"]!=null ? entity["attributes"]["friendly_name"] ?? entity["attributes"]["name"] : "_"}"; | ||||||
|       composedEntity["domain"] = entityDomain; |       composedEntity["domain"] = entityDomain; | ||||||
|  |  | ||||||
|       if ((entityDomain == "automation") || (entityDomain == "switch") || (entityDomain == "light")) { |  | ||||||
|         composedEntity["actionType"] = "switch"; |  | ||||||
|       } else if ((entityDomain == "script") || (entityDomain == "scene")) { |  | ||||||
|         composedEntity["actionType"] = "statelessIcon"; |  | ||||||
|       } else { |  | ||||||
|         composedEntity["actionType"] = "stateText"; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (composedEntity["attributes"] != null) { |       if (composedEntity["attributes"] != null) { | ||||||
|         if ((entityDomain == "group")&&(composedEntity["attributes"]["view"] == true)) { |         if ((entityDomain == "group")&&(composedEntity["attributes"]["view"] == true)) { | ||||||
|           uiGroups.add(entityId); |           uiGroups.add(entityId); | ||||||
|         } |         } | ||||||
|         String iconName = composedEntity["attributes"]["icon"]; |  | ||||||
|         if (iconName != null) { |  | ||||||
|           composedEntity["iconCode"] = MaterialDesignIcons.getCustomIconByName(iconName); |  | ||||||
|         } else { |  | ||||||
|           composedEntity["iconCode"] = MaterialDesignIcons.getDefaultIconByEntityId(entityId); // |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -277,18 +264,40 @@ class HassioDataModel { | |||||||
|  |  | ||||||
|     //Gethering information for UI |     //Gethering information for UI | ||||||
|     debugPrint("Gethering views"); |     debugPrint("Gethering views"); | ||||||
|     uiGroups.forEach((viewId) { |     int viewCounter = 0; | ||||||
|  |     uiGroups.forEach((viewId) { //Each view | ||||||
|  |       viewCounter +=1; | ||||||
|       var viewGroup = _entitiesData[viewId]; |       var viewGroup = _entitiesData[viewId]; | ||||||
|       Map viewGroupStructure = {}; |       Map viewGroupStructure = {}; | ||||||
|       if (viewGroup != null) { |       if (viewGroup != null) { | ||||||
|         viewGroupStructure["standalone"] = []; |         viewGroupStructure["groups"] = {}; | ||||||
|         viewGroupStructure["groups"] = []; |         viewGroupStructure["state"] = "on"; | ||||||
|         viewGroupStructure["iconCode"] = viewGroup["iconCode"]; |         viewGroupStructure["entity_id"] = viewGroup["entity_id"]; | ||||||
|         viewGroup["attributes"]["entity_id"].forEach((entityId) { |         viewGroupStructure["badges"] = {"children": []}; | ||||||
|           if (_entitiesData[entityId]["domain"] != "group") { |         viewGroupStructure["attributes"] = viewGroup["attributes"] != null ? {"icon": viewGroup["attributes"]["icon"]} : {"icon": "none"}; | ||||||
|             viewGroupStructure["standalone"].add(entityId); |  | ||||||
|           } else { |  | ||||||
|  |         viewGroup["attributes"]["entity_id"].forEach((entityId) { //Each entity or group in view | ||||||
|           Map newGroup = {}; |           Map newGroup = {}; | ||||||
|  |           String domain = _entitiesData[entityId]["domain"]; | ||||||
|  |           if (domain != "group") { | ||||||
|  |             if (_topBadgeDomains.contains(domain)) { | ||||||
|  |               viewGroupStructure["badges"]["children"].add(entityId); | ||||||
|  |             } else { | ||||||
|  |               String autoGroupID = "$domain.$domain$viewCounter"; | ||||||
|  |               if (viewGroupStructure["groups"]["$autoGroupID"] == null) { | ||||||
|  |                 newGroup["entity_id"] = "$domain.$domain$viewCounter"; | ||||||
|  |                 newGroup["friendly_name"] = "$domain"; | ||||||
|  |                 newGroup["children"] = []; | ||||||
|  |                 newGroup["children"].add(entityId); | ||||||
|  |                 viewGroupStructure["groups"]["$autoGroupID"] = | ||||||
|  |                     Map.from(newGroup); | ||||||
|  |               } else { | ||||||
|  |                 viewGroupStructure["groups"]["$autoGroupID"]["children"].add( | ||||||
|  |                     entityId); | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } else { | ||||||
|             newGroup["entity_id"] = entityId; |             newGroup["entity_id"] = entityId; | ||||||
|             newGroup["friendly_name"] = |             newGroup["friendly_name"] = | ||||||
|             (_entitiesData[entityId]['attributes'] != null) |             (_entitiesData[entityId]['attributes'] != null) | ||||||
| @@ -299,7 +308,7 @@ class HassioDataModel { | |||||||
|                 groupedEntityId) { |                 groupedEntityId) { | ||||||
|               newGroup["children"].add(groupedEntityId); |               newGroup["children"].add(groupedEntityId); | ||||||
|             }); |             }); | ||||||
|             viewGroupStructure["groups"].add(Map.from(newGroup)); |             viewGroupStructure["groups"]["$entityId"] = Map.from(newGroup); | ||||||
|           } |           } | ||||||
|         }); |         }); | ||||||
|       _uiStructure[viewId.split(".")[1]] = viewGroupStructure; |       _uiStructure[viewId.split(".")[1]] = viewGroupStructure; | ||||||
| @@ -316,7 +325,7 @@ class HassioDataModel { | |||||||
|     }); |     }); | ||||||
|     _reConnectSocket().then((r) { |     _reConnectSocket().then((r) { | ||||||
|       _incrementMessageId(); |       _incrementMessageId(); | ||||||
|       _sendMessageRaw('{"id": $_currentMssageId, "type": "call_service", "domain": "$domain", "service": "$service", "service_data": {"entity_id": "$entity_id"}}'); |       _sendMessageRaw('{"id": $_currentMessageId, "type": "call_service", "domain": "$domain", "service": "$service", "service_data": {"entity_id": "$entity_id"}}'); | ||||||
|       _sendTimer.cancel(); |       _sendTimer.cancel(); | ||||||
|       sendCompleter.complete(); |       sendCompleter.complete(); | ||||||
|     }).catchError((e){ |     }).catchError((e){ | ||||||
| @@ -341,7 +350,59 @@ class MaterialDesignIcons { | |||||||
|     "input_number": "mdi:ray-vertex", |     "input_number": "mdi:ray-vertex", | ||||||
|     "input_select": "mdi:format-list-bulleted", |     "input_select": "mdi:format-list-bulleted", | ||||||
|     "input_text": "mdi:textbox", |     "input_text": "mdi:textbox", | ||||||
|     "sun": "mdi:white-balance-sunny", |     "sun": "mdi:white-balance-sunny" | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   static Map _defaultIconsByDeviceClass = { | ||||||
|  |     //"binary_sensor.battery": "mdi:", //TODO | ||||||
|  |     "binary_sensor.cold.on": "mdi:snowflake", | ||||||
|  |     "binary_sensor.cold.off": "mdi:thermometer", | ||||||
|  |     "binary_sensor.connectivity.on": "mdi:server-network", | ||||||
|  |     "binary_sensor.connectivity.off": "mdi:server-network-off", | ||||||
|  |     "binary_sensor.door.on": "mdi:door-open", | ||||||
|  |     "binary_sensor.door.off": "mdi:door-closed", | ||||||
|  |     //"binary_sensor.garage_door": "mdi:", | ||||||
|  |     //"binary_sensor.gas": "mdi:", | ||||||
|  |     "binary_sensor.heat.on": "mdi:fire", | ||||||
|  |     "binary_sensor.heat.off": "mdi:thermometer", | ||||||
|  |     "binary_sensor.light.on": "mdi:brightness-7", | ||||||
|  |     "binary_sensor.light.off": "mdi:brightness-5", | ||||||
|  |     //"binary_sensor.lock.on": "mdi:", | ||||||
|  |     //"binary_sensor.lock.off": "mdi:", | ||||||
|  |     "binary_sensor.moisture.on": "mdi:water", | ||||||
|  |     "binary_sensor.moisture.off": "mdi:water-off", | ||||||
|  |     "binary_sensor.motion.on": "mdi:run", | ||||||
|  |     "binary_sensor.motion.off": "mdi:walk", | ||||||
|  |     "binary_sensor.moving.on": "mdi:checkbox-marked-circle", | ||||||
|  |     "binary_sensor.moving.off": "mdi:checkbox-blank-circle-outline", | ||||||
|  |     "binary_sensor.occupancy.on": "mdi:home", | ||||||
|  |     "binary_sensor.occupancy.off": "mdi:home-outline", | ||||||
|  |     "binary_sensor.opening.on": "mdi:square-outline", | ||||||
|  |     "binary_sensor.opening.off": "mdi:square", | ||||||
|  |     //"binary_sensor.plug.on": "mdi:", | ||||||
|  |     //"binary_sensor.plug.off": "mdi:", | ||||||
|  |     "binary_sensor.power.on": "mdi:alert", | ||||||
|  |     "binary_sensor.power.off": "mdi:verified", | ||||||
|  |     //"binary_sensor.presence.on": "mdi:", | ||||||
|  |     //"binary_sensor.presence.off": "mdi:", | ||||||
|  |     //"binary_sensor.problem.on": "mdi:", | ||||||
|  |     //"binary_sensor.problem.off": "mdi:", | ||||||
|  |     "binary_sensor.safety.on": "mdi:alert", | ||||||
|  |     "binary_sensor.safety.off": "mdi:verified", | ||||||
|  |     "binary_sensor.smoke.on": "mdi:alert", | ||||||
|  |     "binary_sensor.smoke.off": "mdi:verified", | ||||||
|  |     "binary_sensor.sound.on": "mdi:music-note", | ||||||
|  |     "binary_sensor.sound.off": "mdi:music-note-off", | ||||||
|  |     "binary_sensor.vibration.on": "mdi:vibrate", | ||||||
|  |     "binary_sensor.vibration.off": "mdi:mdi-crop-portrait", | ||||||
|  |     //"binary_sensor.window.on": "mdi:", | ||||||
|  |     //"binary_sensor.window.off": "mdi:", | ||||||
|  |     "sensor.battery": "mdi:battery-80", | ||||||
|  |     "sensor.humidity": "mdi:water-percent", | ||||||
|  |     //"sensor.illuminance": "mdi:", | ||||||
|  |     "sensor.temperature": "mdi:thermometer", | ||||||
|  |     //"cover.window": "mdi:", | ||||||
|  |     //"cover.garage": "mdi:", | ||||||
|   }; |   }; | ||||||
|   static Map _iconsDataMap = { |   static Map _iconsDataMap = { | ||||||
|     "mdi:access-point": 0xf002, |     "mdi:access-point": 0xf002, | ||||||
| @@ -3141,13 +3202,59 @@ class MaterialDesignIcons { | |||||||
|     "mdi:blank": 0xf68c |     "mdi:blank": 0xf68c | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   static int getCustomIconByName(String name) { |   static Widget createIconFromEntityData(Map data, double size, Color color) { | ||||||
|  |     if ((data["attributes"] != null) && (data["attributes"]["entity_picture"] != null)) { | ||||||
|  |       if (homeAssistantWebHost != null) { | ||||||
|  |         return CircleAvatar( | ||||||
|  |           backgroundColor: Colors.white, | ||||||
|  |           backgroundImage: CachedNetworkImageProvider( | ||||||
|  |             "$homeAssistantWebHost${data["attributes"]["entity_picture"]}", | ||||||
|  |           ), | ||||||
|  |         ); | ||||||
|  |       } else { | ||||||
|  |         return Container(width: 0.0, height: 0.0); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       String iconName = data["attributes"] != null | ||||||
|  |           ? data["attributes"]["icon"] | ||||||
|  |           : null; | ||||||
|  |       int iconCode = 0; | ||||||
|  |       if (iconName != null) { | ||||||
|  |         iconCode = getIconCodeByIconName(iconName); | ||||||
|  |       } else { | ||||||
|  |         iconCode = getDefaultIconByEntityId(data["entity_id"], | ||||||
|  |             data["attributes"] != null | ||||||
|  |                 ? data["attributes"]["device_class"] | ||||||
|  |                 : null, data["state"]); // | ||||||
|  |       } | ||||||
|  |       return Icon( | ||||||
|  |           IconData(iconCode, fontFamily: 'Material Design Icons'), | ||||||
|  |           size: size, | ||||||
|  |           color: color, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static IconData createIconDataFromIconCode(int code) { | ||||||
|  |     return IconData(code, fontFamily: 'Material Design Icons'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static IconData createIconDataFromIconName(String name) { | ||||||
|  |     return IconData(getIconCodeByIconName(name), fontFamily: 'Material Design Icons'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static int getIconCodeByIconName(String name) { | ||||||
|     return _iconsDataMap[name] ?? 0; |     return _iconsDataMap[name] ?? 0; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static int getDefaultIconByEntityId(String entityId) { |   static int getDefaultIconByEntityId(String entityId, String deviceClass, String state) { | ||||||
|     String domain = entityId.split(".")[0]; |     String domain = entityId.split(".")[0]; | ||||||
|     String iconName = _defaultIconsByDomains[domain]; |     String iconNameByDomain = _defaultIconsByDomains[domain]; | ||||||
|  |     String iconNameByDeviceClass; | ||||||
|  |     if (deviceClass != null) { | ||||||
|  |       iconNameByDeviceClass = _defaultIconsByDeviceClass["$domain.$deviceClass.$state"] ?? _defaultIconsByDeviceClass["$domain.$deviceClass"]; | ||||||
|  |     } | ||||||
|  |     String iconName = iconNameByDeviceClass ?? iconNameByDomain; | ||||||
|     if (iconName != null) { |     if (iconName != null) { | ||||||
|       return _iconsDataMap[iconName] ?? 0; |       return _iconsDataMap[iconName] ?? 0; | ||||||
|     } else { |     } else { | ||||||
|   | |||||||
							
								
								
									
										321
									
								
								lib/main.dart
									
									
									
									
									
								
							
							
						
						
									
										321
									
								
								lib/main.dart
									
									
									
									
									
								
							| @@ -7,13 +7,16 @@ import 'package:web_socket_channel/io.dart'; | |||||||
| import 'package:progress_indicators/progress_indicators.dart'; | import 'package:progress_indicators/progress_indicators.dart'; | ||||||
| import 'package:event_bus/event_bus.dart'; | import 'package:event_bus/event_bus.dart'; | ||||||
| import 'package:flutter/widgets.dart'; | import 'package:flutter/widgets.dart'; | ||||||
|  | import 'package:cached_network_image/cached_network_image.dart'; | ||||||
|  |  | ||||||
| part 'settings.dart'; | part 'settings.dart'; | ||||||
| part 'data_model.dart'; | part 'data_model.dart'; | ||||||
|  |  | ||||||
| EventBus eventBus = new EventBus(); | EventBus eventBus = new EventBus(); | ||||||
| const String appName = "HA Client"; | const String appName = "HA Client"; | ||||||
| const appVersion = "0.0.10-alpha1"; | const appVersion = "0.1.0-alpha"; | ||||||
|  |  | ||||||
|  | String homeAssistantWebHost; | ||||||
|  |  | ||||||
| void main() => runApp(new HassClientApp()); | void main() => runApp(new HassClientApp()); | ||||||
|  |  | ||||||
| @@ -56,13 +59,17 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|   StreamSubscription _stateSubscription; |   StreamSubscription _stateSubscription; | ||||||
|   StreamSubscription _settingsSubscription; |   StreamSubscription _settingsSubscription; | ||||||
|   bool _isLoading = true; |   bool _isLoading = true; | ||||||
|   Map _stateIconColors = { |   Map<String, Color> _stateIconColors = { | ||||||
|     "on": Colors.amber, |     "on": Colors.amber, | ||||||
|     "off": Colors.blueGrey, |     "off": Color.fromRGBO(68, 115, 158, 1.0), | ||||||
|     "unavailable": Colors.black12, |     "unavailable": Colors.black12, | ||||||
|     "unknown": Colors.black12, |     "unknown": Colors.black12, | ||||||
|     "playing": Colors.amber |     "playing": Colors.amber | ||||||
|   }; |   }; | ||||||
|  |   Map<String, Color> _badgeColors = { | ||||||
|  |     "default": Color.fromRGBO(223, 76, 30, 1.0), | ||||||
|  |     "binary_sensor": Color.fromRGBO(3, 155, 229, 1.0) | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void initState() { |   void initState() { | ||||||
| @@ -92,6 +99,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|     String port = prefs.getString('hassio-port'); |     String port = prefs.getString('hassio-port'); | ||||||
|     _instanceHost = "$domain:$port"; |     _instanceHost = "$domain:$port"; | ||||||
|     String apiEndpoint = "${prefs.getString('hassio-protocol')}://$domain:$port/api/websocket"; |     String apiEndpoint = "${prefs.getString('hassio-protocol')}://$domain:$port/api/websocket"; | ||||||
|  |     homeAssistantWebHost = "${prefs.getString('hassio-res-protocol')}://$domain:$port"; | ||||||
|     String apiPassword = prefs.getString('hassio-password'); |     String apiPassword = prefs.getString('hassio-password'); | ||||||
|     String authType = prefs.getString('hassio-auth-type'); |     String authType = prefs.getString('hassio-auth-type'); | ||||||
|     if ((domain == null) || (port == null) || (apiPassword == null) || |     if ((domain == null) || (port == null) || (apiPassword == null) || | ||||||
| @@ -156,52 +164,175 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|     }).catchError((e) => _setErrorState(e)); |     }).catchError((e) => _setErrorState(e)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Widget _buildEntityAction(String entityId) { |   List<Widget> _buildViews() { | ||||||
|     var entity = _entitiesData[entityId]; |     List<Widget> result = []; | ||||||
|     Widget result; |     if ((_entitiesData != null) && (_uiStructure != null)) { | ||||||
|     if (entity["actionType"] == "switch") { |       _uiStructure.forEach((viewId, structure) { | ||||||
|       result = Switch( |         result.add( | ||||||
|         value: (entity["state"] == "on"), |             RefreshIndicator( | ||||||
|         onChanged: ((state) { |               color: Colors.amber, | ||||||
|           _callService( |               child: ListView( | ||||||
|               entity["domain"], state ? "turn_on" : "turn_off", entityId); |                 physics: const AlwaysScrollableScrollPhysics(), | ||||||
|           setState(() { |                 children: _buildSingleView(structure), | ||||||
|             _entitiesData[entityId]["state"] = state ? "on" : "off"; |  | ||||||
|           }); |  | ||||||
|         }), |  | ||||||
|       ); |  | ||||||
|     } else if (entity["actionType"] == "statelessIcon") { |  | ||||||
|       result = SizedBox( |  | ||||||
|           width: 60.0, |  | ||||||
|           child: FlatButton( |  | ||||||
|             onPressed: (() { |  | ||||||
|               _callService(entity["domain"], "turn_on", entityId); |  | ||||||
|             }), |  | ||||||
|             child: Text( |  | ||||||
|               "Run", |  | ||||||
|               textAlign: TextAlign.right, |  | ||||||
|               style: new TextStyle(fontSize: 16.0, color: Colors.blue), |  | ||||||
|               ), |               ), | ||||||
|           )); |               onRefresh: () => _refreshData(), | ||||||
|     } else { |             ) | ||||||
|       result = Padding( |         ); | ||||||
|           padding: EdgeInsets.fromLTRB(0.0, 0.0, 16.0, 0.0), |       }); | ||||||
|           child: Text( |  | ||||||
|               "${entity["state"]}${(entity["attributes"] != null && entity["attributes"]["unit_of_measurement"] != null) ? entity["attributes"]["unit_of_measurement"] : ''}", |  | ||||||
|               textAlign: TextAlign.right, |  | ||||||
|               style: new TextStyle( |  | ||||||
|                 fontSize: 16.0, |  | ||||||
|               ))); |  | ||||||
|     } |     } | ||||||
|     /*return SizedBox( |  | ||||||
|       width: 60.0, |  | ||||||
|       // height: double.infinity, |  | ||||||
|       child: result |  | ||||||
|     );*/ |  | ||||||
|     return result; |     return result; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Card _buildCard(List<String> ids, String name) { |   List<Widget> _buildSingleView(structure) { | ||||||
|  |     List<Widget> result = []; | ||||||
|  |     if (structure["badges"]["children"].length > 0) { | ||||||
|  |       result.add( | ||||||
|  |             Wrap( | ||||||
|  |               alignment: WrapAlignment.center, | ||||||
|  |               spacing: 10.0, | ||||||
|  |               runSpacing: 4.0, | ||||||
|  |               //padding: new EdgeInsets.all(8.0), | ||||||
|  |               //itemExtent: 40.0, | ||||||
|  |               children: _buildBadges(structure["badges"]["children"]), | ||||||
|  |             ) | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |     structure["groups"].forEach((id, group) { | ||||||
|  |       if (group["children"].length > 0) { | ||||||
|  |         result.add(_buildCard( | ||||||
|  |             group["children"], group["friendly_name"].toString())); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     return result; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   List<Widget> _buildBadges(List ids) { | ||||||
|  |     List<Widget> result = []; | ||||||
|  |     ids.forEach((entityId) { | ||||||
|  |       var data = _entitiesData[entityId]; | ||||||
|  |       if (data == null) { | ||||||
|  |         debugPrint("Hiding unknown entity from badges: $entityId"); | ||||||
|  |       } else { | ||||||
|  |         result.add( | ||||||
|  |         _buildSingleBadge(data) | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |     return result; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Widget _buildSingleBadge(data) { | ||||||
|  |     double iconSize = 26.0; | ||||||
|  |     Widget badgeIcon; | ||||||
|  |     String badgeTextValue; | ||||||
|  |     Color iconColor = _badgeColors[data["domain"]] ?? _badgeColors["default"]; | ||||||
|  |     switch (data["domain"]) { | ||||||
|  |       case "sun": { | ||||||
|  |         badgeIcon = data["state"] == "below_horizon" ? | ||||||
|  |           Icon( | ||||||
|  |             MaterialDesignIcons.createIconDataFromIconCode(0xf0dc), | ||||||
|  |             size: iconSize, | ||||||
|  |           ) : | ||||||
|  |           Icon( | ||||||
|  |             MaterialDesignIcons.createIconDataFromIconCode(0xf5a8), | ||||||
|  |             size: iconSize, | ||||||
|  |           ); | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |       case "sensor": { | ||||||
|  |         badgeTextValue = data["attributes"]["unit_of_measurement"]; | ||||||
|  |         badgeIcon = Center( | ||||||
|  |           child: Text( | ||||||
|  |             "${data['state']}", | ||||||
|  |             overflow: TextOverflow.fade, | ||||||
|  |             softWrap: false, | ||||||
|  |             textAlign: TextAlign.center, | ||||||
|  |             style: TextStyle(fontSize: 18.0), | ||||||
|  |           ), | ||||||
|  |         ); | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |       case "device_tracker": { | ||||||
|  |         badgeIcon = MaterialDesignIcons.createIconFromEntityData(data, iconSize,Colors.black); | ||||||
|  |         badgeTextValue = data["state"]; | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |       default: { | ||||||
|  |        badgeIcon = MaterialDesignIcons.createIconFromEntityData(data, iconSize,Colors.black); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     Widget badgeText; | ||||||
|  |     if (badgeTextValue == null) { | ||||||
|  |       badgeText = Container(width: 0.0, height: 0.0); | ||||||
|  |     } else { | ||||||
|  |       badgeText = Container( | ||||||
|  |           padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0), | ||||||
|  |           child: Text("$badgeTextValue", | ||||||
|  |               style: TextStyle(fontSize: 13.0, color: Colors.white), | ||||||
|  |               textAlign: TextAlign.center, softWrap: false, overflow: TextOverflow.fade), | ||||||
|  |           decoration: new BoxDecoration( | ||||||
|  |             // Circle shape | ||||||
|  |             //shape: BoxShape.circle, | ||||||
|  |             color: iconColor, | ||||||
|  |             borderRadius: BorderRadius.circular(9.0), | ||||||
|  |           ) | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     return 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: Colors.white, | ||||||
|  |             // 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: -15.0, | ||||||
|  |                 right: -15.0, | ||||||
|  |                 child: Center( | ||||||
|  |                   child: badgeText, | ||||||
|  |                 ) | ||||||
|  |               ) | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |         Container( | ||||||
|  |           width: 60.0, | ||||||
|  |           child: Text( | ||||||
|  |             "${data['display_name']}", | ||||||
|  |             textAlign: TextAlign.center, | ||||||
|  |             softWrap: true, | ||||||
|  |             maxLines: 2, | ||||||
|  |             overflow: TextOverflow.ellipsis, | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Card _buildCard(List ids, String name) { | ||||||
|     List<Widget> body = []; |     List<Widget> body = []; | ||||||
|     body.add(_buildCardHeader(name)); |     body.add(_buildCardHeader(name)); | ||||||
|     body.addAll(_buildCardBody(ids)); |     body.addAll(_buildCardBody(ids)); | ||||||
| @@ -228,7 +359,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|     return result; |     return result; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   List<Widget> _buildCardBody(List<String> ids) { |   List<Widget> _buildCardBody(List ids) { | ||||||
|     List<Widget> entities = []; |     List<Widget> entities = []; | ||||||
|     ids.forEach((id) { |     ids.forEach((id) { | ||||||
|       var data = _entitiesData[id]; |       var data = _entitiesData[id]; | ||||||
| @@ -236,15 +367,13 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|         debugPrint("Hiding unknown entity from card: $id"); |         debugPrint("Hiding unknown entity from card: $id"); | ||||||
|       } else { |       } else { | ||||||
|         entities.add(new ListTile( |         entities.add(new ListTile( | ||||||
|           leading: Icon( |           leading: MaterialDesignIcons.createIconFromEntityData(data, 28.0, _stateIconColors[data["state"]] ?? Colors.blueGrey), | ||||||
|             _createMDIfromCode(data["iconCode"]), |  | ||||||
|             color: _stateIconColors[data["state"]] ?? Colors.blueGrey, |  | ||||||
|           ), |  | ||||||
|           //subtitle: Text("${data['entity_id']}"), |           //subtitle: Text("${data['entity_id']}"), | ||||||
|           trailing: _buildEntityAction(id), |           trailing: _buildEntityActionWidget(data), | ||||||
|           title: Text( |           title: Text( | ||||||
|             "${data["display_name"]}", |             "${data["display_name"]}", | ||||||
|             overflow: TextOverflow.ellipsis, |             overflow: TextOverflow.fade, | ||||||
|  |             softWrap: false, | ||||||
|           ), |           ), | ||||||
|         )); |         )); | ||||||
|       } |       } | ||||||
| @@ -252,42 +381,73 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|     return entities; |     return entities; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   List<Widget> buildSingleView(structure) { |   Widget _buildEntityActionWidget(data) { | ||||||
|       List<Widget> result = []; |     String entityId = data["entity_id"]; | ||||||
|       structure["standalone"].forEach((entityId) { |     Widget result; | ||||||
|         result.add(_buildCard([entityId], "")); |     switch (data["domain"]) { | ||||||
|       }); |       case "automation": | ||||||
|       structure["groups"].forEach((group) { |       case "switch": | ||||||
|         result.add(_buildCard( |       case "light": { | ||||||
|             group["children"], group["friendly_name"].toString())); |         result = Switch( | ||||||
|  |           value: (data["state"] == "on"), | ||||||
|  |           onChanged: ((state) { | ||||||
|  |             _callService( | ||||||
|  |                 data["domain"], state ? "turn_on" : "turn_off", entityId); | ||||||
|  |             setState(() { | ||||||
|  |               _entitiesData[entityId]["state"] = state ? "on" : "off"; | ||||||
|             }); |             }); | ||||||
|  |           }), | ||||||
|  |         ); | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       case "script": | ||||||
|  |       case "scene": { | ||||||
|  |         result = SizedBox( | ||||||
|  |           width: 60.0, | ||||||
|  |           child: FlatButton( | ||||||
|  |             onPressed: (() { | ||||||
|  |               _callService(data["domain"], "turn_on", entityId); | ||||||
|  |             }), | ||||||
|  |             child: Text( | ||||||
|  |               "Run", | ||||||
|  |               textAlign: TextAlign.right, | ||||||
|  |               style: new TextStyle(fontSize: 16.0, color: Colors.blue), | ||||||
|  |             ), | ||||||
|  |           ) | ||||||
|  |         ); | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       default: { | ||||||
|  |         result = Padding( | ||||||
|  |           padding: EdgeInsets.fromLTRB(0.0, 0.0, 16.0, 0.0), | ||||||
|  |           child: Text( | ||||||
|  |             "${data["state"]}${(data["attributes"] != null && data["attributes"]["unit_of_measurement"] != null) ? data["attributes"]["unit_of_measurement"] : ''}", | ||||||
|  |             textAlign: TextAlign.right, | ||||||
|  |             style: new TextStyle( | ||||||
|  |               fontSize: 16.0, | ||||||
|  |             ) | ||||||
|  |           ) | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /*return SizedBox( | ||||||
|  |       width: 60.0, | ||||||
|  |       // height: double.infinity, | ||||||
|  |       child: result | ||||||
|  |     );*/ | ||||||
|     return result; |     return result; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   List<ListView> buildUIViews() { |  | ||||||
|     List<ListView> result = []; |  | ||||||
|     if ((_entitiesData != null) && (_uiStructure != null)) { |  | ||||||
|       _uiStructure.forEach((viewId, structure) { |  | ||||||
|         result.add(ListView( |  | ||||||
|           children: buildSingleView(structure), |  | ||||||
|         )); |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
|     return result; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   IconData _createMDIfromCode(int code) { |  | ||||||
|     return IconData(code, fontFamily: 'Material Design Icons'); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   List<Tab> buildUIViewTabs() { |   List<Tab> buildUIViewTabs() { | ||||||
|     List<Tab> result = []; |     List<Tab> result = []; | ||||||
|     if ((_entitiesData != null) && (_uiStructure != null)) { |     if ((_entitiesData != null) && (_uiStructure != null)) { | ||||||
|       _uiStructure.forEach((viewId, structure) { |       _uiStructure.forEach((viewId, structure) { | ||||||
|         result.add( |         result.add( | ||||||
|             Tab( |             Tab( | ||||||
|                 icon: Icon(_createMDIfromCode(structure["iconCode"])) |                 icon: MaterialDesignIcons.createIconFromEntityData(structure, 24.0, null) | ||||||
|             ) |             ) | ||||||
|         ); |         ); | ||||||
|       }); |       }); | ||||||
| @@ -440,7 +600,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|                 ), |                 ), | ||||||
|               ),*/ |               ),*/ | ||||||
|               Icon( |               Icon( | ||||||
|                 _createMDIfromCode(MaterialDesignIcons.getCustomIconByName("mdi:home-assistant")), |                 MaterialDesignIcons.createIconDataFromIconName("mdi:home-assistant"), | ||||||
|                 size: 100.0, |                 size: 100.0, | ||||||
|                 color: _errorCodeToBeShown == 0 ? Colors.blue : Colors.redAccent, |                 color: _errorCodeToBeShown == 0 ? Colors.blue : Colors.redAccent, | ||||||
|               ), |               ), | ||||||
| @@ -468,12 +628,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|             ), |             ), | ||||||
|             drawer: _buildAppDrawer(), |             drawer: _buildAppDrawer(), | ||||||
|             body: TabBarView( |             body: TabBarView( | ||||||
|                 children: buildUIViews() |                 children: _buildViews() | ||||||
|             ), |  | ||||||
|             floatingActionButton: new FloatingActionButton( |  | ||||||
|               onPressed: _refreshData, |  | ||||||
|               tooltip: 'Increment', |  | ||||||
|               child: new Icon(Icons.refresh), |  | ||||||
|             ), |             ), | ||||||
|           ) |           ) | ||||||
|       ); |       ); | ||||||
|   | |||||||
| @@ -40,6 +40,7 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> { | |||||||
|     prefs.setString("hassio-port", _hassioPort); |     prefs.setString("hassio-port", _hassioPort); | ||||||
|     prefs.setString("hassio-password", _hassioPassword); |     prefs.setString("hassio-password", _hassioPassword); | ||||||
|     prefs.setString("hassio-protocol", _socketProtocol); |     prefs.setString("hassio-protocol", _socketProtocol); | ||||||
|  |     prefs.setString("hassio-res-protocol", _socketProtocol == "wss" ? "https" : "http"); | ||||||
|     prefs.setString("hassio-auth-type", _authType); |     prefs.setString("hassio-auth-type", _authType); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										35
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -36,6 +36,13 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.0.4" |     version: "1.0.4" | ||||||
|  |   cached_network_image: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: cached_network_image | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "0.4.2" | ||||||
|   charcode: |   charcode: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -92,6 +99,13 @@ packages: | |||||||
|     description: flutter |     description: flutter | ||||||
|     source: sdk |     source: sdk | ||||||
|     version: "0.0.0" |     version: "0.0.0" | ||||||
|  |   flutter_cache_manager: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: flutter_cache_manager | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "0.1.2" | ||||||
|   flutter_launcher_icons: |   flutter_launcher_icons: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -251,6 +265,13 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.6.2" |     version: "1.6.2" | ||||||
|  |   path_provider: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: path_provider | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "0.4.1" | ||||||
|   petitparser: |   petitparser: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -375,6 +396,13 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.0.4" |     version: "1.0.4" | ||||||
|  |   synchronized: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: synchronized | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "1.5.3" | ||||||
|   term_glyph: |   term_glyph: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -403,6 +431,13 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.9.0+5" |     version: "0.9.0+5" | ||||||
|  |   uuid: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: uuid | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "1.0.3" | ||||||
|   vector_math: |   vector_math: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| name: hass_client | name: hass_client | ||||||
| description: Home Assistant Android Client | description: Home Assistant Android Client | ||||||
|  |  | ||||||
| version: 0.0.10-alpha1 | version: 0.1.0-alpha | ||||||
|  |  | ||||||
| environment: | environment: | ||||||
|   sdk: ">=2.0.0-dev.68.0 <3.0.0" |   sdk: ">=2.0.0-dev.68.0 <3.0.0" | ||||||
| @@ -14,6 +14,7 @@ dependencies: | |||||||
|   event_bus: ^1.0.1 |   event_bus: ^1.0.1 | ||||||
|   package_info: ^0.3.2 |   package_info: ^0.3.2 | ||||||
|   flutter_launcher_icons: ^0.6.1 |   flutter_launcher_icons: ^0.6.1 | ||||||
|  |   cached_network_image: ^0.4.1 | ||||||
|  |  | ||||||
|   # The following adds the Cupertino Icons font to your application. |   # The following adds the Cupertino Icons font to your application. | ||||||
|   # Use with the CupertinoIcons class for iOS style icons. |   # Use with the CupertinoIcons class for iOS style icons. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user