From 284e7ba451bb5ca37a17b2a48b0dd594cb95a6b9 Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Sun, 7 Oct 2018 02:17:14 +0300 Subject: [PATCH] Resolves #125 UI building refactored --- lib/badge_class.dart | 9 -- lib/card_class.dart | 57 +++++-- lib/entity_class/entity.class.dart | 123 ++++++++++++++- lib/entity_collection.class.dart | 14 +- lib/home_assistant.class.dart | 10 +- lib/main.dart | 240 +++-------------------------- lib/ui_builder_class.dart | 64 -------- lib/utils.class.dart | 8 + lib/view_builder.class.dart | 102 ++++++++++++ lib/view_class.dart | 148 +++++++++++++----- 10 files changed, 429 insertions(+), 346 deletions(-) delete mode 100644 lib/badge_class.dart delete mode 100644 lib/ui_builder_class.dart create mode 100644 lib/view_builder.class.dart diff --git a/lib/badge_class.dart b/lib/badge_class.dart deleted file mode 100644 index b62fc65..0000000 --- a/lib/badge_class.dart +++ /dev/null @@ -1,9 +0,0 @@ -part of 'main.dart'; - -class Badge { - String _entityId; - - Badge(String groupId) { - _entityId = groupId; - } -} \ No newline at end of file diff --git a/lib/card_class.dart b/lib/card_class.dart index 59731db..0d2b455 100644 --- a/lib/card_class.dart +++ b/lib/card_class.dart @@ -1,25 +1,54 @@ part of 'main.dart'; -class HACard { - String _entityId; - List _entities; - String _friendlyName; +class HACard extends StatelessWidget { - List get entities => _entities; - String get friendlyName => _friendlyName; + final List entities; + final String friendlyName; - HACard(String groupId, String friendlyName) { - _entityId = groupId; - _entities = []; - _friendlyName = friendlyName; + const HACard({ + Key key, + this.entities, + this.friendlyName + }) : super(key: key); + + @override + Widget build(BuildContext context) { + List body = []; + body.add(_buildCardHeader()); + body.addAll(_buildCardBody(context)); + return Card( + child: new Column(mainAxisSize: MainAxisSize.min, children: body) + ); } - void addEntity(String entityId) { - _entities.add(entityId); + Widget _buildCardHeader() { + var result; + if ((friendlyName != null) && (friendlyName.trim().length > 0)) { + result = new ListTile( + //leading: const Icon(Icons.device_hub), + //subtitle: Text(".."), + //trailing: Text("${data["state"]}"), + title: Text("$friendlyName", + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis, + style: new TextStyle(fontWeight: FontWeight.bold, fontSize: 25.0)), + ); + } else { + result = new Container(width: 0.0, height: 0.0); + } + return result; } - void addEntities(List entities) { - _entities.addAll(entities); + List _buildCardBody(BuildContext context) { + List result = []; + entities.forEach((Entity entity) { + result.add( + Padding( + padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0), + child: entity.buildWidget(context, EntityWidgetType.regular), + )); + }); + return result; } } \ No newline at end of file diff --git a/lib/entity_class/entity.class.dart b/lib/entity_class/entity.class.dart index 3c2a847..56f2bc4 100644 --- a/lib/entity_class/entity.class.dart +++ b/lib/entity_class/entity.class.dart @@ -8,6 +8,11 @@ class Entity { "unknown": Colors.black12, "playing": Colors.amber }; + static const badgeColors = { + "default": Color.fromRGBO(223, 76, 30, 1.0), + "binary_sensor": Color.fromRGBO(3, 155, 229, 1.0) + }; + static List badgeDomains = ["alarm_control_panel", "binary_sensor", "device_tracker", "updater", "sun", "timer", "sensor"]; static const RIGHT_WIDGET_PADDING = 14.0; static const LEFT_WIDGET_PADDING = 8.0; static const EXTENDED_WIDGET_HEIGHT = 50.0; @@ -25,6 +30,8 @@ class Entity { String assumedState; DateTime _lastUpdated; + List childEntities = []; + String get displayName => _attributes["friendly_name"] ?? (_attributes["name"] ?? "_"); String get domain => _domain; @@ -37,11 +44,12 @@ class Entity { (_domain == "group") && (_attributes != null ? _attributes["view"] ?? false : false); bool get isGroup => _domain == "group"; + bool get isBadge => Entity.badgeDomains.contains(_domain); String get icon => _attributes["icon"] ?? ""; bool get isOn => state == "on"; String get entityPicture => _attributes["entity_picture"]; String get unitOfMeasurement => _attributes["unit_of_measurement"] ?? ""; - List get childEntities => _attributes["entity_id"] ?? []; + List get childEntityIds => _attributes["entity_id"] ?? []; String get lastUpdated => _getLastUpdatedFormatted(); Entity(Map rawData) { @@ -159,6 +167,8 @@ class _EntityWidgetState extends State { _buildLastUpdatedWidget() ], ); + } else if (widget.widgetType == EntityWidgetType.badge) { + return _buildBadgeWidget(context); } else { TheLogger.log("Error", "Unknown entity widget type: ${widget.widgetType}"); } @@ -247,4 +257,115 @@ class _EntityWidgetState extends State { ) ); } + + Widget _buildBadgeWidget(BuildContext context) { + //TODO separate this by different Entity classes + double iconSize = 26.0; + Widget badgeIcon; + String onBadgeTextValue; + Color iconColor = Entity.badgeColors[widget.entity.domain] ?? Entity.badgeColors["default"]; + switch (widget.entity.domain) { + case "sun": { + badgeIcon = widget.entity.state == "below_horizon" ? + Icon( + MaterialDesignIcons.createIconDataFromIconCode(0xf0dc), + size: iconSize, + ) : + Icon( + MaterialDesignIcons.createIconDataFromIconCode(0xf5a8), + size: iconSize, + ); + break; + } + case "sensor": { + onBadgeTextValue = widget.entity.unitOfMeasurement; + badgeIcon = Center( + child: Text( + "${widget.entity.state == 'unknown' ? '-' : widget.entity.state}", + overflow: TextOverflow.fade, + softWrap: false, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 17.0), + ), + ); + break; + } + case "device_tracker": { + badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData(widget.entity, iconSize,Colors.black); + onBadgeTextValue = widget.entity.state; + break; + } + default: { + badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData(widget.entity, iconSize,Colors.black); + } + } + Widget onBadgeText; + if (onBadgeTextValue == null || onBadgeTextValue.length == 0) { + onBadgeText = Container(width: 0.0, height: 0.0); + } else { + onBadgeText = Container( + padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0), + child: Text("$onBadgeTextValue", + style: TextStyle(fontSize: 12.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: [ + 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: [ + Positioned( + width: 46.0, + height: 46.0, + top: 0.0, + left: 0.0, + child: badgeIcon, + ), + Positioned( + //width: 50.0, + bottom: -9.0, + left: -10.0, + right: -10.0, + child: Center( + child: onBadgeText, + ) + ) + ], + ), + ), + Container( + width: 60.0, + child: Text( + "${widget.entity.displayName}", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 12.0), + softWrap: true, + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ); + } } diff --git a/lib/entity_collection.class.dart b/lib/entity_collection.class.dart index 2f4fbed..89fe4b9 100644 --- a/lib/entity_collection.class.dart +++ b/lib/entity_collection.class.dart @@ -5,6 +5,8 @@ class EntityCollection { Map _entities; List viewList; + bool get isEmpty => _entities.isEmpty; + EntityCollection() { _entities = {}; viewList = []; @@ -57,6 +59,16 @@ class EntityCollection { return _entities[entityId]; } + List getAll(List ids) { + List result = []; + _entities.forEach((id, Entity entity){ + if (ids.contains(id)) { + result.add(entity); + } + }); + return result; + } + bool isExist(String entityId) { return _entities[entityId] != null; } @@ -76,7 +88,7 @@ class EntityCollection { entities.forEach((entiyId) { bool foundInGroup = false; result["userGroups"].forEach((userGroupId) { - if (_entities[userGroupId].childEntities.contains(entiyId)) { + if (_entities[userGroupId].childEntityIds.contains(entiyId)) { foundInGroup = true; } }); diff --git a/lib/home_assistant.class.dart b/lib/home_assistant.class.dart index 378ddb9..7c9626f 100644 --- a/lib/home_assistant.class.dart +++ b/lib/home_assistant.class.dart @@ -14,7 +14,7 @@ class HomeAssistant { int _subscriptionMessageId = 0; int _configMessageId = 0; EntityCollection _entities; - UIBuilder _uiBuilder; + ViewBuilder _viewBuilder; Map _instanceConfig = {}; Completer _fetchCompleter; @@ -33,13 +33,11 @@ class HomeAssistant { String get locationName => _instanceConfig["location_name"] ?? ""; int get viewsCount => _entities.viewList.length ?? 0; - UIBuilder get uiBuilder => _uiBuilder; EntityCollection get entities => _entities; HomeAssistant() { _entities = EntityCollection(); - _uiBuilder = UIBuilder(); _messageQueue = SendMessageQueue(messageExpirationTime); } @@ -302,9 +300,13 @@ class HomeAssistant { return; } _entities.parse(response["result"]); - _uiBuilder.build(_entities); + _viewBuilder = ViewBuilder(entityCollection: _entities); _statesCompleter.complete(); } + + Widget buildViews(BuildContext context) { + return _viewBuilder.buildWidget(context); + } } class SendMessageQueue { diff --git a/lib/main.dart b/lib/main.dart index 30e6932..6d58ed3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,10 +27,9 @@ part 'entity.page.dart'; part 'utils.class.dart'; part 'mdi.class.dart'; part 'entity_collection.class.dart'; -part 'ui_builder_class.dart'; +part 'view_builder.class.dart'; part 'view_class.dart'; part 'card_class.dart'; -part 'badge_class.dart'; EventBus eventBus = new EventBus(); const String appName = "HA Client"; @@ -99,14 +98,10 @@ class _MainPageState extends State with WidgetsBindingObserver { StreamSubscription _settingsSubscription; StreamSubscription _serviceCallSubscription; StreamSubscription _showEntityPageSubscription; + StreamSubscription _refreshDataSubscription; bool _isLoading = true; bool _settingsLoaded = false; - Map _badgeColors = { - "default": Color.fromRGBO(223, 76, 30, 1.0), - "binary_sensor": Color.fromRGBO(3, 155, 229, 1.0) - }; - @override void initState() { super.initState(); @@ -193,6 +188,12 @@ class _MainPageState extends State with WidgetsBindingObserver { _showEntityPage(event.entity); }); } + + if (_refreshDataSubscription == null) { + _refreshDataSubscription = eventBus.on().listen((event){ + _refreshData(); + }); + } } _refreshData() async { @@ -212,6 +213,7 @@ class _MainPageState extends State with WidgetsBindingObserver { }).catchError((e) { _setErrorState(e); }); + eventBus.fire(RefreshDataFinishedEvent()); } _setErrorState(e) { @@ -235,217 +237,22 @@ class _MainPageState extends State with WidgetsBindingObserver { ); } - List _buildViews() { - List result = []; - if ((_entities != null) && (!_homeAssistant.uiBuilder.isEmpty)) { - _homeAssistant.uiBuilder.views.forEach((viewId, view) { + List buildUIViewTabs() { + //TODO move somewhere to ViewBuilder + List result = []; + if (!_entities.isEmpty) { + if (!_entities.hasDefaultView) { result.add( - RefreshIndicator( - color: Colors.amber, - child: ListView( - physics: const AlwaysScrollableScrollPhysics(), - children: _buildSingleView(view), - ), - onRefresh: () => _refreshData(), + Tab( + icon: + Icon( + MaterialDesignIcons.createIconDataFromIconName("mdi:home-assistant"), + size: 24.0, + ) ) ); - }); - } - return result; - } - - List _buildSingleView(View view) { - List result = []; - if (view.isThereBadges) { - result.add( - Wrap( - alignment: WrapAlignment.center, - spacing: 10.0, - runSpacing: 1.0, - children: _buildBadges(view.badges), - ) - ); - - } - view.cards.forEach((id, card) { - if (card.entities.isNotEmpty) { - result.add(_buildCard(card)); } - }); - - return result; - } - - List _buildBadges( Map badges) { - List result = []; - badges.forEach((id, badge) { - var badgeEntity = _entities.get(id); - if (badgeEntity != null) { - result.add( - _buildSingleBadge(badgeEntity) - ); - } - }); - return result; - } - - Widget _buildSingleBadge(Entity 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.unitOfMeasurement; - badgeIcon = Center( - child: Text( - "${data.state == 'unknown' ? '-' : data.state}", - overflow: TextOverflow.fade, - softWrap: false, - textAlign: TextAlign.center, - style: TextStyle(fontSize: 17.0), - ), - ); - break; - } - case "device_tracker": { - badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData(data, iconSize,Colors.black); - badgeTextValue = data.state; - break; - } - default: { - badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData(data, iconSize,Colors.black); - } - } - Widget badgeText; - if (badgeTextValue == null || badgeTextValue.length == 0) { - 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: 12.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: [ - 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: [ - Positioned( - width: 46.0, - height: 46.0, - top: 0.0, - left: 0.0, - child: badgeIcon, - ), - Positioned( - //width: 50.0, - bottom: -9.0, - left: -10.0, - right: -10.0, - child: Center( - child: badgeText, - ) - ) - ], - ), - ), - Container( - width: 60.0, - child: Text( - "${data.displayName}", - textAlign: TextAlign.center, - style: TextStyle(fontSize: 12.0), - softWrap: true, - maxLines: 3, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ); - } - - Card _buildCard(HACard card) { - List body = []; - body.add(_buildCardHeader(card.friendlyName)); - body.addAll(_buildCardBody(card.entities)); - Card result = Card( - child: new Column(mainAxisSize: MainAxisSize.min, children: body) - ); - return result; - } - - Widget _buildCardHeader(String name) { - var result; - if (name.trim().length > 0) { - result = new ListTile( - //leading: const Icon(Icons.device_hub), - //subtitle: Text(".."), - //trailing: Text("${data["state"]}"), - title: Text("$name", - textAlign: TextAlign.left, - overflow: TextOverflow.ellipsis, - style: new TextStyle(fontWeight: FontWeight.bold, fontSize: 25.0)), - ); - } else { - result = new Container(width: 0.0, height: 0.0); - } - return result; - } - - List _buildCardBody(List ids) { - List entities = []; - ids.forEach((id) { - var entity = _entities.get(id); - if (entity != null) { - entities.add( - Padding( - padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0), - child: entity.buildWidget(context, EntityWidgetType.regular), - )); - } - }); - return entities; - } - - List buildUIViewTabs() { - List result = []; - if ((_entities != null) && (!_homeAssistant.uiBuilder.isEmpty)) { - _homeAssistant.uiBuilder.views.forEach((viewId, view) { + _entities.viewList.forEach((viewId) { result.add( Tab( icon: MaterialDesignIcons.createIconWidgetFromEntityData(_entities.get(viewId), 24.0, null) ?? @@ -652,9 +459,7 @@ class _MainPageState extends State with WidgetsBindingObserver { ), ) : - TabBarView( - children: _buildViews() - ), + _homeAssistant.buildViews(context) ); } @@ -679,6 +484,7 @@ class _MainPageState extends State with WidgetsBindingObserver { if (_settingsSubscription != null) _settingsSubscription.cancel(); if (_serviceCallSubscription != null) _serviceCallSubscription.cancel(); if (_showEntityPageSubscription != null) _showEntityPageSubscription.cancel(); + if (_refreshDataSubscription != null) _refreshDataSubscription.cancel(); _homeAssistant.disconnect(); super.dispose(); } diff --git a/lib/ui_builder_class.dart b/lib/ui_builder_class.dart deleted file mode 100644 index 1dcffd3..0000000 --- a/lib/ui_builder_class.dart +++ /dev/null @@ -1,64 +0,0 @@ -part of 'main.dart'; - -class UIBuilder { - EntityCollection _entities; - Map _views; - static List badgeDomains = ["alarm_control_panel", "binary_sensor", "device_tracker", "updater", "sun", "timer", "sensor"]; - - bool get isEmpty => _views.length == 0; - Map get views => _views ?? {}; - - UIBuilder() { - _views = {}; - } - - static bool isBadge(String domain) { - return badgeDomains.contains(domain); - } - - void build(EntityCollection entitiesCollection) { - _entities = entitiesCollection; - _views.clear(); - if (!_entities.hasDefaultView) { - _createDefaultView(); - } - _createViews(entitiesCollection.viewList); - } - - void _createDefaultView() { - Map> userGroupsList = _entities.getDefaultViewTopLevelEntities(); - View view = View("group.default_view", 0); - userGroupsList["userGroups"].forEach((groupId){ - view.add(_entities.get(groupId)); - }); - userGroupsList["notGroupedEntities"].forEach((entityId){ - view.add(_entities.get(entityId)); - }); - _views["group.default_view"] = view; - } - - void _createViews(List viewsList) { - int counter = 0; - viewsList.forEach((viewId) { - counter += 1; - View view = View(viewId, counter); - - try { - Entity viewGroupEntity = _entities.get(viewId); - viewGroupEntity.childEntities.forEach(( - entityId) { //Each entity or group in view - if (_entities.isExist(entityId)) { - view.add(_entities.get(entityId)); - } else { - TheLogger.log("Warning", "Unknown entity inside view: $entityId"); - } - }); - } catch (error) { - TheLogger.log("Error","Error parsing view: $viewId"); - } - - _views[viewId] = view; - }); - } - -} \ No newline at end of file diff --git a/lib/utils.class.dart b/lib/utils.class.dart index e602706..2a2edc5 100644 --- a/lib/utils.class.dart +++ b/lib/utils.class.dart @@ -56,6 +56,14 @@ class SettingsChangedEvent { SettingsChangedEvent(this.reconnect); } +class RefreshDataEvent { + RefreshDataEvent(); +} + +class RefreshDataFinishedEvent { + RefreshDataFinishedEvent(); +} + class ServiceCallEvent { String domain; String service; diff --git a/lib/view_builder.class.dart b/lib/view_builder.class.dart new file mode 100644 index 0000000..2b418c0 --- /dev/null +++ b/lib/view_builder.class.dart @@ -0,0 +1,102 @@ +part of 'main.dart'; + +class ViewBuilder{ + + EntityCollection entityCollection; + List _views; + + ViewBuilder({ + Key key, + this.entityCollection + }) { + _compose(); + } + + Widget buildWidget(BuildContext context) { + return ViewBuilderWidget( + entities: _views + ); + } + + void _compose() { + TheLogger.log("Debug", "Rebuilding all UI..."); + _views = []; + if (!entityCollection.hasDefaultView) { + _views.add(_composeDefaultView()); + } + _views.addAll(_composeViews()); + } + + View _composeDefaultView() { + Map> userGroupsList = entityCollection.getDefaultViewTopLevelEntities(); + List entitiesForView = []; + //TODO WTF? Why two arrays? + userGroupsList["userGroups"].forEach((groupId){ + entitiesForView.add(entityCollection.get(groupId)); + }); + userGroupsList["notGroupedEntities"].forEach((entityId){ + entitiesForView.add(entityCollection.get(entityId)); + }); + return View( + entities: entitiesForView, + count: 0 + ); + } + + List _composeViews() { + List result = []; + int counter = 0; + entityCollection.viewList.forEach((viewId) { + counter += 1; + //try { + Entity viewGroupEntity = entityCollection.get(viewId); + List entitiesForView = []; + viewGroupEntity.childEntityIds.forEach(( + entityId) { //Each entity or group in view + if (entityCollection.isExist(entityId)) { + Entity en = entityCollection.get(entityId); + if (en.isGroup) { + en.childEntities = entityCollection.getAll(en.childEntityIds); + } + entitiesForView.add(en); + } else { + TheLogger.log("Warning", "Unknown entity inside view: $entityId"); + } + }); + result.add(View( + count: counter, + entities: entitiesForView + )); + /*} catch (error) { + TheLogger.log("Error","Error parsing view: $viewId"); + }*/ + }); + return result; + } +} + +class ViewBuilderWidget extends StatelessWidget { + + final List entities; + + const ViewBuilderWidget({ + Key key, + this.entities + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return TabBarView( + children: _buildChildren(context) + ); + } + + List _buildChildren(BuildContext context) { + List result = []; + entities.forEach((View view){ + result.add(view.buildWidget(context)); + }); + return result; + } + +} \ No newline at end of file diff --git a/lib/view_class.dart b/lib/view_class.dart index 244bff5..459b543 100644 --- a/lib/view_class.dart +++ b/lib/view_class.dart @@ -1,53 +1,129 @@ part of 'main.dart'; class View { - String _entityId; - int _count; - Map cards; - Map badges; + List childEntitiesAsBadges; + Map childEntitiesAsCards; - bool get isThereBadges => (badges != null) && (badges.isNotEmpty); + int count; + List entities; - View(String groupId, int viewCount) { - _entityId = groupId; - _count = viewCount; - cards = {}; - badges = {}; + View({ + Key key, + this.count, + this.entities + }) { + childEntitiesAsBadges = []; + childEntitiesAsCards = {}; + _composeEntities(); } - void add(Entity entity) { - if (!entity.isGroup) { - _addEntityWithoutGroup(entity); - } else { - _addCardWithEntities(entity); - } + Widget buildWidget(BuildContext context) { + return ViewWidget( + badges: childEntitiesAsBadges, + cards: childEntitiesAsCards, + ); } - void _addBadge(String entityId) { - badges.addAll({entityId: Badge(entityId)}); - } - - void _addEntityWithoutGroup(Entity entity) { - if (UIBuilder.isBadge(entity.domain)) { - //This is badge - _addBadge(entity.entityId); - } else { - //This is a standalone entity - String groupIdToAdd = "${entity.domain}.${entity.domain}$_count"; - if (cards[groupIdToAdd] == null) { - _addCard(groupIdToAdd, entity.domain); + void _composeEntities() { + entities.forEach((Entity entity){ + if (!entity.isGroup) { + if (entity.isBadge) { + childEntitiesAsBadges.add(entity); + } else { + String groupIdToAdd = "${entity.domain}.${entity.domain}$count"; + if (childEntitiesAsCards[groupIdToAdd] == null) { + childEntitiesAsCards[groupIdToAdd] = CardSkeleton( + displayName: entity.domain, + ); + } + childEntitiesAsCards[groupIdToAdd].childEntities.add(entity); + } + } else { + childEntitiesAsCards[entity.entityId] = CardSkeleton( + displayName: entity.displayName, + ); + childEntitiesAsCards[entity.entityId].childEntities = entity.childEntities; } - cards[groupIdToAdd].addEntity(entity.entityId); + }); + } + +} + +class ViewWidget extends StatelessWidget { + final List badges; + final Map cards; + final String displayName; + + const ViewWidget({ + Key key, + this.badges, + this.cards, + this.displayName + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return RefreshIndicator( + color: Colors.amber, + child: ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: _buildChildren(context), + ), + onRefresh: () => _refreshData(), + ); + } + + List _buildChildren(BuildContext context) { + List result = []; + + if (badges.isNotEmpty) { + result.insert(0, + Wrap( + alignment: WrapAlignment.center, + spacing: 10.0, + runSpacing: 1.0, + children: _buildBadges(context, badges), + ) + ); } + + cards.forEach((String id, CardSkeleton skeleton){ + result.add( + HACard( + entities: skeleton.childEntities, + friendlyName: skeleton.displayName, + ) + ); + }); + + return result; } - void _addCard(String entityId, String friendlyName) { - cards.addAll({"$entityId": HACard(entityId, friendlyName)}); + List _buildBadges(BuildContext context, List badges) { + List result = []; + badges.forEach((Entity entity) { + result.add(entity.buildWidget(context, EntityWidgetType.badge)); + }); + return result; } - void _addCardWithEntities(Entity entity) { - cards.addAll({"${entity.entityId}": HACard(entity.entityId, entity.displayName)}); - cards[entity.entityId].addEntities(entity.childEntities); - } + Future _refreshData() { + Completer refreshCompleter = Completer(); + eventBus.fire(RefreshDataEvent()); + eventBus.on().listen((event) { + refreshCompleter.complete(); + }); + + return refreshCompleter.future; + } +} + +class CardSkeleton { + String displayName; + List childEntities; + + CardSkeleton({Key key, this.displayName, this.childEntities}) { + childEntities = []; + } } \ No newline at end of file