diff --git a/lib/home_assistant.class.dart b/lib/home_assistant.class.dart index 73d418f..22aa5b8 100644 --- a/lib/home_assistant.class.dart +++ b/lib/home_assistant.class.dart @@ -4,6 +4,7 @@ class HomeAssistant { String _webSocketAPIEndpoint; String _password; String _authType; + bool _useLovelace; IOWebSocketChannel _hassioChannel; SendMessageQueue _messageQueue; @@ -14,14 +15,18 @@ class HomeAssistant { int _subscriptionMessageId = 0; int _configMessageId = 0; int _userInfoMessageId = 0; + int _lovelaceMessageId = 0; EntityCollection entities; - GroupBasedUI ui; + HomeAssistantUI ui; Map _instanceConfig = {}; String _userName; + Map _rawLovelaceData; + Completer _fetchCompleter; Completer _statesCompleter; Completer _servicesCompleter; + Completer _lovelaceCompleter; Completer _configCompleter; Completer _connectionCompleter; Completer _userInfoCompleter; @@ -45,10 +50,12 @@ class HomeAssistant { _messageQueue = SendMessageQueue(messageExpirationTime); } - void updateConnectionSettings(String url, String password, String authType) { + void updateSettings(String url, String password, String authType, bool useLovelace) { _webSocketAPIEndpoint = url; _password = password; _authType = authType; + _useLovelace = useLovelace; + TheLogger.log("Debug", "Use lovelace is $_useLovelace"); } Future fetch() { @@ -150,11 +157,15 @@ class HomeAssistant { _getData() async { List futures = []; futures.add(_getStates()); + if (_useLovelace) { + futures.add(_getLovelace()); + } futures.add(_getConfig()); futures.add(_getServices()); futures.add(_getUserInfo()); try { await Future.wait(futures); + _createUI(); _completeFetching(null); } catch (error) { _completeFetching(error); @@ -202,6 +213,8 @@ class HomeAssistant { _parseConfig(data); } else if (data["id"] == _statesMessageId) { _parseEntities(data); + } else if (data["id"] == _lovelaceMessageId) { + _handleLovelace(data); } else if (data["id"] == _servicesMessageId) { _parseServices(data); } else if (data["id"] == _userInfoMessageId) { @@ -247,6 +260,15 @@ class HomeAssistant { return _statesCompleter.future; } + Future _getLovelace() { + _lovelaceCompleter = new Completer(); + _incrementMessageId(); + _lovelaceMessageId = _currentMessageId; + _sendMessageRaw('{"id": $_lovelaceMessageId, "type": "lovelace/config"}', false); + + return _lovelaceCompleter.future; + } + Future _getUserInfo() { _userInfoCompleter = new Completer(); _incrementMessageId(); @@ -336,28 +358,66 @@ class HomeAssistant { void _parseServices(response) { _servicesCompleter.complete(); - /*if (response["success"] == false) { - _servicesCompleter.completeError({"errorCode": 4, "errorMessage": response["error"]["message"]}); - return; + } + + void _handleLovelace(response) { + if (response["success"] == true) { + _rawLovelaceData = response["result"]; + } else { + _rawLovelaceData = null; } - try { - Map data = response["result"]; - Map result = {}; - TheLogger.log("Debug","Parsing ${data.length} Home Assistant service domains"); - data.forEach((domain, services) { - result[domain] = Map.from(services); - services.forEach((serviceName, serviceData) { - if (_entitiesData.isExist("$domain.$serviceName")) { - result[domain].remove(serviceName); + _lovelaceCompleter.complete(); + } + + void _parseLovelace() { + ui = HomeAssistantUI(); + TheLogger.log("debug","Parsing lovelace config"); + TheLogger.log("debug","--Title: ${_rawLovelaceData["title"]}"); + int viewCounter = 0; + TheLogger.log("debug","--Views count: ${_rawLovelaceData['views'].length}"); + _rawLovelaceData["views"].forEach((rawView){ + TheLogger.log("debug","----view id: ${rawView['id']}"); + HAView view = HAView( + count: viewCounter, + id: rawView['id'], + name: rawView['title'], + iconName: rawView['icon'] + ); + view.cards.addAll(_createLovelaceCards(rawView["cards"] ?? [])); + ui.views.add( + view + ); + viewCounter += 1; + }); + } + + List _createLovelaceCards(List rawCards) { + List result = []; + rawCards.forEach((rawCard){ + if (rawCard["cards"] != null) { + TheLogger.log("debug","------card: ${rawCard['type']} has child cards"); + result.addAll(_createLovelaceCards(rawCard["cards"])); + } else { + TheLogger.log("debug","------card: ${rawCard['type']}"); + HACard card = HACard( + id: "card", + name: rawCard["title"] + ); + rawCard["entities"]?.forEach((rawEntity) { + if (rawEntity is String) { + if (entities.isExist(rawEntity)) { + card.entities.add(entities.get(rawEntity)); + } + } else { + if (entities.isExist(rawEntity["entity"])) { + card.entities.add(entities.get(rawEntity["entity"])); + } } }); - }); - _servicesData = result; - _servicesCompleter.complete(); - } catch (e) { - TheLogger.log("Error","Error parsing services. But they are not used :-)"); - _servicesCompleter.complete(); - }*/ + result.add(card); + } + }); + return result; } void _parseEntities(response) async { @@ -366,80 +426,46 @@ class HomeAssistant { return; } entities.parse(response["result"]); - - - ui = GroupBasedUI(); - int viewCounter = 0; - //TODO add default_view - if (!entities.hasDefaultView) { - TheLogger.log("Debug","--Default view"); - HACView view = HACView( - count: viewCounter, - id: "group.default_view", - name: "Home" - ); - _createView(view, entities.filterEntitiesForDefaultView(), viewCounter); - ui.views.add( - view - ); - viewCounter+=1; - } - entities.viewEntities.forEach((viewEntity) { - TheLogger.log("Debug","--View: ${viewEntity.entityId}"); - HACView view = HACView( - count: viewCounter, - id: viewEntity.entityId, - name: viewEntity.displayName - ); - view.linkedEntity = viewEntity; - _createView(view, viewEntity.childEntities, viewCounter); - ui.views.add( - view - ); - viewCounter += 1; - }); - - _statesCompleter.complete(); } - void _createView(HACView view, List childEntities, int viewCounter) { - List autoGeneratedCards = []; - childEntities.forEach((entity) { - if (entity.isBadge) { - view.badges.add(entity); - TheLogger.log("Debug","----Badge: ${entity.entityId}"); - } else { - if (!entity.isGroup) { - String groupIdToAdd = "${entity.domain}.${entity.domain}$viewCounter"; - if (autoGeneratedCards.every((HACCard card) => card.id != groupIdToAdd )) { - HACCard card = HACCard( - id: groupIdToAdd, - name: entity.domain - ); - TheLogger.log("Debug","----Creating card: $groupIdToAdd"); - card.entities.add(entity); - autoGeneratedCards.add(card); - } else { - autoGeneratedCards.firstWhere((card) => card.id == groupIdToAdd).entities.add(entity); - } - } else { - TheLogger.log("Debug","----Card: ${entity.entityId}"); - HACCard card = HACCard( - name: entity.displayName, - id: entity.entityId, - linkedEntity: entity - ); - card.entities.addAll(entity.childEntities); - view.cards.add(card); - } + void _createUI() { + if ((_useLovelace) && (_rawLovelaceData != null)) { + _parseLovelace(); + } else { + ui = HomeAssistantUI(); + int viewCounter = 0; + if (!entities.hasDefaultView) { + TheLogger.log("Debug", "--Default view"); + HAView view = HAView( + count: viewCounter, + id: "group.default_view", + name: "Home", + childEntities: entities.filterEntitiesForDefaultView() + ); + ui.views.add( + view + ); + viewCounter += 1; } - }); - view.cards.addAll(autoGeneratedCards); + entities.viewEntities.forEach((viewEntity) { + TheLogger.log("Debug", "--View: ${viewEntity.entityId}"); + HAView view = HAView( + count: viewCounter, + id: viewEntity.entityId, + name: viewEntity.displayName, + childEntities: viewEntity.childEntities + ); + view.linkedEntity = viewEntity; + ui.views.add( + view + ); + viewCounter += 1; + }); + } } - Widget buildViews(BuildContext context) { - //return _viewBuilder.buildWidget(context); + Widget buildViews(BuildContext context, bool lovelace) { return ui.build(context); } diff --git a/lib/main.dart b/lib/main.dart index 2f07ab8..143e69d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -24,7 +24,7 @@ part 'entity.page.dart'; part 'utils.class.dart'; part 'mdi.class.dart'; part 'entity_collection.class.dart'; -part 'group_based_ui.dart'; +part 'ui.dart'; EventBus eventBus = new EventBus(); const String appName = "HA Client"; @@ -62,7 +62,7 @@ class HAClientApp extends StatelessWidget { initialRoute: "/", routes: { "/": (context) => MainPage(title: 'HA Client'), - "/connection-settings": (context) => ConnectionSettingsPage(title: "Connection Settings"), + "/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"), "/log-view": (context) => LogViewPage(title: "Log") }, ); @@ -96,6 +96,7 @@ class _MainPageState extends State with WidgetsBindingObserver { int _isLoading = 1; bool _settingsLoaded = false; bool _accountMenuExpanded = false; + bool _useLovelaceUI; @override void initState() { @@ -145,6 +146,7 @@ class _MainPageState extends State with WidgetsBindingObserver { homeAssistantWebHost = "${prefs.getString('hassio-res-protocol')}://$domain:$port"; _password = prefs.getString('hassio-password'); _authType = prefs.getString('hassio-auth-type'); + _useLovelaceUI = prefs.getBool('use-lovelace') ?? false; if ((domain == null) || (port == null) || (_password == null) || (domain.length == 0) || (port.length == 0) || (_password.length == 0)) { throw("Check connection settings"); @@ -195,7 +197,7 @@ class _MainPageState extends State with WidgetsBindingObserver { } _refreshData() async { - _homeAssistant.updateConnectionSettings(_webSocketApiEndpoint, _password, _authType); + _homeAssistant.updateSettings(_webSocketApiEndpoint, _password, _authType, _useLovelaceUI); setState(() { _hideErrorSnackBar(); _isLoading = 1; @@ -239,33 +241,36 @@ class _MainPageState extends State with WidgetsBindingObserver { List buildUIViewTabs() { List result = []; - if (_homeAssistant.ui.views.isNotEmpty) { - _homeAssistant.ui.views.forEach((HACView view) { - if (view.linkedEntity == null) { - result.add( - Tab( - icon: - Icon( - MaterialDesignIcons.createIconDataFromIconName("mdi:home-assistant"), - size: 24.0, - ) - ) - ); - } else { - result.add( - Tab( - icon: MaterialDesignIcons.createIconWidgetFromEntityData( - view.linkedEntity, 24.0, null) ?? - Icon( - MaterialDesignIcons.createIconDataFromIconName( - "mdi:home-assistant"), - size: 24.0, - ) - ) - ); - } - }); - } + + if (_homeAssistant.ui.views.isNotEmpty) { + _homeAssistant.ui.views.forEach((HAView view) { + if (view.linkedEntity == null) { + result.add( + Tab( + icon: + Icon( + MaterialDesignIcons.createIconDataFromIconName( + view.iconName ?? "mdi:home-assistant"), + size: 24.0, + ) + ) + ); + } else { + result.add( + Tab( + icon: MaterialDesignIcons.createIconWidgetFromEntityData( + view.linkedEntity, 24.0, null) ?? + Icon( + MaterialDesignIcons.createIconDataFromIconName( + "mdi:home-assistant"), + size: 24.0, + ) + ) + ); + } + }); + } + return result; } @@ -319,7 +324,7 @@ class _MainPageState extends State with WidgetsBindingObserver { menuItems.addAll([ ListTile( leading: Icon(Icons.settings), - title: Text("Connection settings"), + title: Text("Settings"), onTap: () { Navigator.of(context).pop(); Navigator.of(context).pushNamed('/connection-settings'); @@ -452,6 +457,7 @@ class _MainPageState extends State with WidgetsBindingObserver { }); }, ), + primary: true, bottom: empty ? null : TabBar( tabs: buildUIViewTabs(), isScrollable: true, @@ -472,7 +478,7 @@ class _MainPageState extends State with WidgetsBindingObserver { ), ) : - _homeAssistant.buildViews(context) + _homeAssistant.buildViews(context, _useLovelaceUI) ); } diff --git a/lib/settings.page.dart b/lib/settings.page.dart index 1738bea..b0d14a5 100644 --- a/lib/settings.page.dart +++ b/lib/settings.page.dart @@ -20,6 +20,8 @@ class _ConnectionSettingsPageState extends State { String _newSocketProtocol = "wss"; String _authType = "access_token"; String _newAuthType = "access_token"; + bool _useLovelace = false; + bool _newUseLovelace = false; bool _edited = false; FocusNode _domainFocusNode; FocusNode _portFocusNode; @@ -46,6 +48,11 @@ class _ConnectionSettingsPageState extends State { _hassioPassword = _newHassioPassword = prefs.getString("hassio-password") ?? ""; _socketProtocol = _newSocketProtocol = prefs.getString("hassio-protocol") ?? 'wss'; _authType = _newAuthType = prefs.getString("hassio-auth-type") ?? 'access_token'; + try { + _useLovelace = _newUseLovelace = prefs.getBool("use-lovelace") ?? false; + } catch (e) { + _useLovelace = _newUseLovelace = false; + } }); } @@ -55,7 +62,8 @@ class _ConnectionSettingsPageState extends State { (_newHassioPort != _hassioPort) || (_newHassioDomain != _hassioDomain) || (_newSocketProtocol != _socketProtocol) || - (_newAuthType != _authType)); + (_newAuthType != _authType) || + (_newUseLovelace != _useLovelace)); }); } @@ -70,6 +78,7 @@ class _ConnectionSettingsPageState extends State { prefs.setString("hassio-protocol", _newSocketProtocol); prefs.setString("hassio-res-protocol", _newSocketProtocol == "wss" ? "https" : "http"); prefs.setString("hassio-auth-type", _newAuthType); + prefs.setBool("use-lovelace", _newUseLovelace); } @override @@ -95,6 +104,13 @@ class _ConnectionSettingsPageState extends State { body: ListView( padding: const EdgeInsets.all(20.0), children: [ + Text( + "Connection settings", + style: TextStyle( + color: Colors.black45, + fontSize: 20.0 + ), + ), new Row( children: [ Text("Use ssl (HTTPS)"), @@ -172,7 +188,29 @@ class _ConnectionSettingsPageState extends State { }, focusNode: _passwordFocusNode, onEditingComplete: _checkConfigChanged, - ) + ), + Padding( + padding: EdgeInsets.only(top: 20.0), + child: Text( + "UI", + style: TextStyle( + color: Colors.black45, + fontSize: 20.0 + ), + ), + ), + new Row( + children: [ + Text("Use Lovelace UI"), + Switch( + value: _newUseLovelace, + onChanged: (value) { + _newUseLovelace = value; + _checkConfigChanged(); + }, + ) + ], + ), ], ), ); diff --git a/lib/group_based_ui.dart b/lib/ui.dart similarity index 72% rename from lib/group_based_ui.dart rename to lib/ui.dart index 745923f..ae10bc9 100644 --- a/lib/group_based_ui.dart +++ b/lib/ui.dart @@ -1,9 +1,9 @@ part of 'main.dart'; -class GroupBasedUI { - List views; +class HomeAssistantUI { + List views; - GroupBasedUI() { + HomeAssistantUI() { views = []; } @@ -14,7 +14,6 @@ class GroupBasedUI { } List _buildViews(BuildContext context) { - TheLogger.log("Debug", "Building UI"); List result = []; views.forEach((view) { result.add( @@ -26,19 +25,59 @@ class GroupBasedUI { } -class HACView { - List cards = []; +class HAView { + List cards = []; List badges = []; Entity linkedEntity; String name; String id; + String iconName; int count; - HACView({ + HAView({ this.name, this.id, - this.count - }); + this.count, + this.iconName, + List childEntities + }) { + _fillView(childEntities ?? []); + } + + void _fillView(List childEntities) { + List autoGeneratedCards = []; + childEntities.forEach((entity) { + if (entity.isBadge) { + badges.add(entity); + TheLogger.log("Debug","----Badge: ${entity.entityId}"); + } else { + if (!entity.isGroup) { + String groupIdToAdd = "${entity.domain}.${entity.domain}$count"; + if (autoGeneratedCards.every((HACard card) => card.id != groupIdToAdd )) { + HACard card = HACard( + id: groupIdToAdd, + name: entity.domain + ); + TheLogger.log("Debug","----Creating card: $groupIdToAdd"); + card.entities.add(entity); + autoGeneratedCards.add(card); + } else { + autoGeneratedCards.firstWhere((card) => card.id == groupIdToAdd).entities.add(entity); + } + } else { + TheLogger.log("Debug","----Card: ${entity.entityId}"); + HACard card = HACard( + name: entity.displayName, + id: entity.entityId, + linkedEntity: entity + ); + card.entities.addAll(entity.childEntities); + cards.add(card); + } + } + }); + cards.addAll(autoGeneratedCards); + } Widget build(BuildContext context) { return NewViewWidget( @@ -48,7 +87,7 @@ class HACView { } class NewViewWidget extends StatefulWidget { - final HACView view; + final HAView view; const NewViewWidget({ Key key, @@ -103,7 +142,7 @@ class NewViewWidgetState extends State { ); } - widget.view.cards.forEach((HACCard card){ + widget.view.cards.forEach((HACard card){ result.add( card.build(context) ); @@ -141,13 +180,13 @@ class NewViewWidgetState extends State { } -class HACCard { +class HACard { List entities = []; Entity linkedEntity; String name; String id; - HACCard({ + HACard({ this.name, this.id, this.linkedEntity @@ -163,7 +202,7 @@ class HACCard { class NewCardWidget extends StatelessWidget { - final HACCard card; + final HACard card; const NewCardWidget({ Key key,