Resolves #154 UI building refactoring

This commit is contained in:
Yegor Vialov 2018-10-25 00:05:29 +03:00
parent 24d071e2f8
commit 5338e45ddc
10 changed files with 295 additions and 347 deletions

View File

@ -1,61 +0,0 @@
part of 'main.dart';
class CardWidget extends StatelessWidget {
final List<Entity> entities;
final String friendlyName;
const CardWidget({
Key key,
this.entities,
this.friendlyName
}) : super(key: key);
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
if (entityModel != null) {
final groupEntity = entityModel.entity;
if ((groupEntity!= null) && (groupEntity.isHidden)) {
return Container(width: 0.0, height: 0.0,);
}
}
List<Widget> body = [];
body.add(_buildCardHeader());
body.addAll(_buildCardBody(context));
return Card(
child: new Column(mainAxisSize: MainAxisSize.min, children: body)
);
}
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;
}
List<Widget> _buildCardBody(BuildContext context) {
List<Widget> result = [];
entities.forEach((Entity entity) {
result.add(
Padding(
padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0),
child: entity.buildDefaultWidget(context),
));
});
return result;
}
}

View File

@ -1,4 +1,4 @@
part of '../main.dart';
part of 'main.dart';
class Entity {
static const STATE_ICONS_COLORS = {

View File

@ -6,13 +6,14 @@ class EntityCollection {
Map<String, Entity> views;
bool get isEmpty => _allEntities.isEmpty;
List<Entity> get viewEntities => _allEntities.values.where((entity) => entity.isView).toList();
EntityCollection() {
_allEntities = {};
views = {};
}
bool get hasDefaultView => _allEntities["group.default_view"] != null;
bool get hasDefaultView => _allEntities.keys.contains("group.default_view");
void parse(List rawData) {
_allEntities.clear();
@ -114,31 +115,32 @@ class EntityCollection {
return _allEntities[entityId] != null;
}
Map<String,List<String>> getDefaultViewTopLevelEntities() {
Map<String,List<String>> result = {"userGroups": [], "notGroupedEntities": []};
List<String> entities = [];
List<Entity> filterEntitiesForDefaultView() {
List<Entity> result = [];
List<Entity> groups = [];
List<Entity> nonGroupEntities = [];
_allEntities.forEach((id, entity){
if ((id.indexOf("group.") == 0) && (id.indexOf(".all_") == -1) && (!entity.isView)) {
result["userGroups"].add(id);
groups.add(entity);
}
if (!entity.isGroup) {
entities.add(id);
nonGroupEntities.add(entity);
}
});
entities.forEach((entiyId) {
nonGroupEntities.forEach((entity) {
bool foundInGroup = false;
result["userGroups"].forEach((userGroupId) {
if (_allEntities[userGroupId].childEntityIds.contains(entiyId)) {
groups.forEach((groupEntity) {
if (groupEntity.childEntityIds.contains(entity.entityId)) {
foundInGroup = true;
}
});
if (!foundInGroup) {
result["notGroupedEntities"].add(entiyId);
result.add(entity);
}
});
result.insertAll(0, groups);
return result;
}
}

View File

@ -0,0 +1,217 @@
part of 'main.dart';
class GroupBasedUI {
List<HACView> views;
GroupBasedUI() {
views = [];
}
Widget build(BuildContext context) {
return TabBarView(
children: _buildViews(context)
);
}
List<Widget> _buildViews(BuildContext context) {
TheLogger.log("Debug", "Building UI");
List<Widget> result = [];
views.forEach((view) {
result.add(
view.build(context)
);
});
return result;
}
}
class HACView {
List<HACCard> cards = [];
List<Entity> badges = [];
Entity linkedEntity;
String name;
String id;
int count;
HACView({
this.name,
this.id,
this.count
});
Widget build(BuildContext context) {
return NewViewWidget(
view: this,
);
}
}
class NewViewWidget extends StatefulWidget {
final HACView view;
const NewViewWidget({
Key key,
this.view
}) : super(key: key);
@override
State<StatefulWidget> createState() {
return NewViewWidgetState();
}
}
class NewViewWidgetState extends State<NewViewWidget> {
StreamSubscription _refreshDataSubscription;
Completer _refreshCompleter;
@override
void initState() {
super.initState();
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) {
_refreshCompleter.complete();
}
});
}
@override
Widget build(BuildContext context) {
TheLogger.log("Debug", "--Building view ${widget.view.id}");
return RefreshIndicator(
color: Colors.amber,
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: _buildChildren(context),
),
onRefresh: () => _refreshData(),
);
}
List<Widget> _buildChildren(BuildContext context) {
List<Widget> result = [];
if (widget.view.badges.isNotEmpty) {
result.insert(0,
Wrap(
alignment: WrapAlignment.center,
spacing: 10.0,
runSpacing: 1.0,
children: _buildBadges(context),
)
);
}
widget.view.cards.forEach((HACCard card){
result.add(
card.build(context)
);
});
return result;
}
List<Widget> _buildBadges(BuildContext context) {
List<Widget> result = [];
widget.view.badges.forEach((Entity entity) {
result.add(entity.buildBadgeWidget(context));
});
return result;
}
Future _refreshData() {
if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) {
TheLogger.log("Debug","Previous data refresh is still in progress");
} else {
_refreshCompleter = Completer();
eventBus.fire(RefreshDataEvent());
}
return _refreshCompleter.future;
}
@override
void dispose() {
_refreshDataSubscription.cancel();
super.dispose();
}
}
class HACCard {
List<Entity> entities = [];
Entity linkedEntity;
String name;
String id;
HACCard({
this.name,
this.id,
this.linkedEntity
});
Widget build(BuildContext context) {
return NewCardWidget(
card: this,
);
}
}
class NewCardWidget extends StatelessWidget {
final HACCard card;
const NewCardWidget({
Key key,
this.card
}) : super(key: key);
@override
Widget build(BuildContext context) {
TheLogger.log("Debug", "----Building card ${card.id}");
if ((card.linkedEntity!= null) && (card.linkedEntity.isHidden)) {
return Container(width: 0.0, height: 0.0,);
}
List<Widget> body = [];
body.add(_buildCardHeader());
body.addAll(_buildCardBody(context));
return Card(
child: new Column(mainAxisSize: MainAxisSize.min, children: body)
);
}
Widget _buildCardHeader() {
var result;
if ((card.name != null) && (card.name.trim().length > 0)) {
result = new ListTile(
//leading: const Icon(Icons.device_hub),
//subtitle: Text(".."),
//trailing: Text("${data["state"]}"),
title: Text("${card.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<Widget> _buildCardBody(BuildContext context) {
List<Widget> result = [];
card.entities.forEach((Entity entity) {
TheLogger.log("Debug", "------entity ${entity.entityId}");
result.add(
Padding(
padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0),
child: entity.buildDefaultWidget(context),
));
});
return result;
}
}

View File

@ -14,8 +14,8 @@ class HomeAssistant {
int _subscriptionMessageId = 0;
int _configMessageId = 0;
int _userInfoMessageId = 0;
EntityCollection _entities;
ViewBuilder _viewBuilder;
EntityCollection entities;
GroupBasedUI ui;
Map _instanceConfig = {};
String _userName;
@ -38,12 +38,10 @@ class HomeAssistant {
String get locationName => _instanceConfig["location_name"] ?? "";
String get userName => _userName ?? locationName;
String get userAvatarText => userName.length > 0 ? userName[0] : "";
int get viewsCount => _entities.views.length ?? 0;
EntityCollection get entities => _entities;
int get viewsCount => entities.views.length ?? 0;
HomeAssistant() {
_entities = EntityCollection();
entities = EntityCollection();
_messageQueue = SendMessageQueue(messageExpirationTime);
}
@ -314,7 +312,7 @@ class HomeAssistant {
void _handleEntityStateChange(Map eventData) {
//TheLogger.log("Debug", "New state for ${eventData['entity_id']}");
Map data = Map.from(eventData);
_entities.updateState(data);
entities.updateState(data);
eventBus.fire(new StateChangedEvent(data["entity_id"], null, false));
}
@ -367,13 +365,65 @@ class HomeAssistant {
_statesCompleter.completeError({"errorCode": 3, "errorMessage": response["error"]["message"]});
return;
}
_entities.parse(response["result"]);
_viewBuilder = ViewBuilder(entityCollection: _entities);
entities.parse(response["result"]);
ui = GroupBasedUI();
int viewCounter = 0;
//TODO add default_view
entities.viewEntities.forEach((viewEntity) {
TheLogger.log("Debug","--View: ${viewEntity.entityId}");
HACView view = HACView(
count: viewCounter,
id: viewEntity.entityId,
name: viewEntity.displayName
);
view.linkedEntity = viewEntity;
List<HACCard> autoGeneratedCards = [];
viewEntity.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);
}
}
});
view.cards.addAll(autoGeneratedCards);
ui.views.add(
view
);
viewCounter += 1;
});
_statesCompleter.complete();
}
Widget buildViews(BuildContext context) {
return _viewBuilder.buildWidget(context);
//return _viewBuilder.buildWidget(context);
return ui.build(context);
}
Future<List> getHistory(String entityId) async {

View File

@ -14,10 +14,9 @@ import 'package:date_format/date_format.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_colorpicker/material_picker.dart';
part 'entity_class/entity.class.dart';
part 'entity_class/stateless_widgets.dart';
part 'entity_class/stateful_widgets.dart';
part 'entity.class.dart';
part 'widgets/stateless_widgets.dart';
part 'widgets/stateful_widgets.dart';
part 'settings.page.dart';
part 'home_assistant.class.dart';
part 'log.page.dart';
@ -25,9 +24,7 @@ part 'entity.page.dart';
part 'utils.class.dart';
part 'mdi.class.dart';
part 'entity_collection.class.dart';
part 'view_builder.class.dart';
part 'view_class.dart';
part 'card_class.dart';
part 'group_based_ui.dart';
EventBus eventBus = new EventBus();
const String appName = "HA Client";

View File

@ -1,88 +0,0 @@
part of 'main.dart';
class ViewBuilder{
EntityCollection entityCollection;
List<View> _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<String, List<String>> userGroupsList = entityCollection.getDefaultViewTopLevelEntities();
List<Entity> entitiesForView = [];
userGroupsList["userGroups"].forEach((groupId){
Entity en = entityCollection.get(groupId);
entitiesForView.add(en);
});
userGroupsList["notGroupedEntities"].forEach((entityId){
entitiesForView.add(entityCollection.get(entityId));
});
return View(
entities: entitiesForView,
count: 0
);
}
List<View> _composeViews() {
List<View> result = [];
int counter = 0;
entityCollection.views.forEach((viewId, viewGroupEntity) {
counter += 1;
//try {
result.add(View(
count: counter,
entities: viewGroupEntity.childEntities
));
/*} catch (error) {
TheLogger.log("Error","Error parsing view: $viewId");
}*/
});
return result;
}
}
class ViewBuilderWidget extends StatelessWidget {
final List<View> entities;
const ViewBuilderWidget({
Key key,
this.entities
}) : super(key: key);
@override
Widget build(BuildContext context) {
return TabBarView(
children: _buildChildren(context)
);
}
List<Widget> _buildChildren(BuildContext context) {
List<Widget> result = [];
entities.forEach((View view){
result.add(view.buildWidget(context));
});
return result;
}
}

View File

@ -1,168 +0,0 @@
part of 'main.dart';
class View {
List<Entity> childEntitiesAsBadges;
Map<String, CardSkeleton> childEntitiesAsCards;
int count;
List<Entity> entities;
View({
Key key,
this.count,
this.entities
}) {
childEntitiesAsBadges = [];
childEntitiesAsCards = {};
_filterEntities();
}
Widget buildWidget(BuildContext context) {
return ViewWidget(
badges: childEntitiesAsBadges,
cards: childEntitiesAsCards,
);
}
void _filterEntities() {
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,
groupEntity: entity
);
childEntitiesAsCards[entity.entityId].childEntities = entity.childEntities;
}
});
}
}
class ViewWidget extends StatefulWidget {
final List<Entity> badges;
final Map<String, CardSkeleton> cards;
final String displayName;
const ViewWidget({
Key key,
this.badges,
this.cards,
this.displayName
}) : super(key: key);
@override
State<StatefulWidget> createState() {
return ViewWidgetState();
}
}
class ViewWidgetState extends State<ViewWidget> {
StreamSubscription _refreshDataSubscription;
Completer _refreshCompleter;
@override
void initState() {
super.initState();
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) {
_refreshCompleter.complete();
}
});
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
color: Colors.amber,
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: _buildChildren(context),
),
onRefresh: () => _refreshData(),
);
}
List<Widget> _buildChildren(BuildContext context) {
List<Widget> result = [];
if (widget.badges.isNotEmpty) {
result.insert(0,
Wrap(
alignment: WrapAlignment.center,
spacing: 10.0,
runSpacing: 1.0,
children: _buildBadges(context, widget.badges),
)
);
}
widget.cards.forEach((String id, CardSkeleton skeleton){
result.add(
EntityModel(
entity: skeleton.groupEntity,
handleTap: false,
child: CardWidget(
entities: skeleton.childEntities,
friendlyName: skeleton.displayName,
)
)
);
});
return result;
}
List<Widget> _buildBadges(BuildContext context, List<Entity> badges) {
List<Widget> result = [];
badges.forEach((Entity entity) {
result.add(entity.buildBadgeWidget(context));
});
return result;
}
Future _refreshData() {
if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) {
TheLogger.log("Debug","Previous data refresh is still in progress");
} else {
_refreshCompleter = Completer();
eventBus.fire(RefreshDataEvent());
}
return _refreshCompleter.future;
}
@override
void dispose() {
_refreshDataSubscription.cancel();
super.dispose();
}
}
class CardSkeleton {
String displayName;
List<Entity> childEntities;
Entity groupEntity;
CardSkeleton({
Key key,
this.displayName,
this.childEntities,
this.groupEntity}) {
childEntities = [];
}
}

View File

@ -113,7 +113,6 @@ class EntityIcon extends StatelessWidget {
child: Padding(
padding: EdgeInsets.fromLTRB(
Entity.leftWidgetPadding, 0.0, 12.0, 0.0),
//TODO: move createIconWidgetFromEntityData into this widget
child: MaterialDesignIcons.createIconWidgetFromEntityData(
entityModel.entity,
Entity.iconSize,