Compare commits
	
		
			8 Commits
		
	
	
		
			v0.0.12-al
			...
			0.1.1-alph
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | aa0d7ee8fd | ||
|  | 36d727b454 | ||
|  | 7cad0141c7 | ||
|  | 86738a0515 | ||
|  | 4dc211f2f7 | ||
|  | 739e659a81 | ||
|  | 2fb8d8e26b | ||
|  | 2db432ccd2 | 
| @@ -39,8 +39,8 @@ android { | |||||||
|         applicationId "com.keyboardcrumbs.haclient" |         applicationId "com.keyboardcrumbs.haclient" | ||||||
|         minSdkVersion 21 |         minSdkVersion 21 | ||||||
|         targetSdkVersion 27 |         targetSdkVersion 27 | ||||||
|         versionCode 17 |         versionCode 19 | ||||||
|         versionName "0.0.12-alpha" |         versionName "0.1.1-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; | ||||||
| @@ -46,10 +46,10 @@ class HassioDataModel { | |||||||
|  |  | ||||||
|   Future fetch() { |   Future fetch() { | ||||||
|     if ((_fetchCompleter != null) && (!_fetchCompleter.isCompleted)) { |     if ((_fetchCompleter != null) && (!_fetchCompleter.isCompleted)) { | ||||||
|       debugPrint("Previous fetch is not complited"); |       TheLogger.log("Warning","Previous fetch is not complited"); | ||||||
|     } else { |     } else { | ||||||
|       //TODO: Fetch timeout timer. Should be removed after #21 fix |       //TODO: Fetch timeout timer. Should be removed after #21 fix | ||||||
|       _fetchingTimer = Timer(Duration(seconds: 10), () { |       _fetchingTimer = Timer(Duration(seconds: 15), () { | ||||||
|         closeConnection(); |         closeConnection(); | ||||||
|         _fetchCompleter.completeError({"errorCode" : 1,"errorMessage": "Connection timeout"}); |         _fetchCompleter.completeError({"errorCode" : 1,"errorMessage": "Connection timeout"}); | ||||||
|       }); |       }); | ||||||
| @@ -73,10 +73,10 @@ class HassioDataModel { | |||||||
|   Future _reConnectSocket() { |   Future _reConnectSocket() { | ||||||
|     var _connectionCompleter = new Completer(); |     var _connectionCompleter = new Completer(); | ||||||
|     if ((_hassioChannel == null) || (_hassioChannel.closeCode != null)) { |     if ((_hassioChannel == null) || (_hassioChannel.closeCode != null)) { | ||||||
|       debugPrint("Socket connecting..."); |       TheLogger.log("Debug","Socket connecting..."); | ||||||
|       _hassioChannel = IOWebSocketChannel.connect(_hassioAPIEndpoint); |       _hassioChannel = IOWebSocketChannel.connect(_hassioAPIEndpoint); | ||||||
|       _hassioChannel.stream.handleError((e) { |       _hassioChannel.stream.handleError((e) { | ||||||
|         debugPrint("Unhandled socket error: ${e.toString()}"); |         TheLogger.log("Error","Unhandled socket error: ${e.toString()}"); | ||||||
|       }); |       }); | ||||||
|       _hassioChannel.stream.listen((message) => |       _hassioChannel.stream.listen((message) => | ||||||
|           _handleMessage(_connectionCompleter, message)); |           _handleMessage(_connectionCompleter, message)); | ||||||
| @@ -113,7 +113,7 @@ class HassioDataModel { | |||||||
|  |  | ||||||
|   _handleMessage(Completer connectionCompleter, String message) { |   _handleMessage(Completer connectionCompleter, String message) { | ||||||
|     var data = json.decode(message); |     var data = json.decode(message); | ||||||
|     debugPrint("[Received]Message type: ${data['type']}"); |     TheLogger.log("Debug","[Received] => Message type: ${data['type']}"); | ||||||
|     if (data["type"] == "auth_required") { |     if (data["type"] == "auth_required") { | ||||||
|       _sendMessageRaw('{"type": "auth","$_hassioAuthType": "$_hassioPassword"}'); |       _sendMessageRaw('{"type": "auth","$_hassioAuthType": "$_hassioPassword"}'); | ||||||
|     } else if (data["type"] == "auth_ok") { |     } else if (data["type"] == "auth_ok") { | ||||||
| @@ -128,36 +128,32 @@ 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"); |         TheLogger.log("Debug","Request id:$_currentMessageId was successful"); | ||||||
|       } else { |  | ||||||
|         debugPrint("Skipped message due to messageId:"); |  | ||||||
|         debugPrint(message); |  | ||||||
|       } |       } | ||||||
|     } else if (data["type"] == "event") { |     } else if (data["type"] == "event") { | ||||||
|       if ((data["event"] != null) && (data["event"]["event_type"] == "state_changed")) { |       if ((data["event"] != null) && (data["event"]["event_type"] == "state_changed")) { | ||||||
|         _handleEntityStateChange(data["event"]["data"]); |         _handleEntityStateChange(data["event"]["data"]); | ||||||
|       } else if (data["event"] != null) { |       } else if (data["event"] != null) { | ||||||
|         debugPrint("Unhandled event type: ${data["event"]["event_type"]}"); |         TheLogger.log("Warning","Unhandled event type: ${data["event"]["event_type"]}"); | ||||||
|       } else { |       } else { | ||||||
|         debugPrint("Event is null"); |         TheLogger.log("Error","Event is null: $message"); | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       debugPrint("Unknown message type"); |       TheLogger.log("Warning","Unknown message type: $message"); | ||||||
|       debugPrint(message); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   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; | ||||||
| @@ -166,7 +162,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; | ||||||
| @@ -175,28 +171,38 @@ 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(String message) { | ||||||
|     debugPrint("[Sent]$message"); |     if (message.indexOf('"type": "auth"') > 0) { | ||||||
|  |       TheLogger.log("Debug", "[Sending] ==> auth request"); | ||||||
|  |     } else { | ||||||
|  |       TheLogger.log("Debug", "[Sending] ==> $message"); | ||||||
|  |     } | ||||||
|     _hassioChannel.sink.add(message); |     _hassioChannel.sink.add(message); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void _handleEntityStateChange(Map eventData) { |   void _handleEntityStateChange(Map eventData) { | ||||||
|     String entityId = eventData["entity_id"]; |     TheLogger.log("Debug", "Parsing new state for ${eventData['entity_id']}"); | ||||||
|     if (_entitiesData[entityId] != null) { |     if (eventData["new_state"] == null) { | ||||||
|       _entitiesData[entityId].addAll(eventData["new_state"]); |       TheLogger.log("Error", "No new_state found"); | ||||||
|       eventBus.fire(new StateChangedEvent(eventData["entity_id"])); |  | ||||||
|     } else { |     } else { | ||||||
|       debugPrint("Unknown enity $entityId"); |       var parsedEntityData = _parseEntity(eventData["new_state"]); | ||||||
|  |       String entityId = parsedEntityData["entity_id"]; | ||||||
|  |       if (_entitiesData[entityId] == null) { | ||||||
|  |         _entitiesData[entityId] = parsedEntityData; | ||||||
|  |       } else { | ||||||
|  |         _entitiesData[entityId].addAll(parsedEntityData); | ||||||
|  |       } | ||||||
|  |       eventBus.fire(new StateChangedEvent(eventData["entity_id"])); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -214,118 +220,124 @@ class HassioDataModel { | |||||||
|       _servicesCompleter.completeError({"errorCode": 4, "errorMessage": response["error"]["message"]}); |       _servicesCompleter.completeError({"errorCode": 4, "errorMessage": response["error"]["message"]}); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     Map data = response["result"]; |     try { | ||||||
|     Map result = {}; |       Map data = response["result"]; | ||||||
|     debugPrint("Parsing ${data.length} Home Assistant service domains"); |       Map result = {}; | ||||||
|     data.forEach((domain, services){ |       TheLogger.log("Debug","Parsing ${data.length} Home Assistant service domains"); | ||||||
|       result[domain] = Map.from(services); |       data.forEach((domain, services) { | ||||||
|       services.forEach((serviceName, serviceData){ |         result[domain] = Map.from(services); | ||||||
|         if (_entitiesData["$domain.$serviceName"] != null) { |         services.forEach((serviceName, serviceData) { | ||||||
|           result[domain].remove(serviceName); |           if (_entitiesData["$domain.$serviceName"] != null) { | ||||||
|         } |             result[domain].remove(serviceName); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|       }); |       }); | ||||||
|     }); |       _servicesData = result; | ||||||
|     _servicesData = result; |       _servicesCompleter.complete(); | ||||||
|     _servicesCompleter.complete(); |     } catch (e) { | ||||||
|  |       //TODO hadle it properly | ||||||
|  |       TheLogger.log("Error","Error parsing services. But they are not used :-)"); | ||||||
|  |       _servicesCompleter.complete(); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void _parseEntities(response) async { |   void _parseEntities(response) async { | ||||||
|  |     _entitiesData.clear(); | ||||||
|  |     _uiStructure.clear(); | ||||||
|     if (response["success"] == false) { |     if (response["success"] == false) { | ||||||
|       _statesCompleter.completeError({"errorCode": 3, "errorMessage": response["error"]["message"]}); |       _statesCompleter.completeError({"errorCode": 3, "errorMessage": response["error"]["message"]}); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     List data = response["result"]; |     List data = response["result"]; | ||||||
|     debugPrint("Parsing ${data.length} Home Assistant entities"); |     TheLogger.log("Debug","Parsing ${data.length} Home Assistant entities"); | ||||||
|     List<String> uiGroups = []; |     List<String> uiGroups = []; | ||||||
|     data.forEach((entity) { |     data.forEach((entity) { | ||||||
|       var composedEntity = Map.from(entity); |       try { | ||||||
|       String entityDomain = entity["entity_id"].split(".")[0]; |         var composedEntity = _parseEntity(entity); | ||||||
|       String entityId = entity["entity_id"]; |  | ||||||
|  |  | ||||||
|       composedEntity["display_name"] = "${entity["attributes"]!=null ? entity["attributes"]["friendly_name"] ?? entity["attributes"]["name"] : "_"}"; |         if (composedEntity["attributes"] != null) { | ||||||
|       composedEntity["domain"] = entityDomain; |           if ((composedEntity["domain"] == "group") && | ||||||
|  |               (composedEntity["attributes"]["view"] == true)) { | ||||||
|       if ((entityDomain == "automation") || (entityDomain == "switch") || (entityDomain == "light")) { |             uiGroups.add(composedEntity["entity_id"]); | ||||||
|         composedEntity["actionType"] = "switch"; |           } | ||||||
|       } else if ((entityDomain == "script") || (entityDomain == "scene")) { |  | ||||||
|         composedEntity["actionType"] = "statelessIcon"; |  | ||||||
|       } else { |  | ||||||
|         composedEntity["actionType"] = "stateText"; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (composedEntity["attributes"] != null) { |  | ||||||
|         if ((entityDomain == "group")&&(composedEntity["attributes"]["view"] == true)) { |  | ||||||
|           uiGroups.add(entityId); |  | ||||||
|         } |         } | ||||||
|  |         _entitiesData[entity["entity_id"]] = composedEntity; | ||||||
|  |       } catch (error) { | ||||||
|  |         TheLogger.log("Error","Error parsing entity: ${entity['entity_id']}"); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |  | ||||||
|       if (entityDomain == "group") { |  | ||||||
|         if ((composedEntity["attributes"] != null) && |  | ||||||
|             (composedEntity["attributes"]["view"] == true)) { |  | ||||||
|  |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       _entitiesData[entityId] = Map.from(composedEntity); |  | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     //Gethering information for UI |     //Gethering information for UI | ||||||
|     debugPrint("Gethering views"); |     TheLogger.log("Debug","Gethering views"); | ||||||
|     int viewCounter = 0; |     int viewCounter = 0; | ||||||
|     uiGroups.forEach((viewId) { //Each view |     uiGroups.forEach((viewId) { //Each view | ||||||
|       viewCounter +=1; |       try { | ||||||
|       var viewGroup = _entitiesData[viewId]; |         Map viewGroupStructure = {}; | ||||||
|       Map viewGroupStructure = {}; |         viewCounter += 1; | ||||||
|       if (viewGroup != null) { |         var viewGroup = _entitiesData[viewId]; | ||||||
|         viewGroupStructure["standalone"] = {}; |         if (viewGroup != null) { | ||||||
|         viewGroupStructure["groups"] = {}; |           viewGroupStructure["groups"] = {}; | ||||||
|         viewGroupStructure["state"] = "on"; |           viewGroupStructure["state"] = "on"; | ||||||
|         viewGroupStructure["entity_id"] = viewGroup["entity_id"]; |           viewGroupStructure["entity_id"] = viewGroup["entity_id"]; | ||||||
|         viewGroupStructure["badges"] = {"children": []}; |           viewGroupStructure["badges"] = {"children": []}; | ||||||
|         viewGroupStructure["attributes"] = viewGroup["attributes"] != null ? {"icon": viewGroup["attributes"]["icon"]} : {"icon": "none"}; |           viewGroupStructure["attributes"] = viewGroup["attributes"] != null ? { | ||||||
|  |             "icon": viewGroup["attributes"]["icon"] | ||||||
|  |           } : {"icon": "none"}; | ||||||
|  |  | ||||||
|  |  | ||||||
|         viewGroup["attributes"]["entity_id"].forEach((entityId) { //Each entity or group in view |           viewGroup["attributes"]["entity_id"].forEach(( | ||||||
|           Map newGroup = {}; |               entityId) { //Each entity or group in view | ||||||
|           String domain = _entitiesData[entityId]["domain"]; |             Map newGroup = {}; | ||||||
|           if (domain != "group") { |             String domain = _entitiesData[entityId]["domain"]; | ||||||
|             if (_topBadgeDomains.contains(domain)) { |             if (domain != "group") { | ||||||
|               viewGroupStructure["badges"]["children"].add(entityId); |               if (_topBadgeDomains.contains(domain)) { | ||||||
|             } else { |                 viewGroupStructure["badges"]["children"].add(entityId); | ||||||
|               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 { |               } else { | ||||||
|                 viewGroupStructure["groups"]["$autoGroupID"]["children"].add( |                 String autoGroupID = "$domain.$domain$viewCounter"; | ||||||
|                     entityId); |                 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["friendly_name"] = | ||||||
|  |               (_entitiesData[entityId]['attributes'] != null) | ||||||
|  |                   ? (_entitiesData[entityId]['attributes']['friendly_name'] ?? | ||||||
|  |                   "") | ||||||
|  |                   : ""; | ||||||
|  |               newGroup["children"] = List<String>(); | ||||||
|  |               _entitiesData[entityId]["attributes"]["entity_id"].forEach(( | ||||||
|  |                   groupedEntityId) { | ||||||
|  |                 newGroup["children"].add(groupedEntityId); | ||||||
|  |               }); | ||||||
|  |               viewGroupStructure["groups"]["$entityId"] = Map.from(newGroup); | ||||||
|             } |             } | ||||||
|           } else { |           }); | ||||||
|             newGroup["entity_id"] = entityId; |         } | ||||||
|             newGroup["friendly_name"] = |         _uiStructure[viewId.split(".")[1]] = viewGroupStructure; | ||||||
|             (_entitiesData[entityId]['attributes'] != null) |       } catch (error) { | ||||||
|                 ? (_entitiesData[entityId]['attributes']['friendly_name'] ?? "") |         TheLogger.log("Error","Error parsing view: $viewId"); | ||||||
|                 : ""; |  | ||||||
|             newGroup["children"] = List<String>(); |  | ||||||
|             _entitiesData[entityId]["attributes"]["entity_id"].forEach(( |  | ||||||
|                 groupedEntityId) { |  | ||||||
|               newGroup["children"].add(groupedEntityId); |  | ||||||
|             }); |  | ||||||
|             viewGroupStructure["groups"]["$entityId"] = Map.from(newGroup); |  | ||||||
|           } |  | ||||||
|         }); |  | ||||||
|       _uiStructure[viewId.split(".")[1]] = viewGroupStructure; |  | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|     _statesCompleter.complete(); |     _statesCompleter.complete(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   Map _parseEntity(rawData) { | ||||||
|  |     var composedEntity = Map.from(rawData); | ||||||
|  |     String entityDomain = rawData["entity_id"].split(".")[0]; | ||||||
|  |     composedEntity["display_name"] = "${rawData["attributes"]!=null ? rawData["attributes"]["friendly_name"] ?? rawData["attributes"]["name"] : "_"}"; | ||||||
|  |     composedEntity["domain"] = entityDomain; | ||||||
|  |     return composedEntity; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   Future callService(String domain, String service, String entity_id) { |   Future callService(String domain, String service, String entity_id) { | ||||||
|     var sendCompleter = Completer(); |     var sendCompleter = Completer(); | ||||||
|     //TODO: Send service call timeout timer. Should be removed after #21 fix |     //TODO: Send service call timeout timer. Should be removed after #21 fix | ||||||
| @@ -334,7 +346,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){ | ||||||
|   | |||||||
							
								
								
									
										49
									
								
								lib/logPage.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								lib/logPage.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | part of 'main.dart'; | ||||||
|  |  | ||||||
|  | class LogViewPage extends StatefulWidget { | ||||||
|  |   LogViewPage({Key key, this.title}) : super(key: key); | ||||||
|  |  | ||||||
|  |   final String title; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   _LogViewPageState createState() => new _LogViewPageState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _LogViewPageState extends State<LogViewPage> { | ||||||
|  |   String _hassioDomain = ""; | ||||||
|  |   String _hassioPort = "8123"; | ||||||
|  |   String _hassioPassword = ""; | ||||||
|  |   String _socketProtocol = "wss"; | ||||||
|  |   String _authType = "access_token"; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void initState() { | ||||||
|  |     super.initState(); | ||||||
|  |     _loadLog(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   _loadLog() async { | ||||||
|  |     // | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return new Scaffold( | ||||||
|  |       appBar: new AppBar( | ||||||
|  |         leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){ | ||||||
|  |           Navigator.pop(context); | ||||||
|  |         }), | ||||||
|  |         // Here we take the value from the MyHomePage object that was created by | ||||||
|  |         // the App.build method, and use it to set our appbar title. | ||||||
|  |         title: new Text(widget.title), | ||||||
|  |       ), | ||||||
|  |       body: TextField( | ||||||
|  |         maxLines: null, | ||||||
|  |  | ||||||
|  |         controller: TextEditingController( | ||||||
|  |             text: TheLogger.getLog() | ||||||
|  |         ), | ||||||
|  |       ) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										149
									
								
								lib/main.dart
									
									
									
									
									
								
							
							
						
						
									
										149
									
								
								lib/main.dart
									
									
									
									
									
								
							| @@ -9,15 +9,46 @@ 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'; | import 'package:cached_network_image/cached_network_image.dart'; | ||||||
|  |  | ||||||
| part 'settings.dart'; | part 'settingsPage.dart'; | ||||||
| part 'data_model.dart'; | part 'data_model.dart'; | ||||||
|  | part 'logPage.dart'; | ||||||
|  |  | ||||||
| EventBus eventBus = new EventBus(); | EventBus eventBus = new EventBus(); | ||||||
| const String appName = "HA Client"; | const String appName = "HA Client"; | ||||||
| const appVersion = "0.0.12-alpha"; | const appVersion = "0.1.1-alpha"; | ||||||
|  |  | ||||||
| String homeAssistantWebHost; | String homeAssistantWebHost; | ||||||
|  |  | ||||||
|  | class TheLogger { | ||||||
|  |  | ||||||
|  |   static List<String> _log = []; | ||||||
|  |  | ||||||
|  |   static String getLog() { | ||||||
|  |     String res = ''; | ||||||
|  |     _log.forEach((line) { | ||||||
|  |       res += "$line\n\n"; | ||||||
|  |     }); | ||||||
|  |     return res; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static bool get isInDebugMode { | ||||||
|  |     bool inDebugMode = false; | ||||||
|  |  | ||||||
|  |     assert(inDebugMode = true); | ||||||
|  |  | ||||||
|  |     return inDebugMode; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static void log(String level, String message) { | ||||||
|  |     debugPrint('$message'); | ||||||
|  |     _log.add("[$level] :  $message"); | ||||||
|  |     if (_log.length > 50) { | ||||||
|  |       _log.removeAt(0); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
| void main() => runApp(new HassClientApp()); | void main() => runApp(new HassClientApp()); | ||||||
|  |  | ||||||
| class HassClientApp extends StatelessWidget { | class HassClientApp extends StatelessWidget { | ||||||
| @@ -32,7 +63,8 @@ class HassClientApp extends StatelessWidget { | |||||||
|       initialRoute: "/", |       initialRoute: "/", | ||||||
|       routes: { |       routes: { | ||||||
|         "/": (context) => MainPage(title: 'Hass Client'), |         "/": (context) => MainPage(title: 'Hass Client'), | ||||||
|         "/connection-settings": (context) => ConnectionSettingsPage(title: "Connection Settings") |         "/connection-settings": (context) => ConnectionSettingsPage(title: "Connection Settings"), | ||||||
|  |         "/log-view": (context) => LogViewPage(title: "Log") | ||||||
|       }, |       }, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @@ -59,20 +91,24 @@ 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() { | ||||||
|     super.initState(); |     super.initState(); | ||||||
|     WidgetsBinding.instance.addObserver(this); |     WidgetsBinding.instance.addObserver(this); | ||||||
|     _settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) { |     _settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) { | ||||||
|       debugPrint("Settings change event: reconnect=${event.reconnect}"); |       TheLogger.log("Debug","Settings change event: reconnect=${event.reconnect}"); | ||||||
|       setState(() { |       setState(() { | ||||||
|         _errorCodeToBeShown = 0; |         _errorCodeToBeShown = 0; | ||||||
|       }); |       }); | ||||||
| @@ -83,7 +119,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void didChangeAppLifecycleState(AppLifecycleState state) { |   void didChangeAppLifecycleState(AppLifecycleState state) { | ||||||
|     debugPrint("$state"); |     TheLogger.log("Debug","$state"); | ||||||
|     if (state == AppLifecycleState.resumed) { |     if (state == AppLifecycleState.resumed) { | ||||||
|       _refreshData(); |       _refreshData(); | ||||||
|     } |     } | ||||||
| @@ -114,7 +150,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|     _refreshData(); |     _refreshData(); | ||||||
|     if (_stateSubscription != null) _stateSubscription.cancel(); |     if (_stateSubscription != null) _stateSubscription.cancel(); | ||||||
|     _stateSubscription = eventBus.on<StateChangedEvent>().listen((event) { |     _stateSubscription = eventBus.on<StateChangedEvent>().listen((event) { | ||||||
|       debugPrint("State change event for ${event.entityId}"); |  | ||||||
|       setState(() { |       setState(() { | ||||||
|         _entitiesData = _dataModel.entities; |         _entitiesData = _dataModel.entities; | ||||||
|       }); |       }); | ||||||
| @@ -208,11 +243,9 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|     List<Widget> result = []; |     List<Widget> result = []; | ||||||
|     ids.forEach((entityId) { |     ids.forEach((entityId) { | ||||||
|       var data = _entitiesData[entityId]; |       var data = _entitiesData[entityId]; | ||||||
|       if (data == null) { |       if (data != null) { | ||||||
|         debugPrint("Hiding unknown entity from badges: $entityId"); |  | ||||||
|       } else { |  | ||||||
|         result.add( |         result.add( | ||||||
|         _buildSingleBadge(data) |           _buildSingleBadge(data) | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
| @@ -220,11 +253,21 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   Widget _buildSingleBadge(data) { |   Widget _buildSingleBadge(data) { | ||||||
|  |     double iconSize = 26.0; | ||||||
|     Widget badgeIcon; |     Widget badgeIcon; | ||||||
|     String badgeTextValue; |     String badgeTextValue; | ||||||
|  |     Color iconColor = _badgeColors[data["domain"]] ?? _badgeColors["default"]; | ||||||
|     switch (data["domain"]) { |     switch (data["domain"]) { | ||||||
|       case "sun": { |       case "sun": { | ||||||
|         badgeIcon = data["state"] == "below_horizon" ? Icon(MaterialDesignIcons.createIconDataFromIconCode(0xf0dc)) : Icon(MaterialDesignIcons.createIconDataFromIconCode(0xf5a8)); |         badgeIcon = data["state"] == "below_horizon" ? | ||||||
|  |           Icon( | ||||||
|  |             MaterialDesignIcons.createIconDataFromIconCode(0xf0dc), | ||||||
|  |             size: iconSize, | ||||||
|  |           ) : | ||||||
|  |           Icon( | ||||||
|  |             MaterialDesignIcons.createIconDataFromIconCode(0xf5a8), | ||||||
|  |             size: iconSize, | ||||||
|  |           ); | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
|       case "sensor": { |       case "sensor": { | ||||||
| @@ -241,12 +284,12 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
|       case "device_tracker": { |       case "device_tracker": { | ||||||
|         badgeIcon = MaterialDesignIcons.createIconFromEntityData(data, 50.0,Colors.black); |         badgeIcon = MaterialDesignIcons.createIconFromEntityData(data, iconSize,Colors.black); | ||||||
|         badgeTextValue = data["state"]; |         badgeTextValue = data["state"]; | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
|       default: { |       default: { | ||||||
|        badgeIcon = MaterialDesignIcons.createIconFromEntityData(data, 50.0,Colors.black); |        badgeIcon = MaterialDesignIcons.createIconFromEntityData(data, iconSize,Colors.black); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     Widget badgeText; |     Widget badgeText; | ||||||
| @@ -261,7 +304,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|           decoration: new BoxDecoration( |           decoration: new BoxDecoration( | ||||||
|             // Circle shape |             // Circle shape | ||||||
|             //shape: BoxShape.circle, |             //shape: BoxShape.circle, | ||||||
|             color: Colors.redAccent, |             color: iconColor, | ||||||
|             borderRadius: BorderRadius.circular(9.0), |             borderRadius: BorderRadius.circular(9.0), | ||||||
|           ) |           ) | ||||||
|       ); |       ); | ||||||
| @@ -279,7 +322,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|             // The border you want |             // The border you want | ||||||
|             border: new Border.all( |             border: new Border.all( | ||||||
|               width: 2.0, |               width: 2.0, | ||||||
|               color: Colors.redAccent, |               color: iconColor, | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|           child: Stack( |           child: Stack( | ||||||
| @@ -349,13 +392,11 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|     List<Widget> entities = []; |     List<Widget> entities = []; | ||||||
|     ids.forEach((id) { |     ids.forEach((id) { | ||||||
|       var data = _entitiesData[id]; |       var data = _entitiesData[id]; | ||||||
|       if (data == null) { |       if (data != null) { | ||||||
|         debugPrint("Hiding unknown entity from card: $id"); |  | ||||||
|       } else { |  | ||||||
|         entities.add(new ListTile( |         entities.add(new ListTile( | ||||||
|           leading: MaterialDesignIcons.createIconFromEntityData(data, 28.0, _stateIconColors[data["state"]] ?? Colors.blueGrey), |           leading: MaterialDesignIcons.createIconFromEntityData(data, 28.0, _stateIconColors[data["state"]] ?? Colors.blueGrey), | ||||||
|           //subtitle: Text("${data['entity_id']}"), |           //subtitle: Text("${data['entity_id']}"), | ||||||
|           trailing: _buildEntityStateWidget(data), |           trailing: _buildEntityActionWidget(data), | ||||||
|           title: Text( |           title: Text( | ||||||
|             "${data["display_name"]}", |             "${data["display_name"]}", | ||||||
|             overflow: TextOverflow.fade, |             overflow: TextOverflow.fade, | ||||||
| @@ -367,22 +408,29 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|     return entities; |     return entities; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Widget _buildEntityStateWidget(data) { |   Widget _buildEntityActionWidget(data) { | ||||||
|     String entityId = data["entity_id"]; |     String entityId = data["entity_id"]; | ||||||
|     Widget result; |     Widget result; | ||||||
|     if (data["actionType"] == "switch") { |     switch (data["domain"]) { | ||||||
|       result = Switch( |       case "automation": | ||||||
|         value: (data["state"] == "on"), |       case "switch": | ||||||
|         onChanged: ((state) { |       case "light": { | ||||||
|           _callService( |         result = Switch( | ||||||
|               data["domain"], state ? "turn_on" : "turn_off", entityId); |           value: (data["state"] == "on"), | ||||||
|           setState(() { |           onChanged: ((state) { | ||||||
|             _entitiesData[entityId]["state"] = state ? "on" : "off"; |             _callService( | ||||||
|           }); |                 data["domain"], state ? "turn_on" : "turn_off", entityId); | ||||||
|         }), |             setState(() { | ||||||
|       ); |               _entitiesData[entityId]["state"] = state ? "on" : "off"; | ||||||
|     } else if (data["actionType"] == "statelessIcon") { |             }); | ||||||
|       result = SizedBox( |           }), | ||||||
|  |         ); | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       case "script": | ||||||
|  |       case "scene": { | ||||||
|  |         result = SizedBox( | ||||||
|           width: 60.0, |           width: 60.0, | ||||||
|           child: FlatButton( |           child: FlatButton( | ||||||
|             onPressed: (() { |             onPressed: (() { | ||||||
| @@ -393,17 +441,25 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|               textAlign: TextAlign.right, |               textAlign: TextAlign.right, | ||||||
|               style: new TextStyle(fontSize: 16.0, color: Colors.blue), |               style: new TextStyle(fontSize: 16.0, color: Colors.blue), | ||||||
|             ), |             ), | ||||||
|           )); |           ) | ||||||
|     } else { |         ); | ||||||
|       result = Padding( |         break; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       default: { | ||||||
|  |         result = Padding( | ||||||
|           padding: EdgeInsets.fromLTRB(0.0, 0.0, 16.0, 0.0), |           padding: EdgeInsets.fromLTRB(0.0, 0.0, 16.0, 0.0), | ||||||
|           child: Text( |           child: Text( | ||||||
|               "${data["state"]}${(data["attributes"] != null && data["attributes"]["unit_of_measurement"] != null) ? data["attributes"]["unit_of_measurement"] : ''}", |             "${data["state"]}${(data["attributes"] != null && data["attributes"]["unit_of_measurement"] != null) ? data["attributes"]["unit_of_measurement"] : ''}", | ||||||
|               textAlign: TextAlign.right, |             textAlign: TextAlign.right, | ||||||
|               style: new TextStyle( |             style: new TextStyle( | ||||||
|                 fontSize: 16.0, |               fontSize: 16.0, | ||||||
|               ))); |             ) | ||||||
|  |           ) | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /*return SizedBox( |     /*return SizedBox( | ||||||
|       width: 60.0, |       width: 60.0, | ||||||
|       // height: double.infinity, |       // height: double.infinity, | ||||||
| @@ -458,6 +514,13 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver { | |||||||
|               Navigator.pushNamed(context, '/connection-settings'); |               Navigator.pushNamed(context, '/connection-settings'); | ||||||
|             }, |             }, | ||||||
|           ), |           ), | ||||||
|  |           new ListTile( | ||||||
|  |             leading: Icon(Icons.insert_drive_file), | ||||||
|  |             title: Text("Log"), | ||||||
|  |             onTap: () { | ||||||
|  |               Navigator.pushNamed(context, '/log-view'); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|           new AboutListTile( |           new AboutListTile( | ||||||
|             applicationName: appName, |             applicationName: appName, | ||||||
|             applicationVersion: appVersion, |             applicationVersion: appVersion, | ||||||
|   | |||||||
| @@ -35,6 +35,9 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _saveSettings() async { |   _saveSettings() async { | ||||||
|  |     if (_hassioDomain.indexOf("http") == 0 && _hassioDomain.indexOf("//") > 0) { | ||||||
|  |       _hassioDomain = _hassioDomain.split("//")[1]; | ||||||
|  |     } | ||||||
|     SharedPreferences prefs = await SharedPreferences.getInstance(); |     SharedPreferences prefs = await SharedPreferences.getInstance(); | ||||||
|     prefs.setString("hassio-domain", _hassioDomain); |     prefs.setString("hassio-domain", _hassioDomain); | ||||||
|     prefs.setString("hassio-port", _hassioPort); |     prefs.setString("hassio-port", _hassioPort); | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| name: hass_client | name: hass_client | ||||||
| description: Home Assistant Android Client | description: Home Assistant Android Client | ||||||
|  |  | ||||||
| version: 0.0.12-alpha | version: 0.1.1-alpha | ||||||
|  |  | ||||||
| environment: | environment: | ||||||
|   sdk: ">=2.0.0-dev.68.0 <3.0.0" |   sdk: ">=2.0.0-dev.68.0 <3.0.0" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user