Resolves #125 UI building refactored
This commit is contained in:
parent
17a3bd8d35
commit
284e7ba451
@ -1,9 +0,0 @@
|
|||||||
part of 'main.dart';
|
|
||||||
|
|
||||||
class Badge {
|
|
||||||
String _entityId;
|
|
||||||
|
|
||||||
Badge(String groupId) {
|
|
||||||
_entityId = groupId;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +1,54 @@
|
|||||||
part of 'main.dart';
|
part of 'main.dart';
|
||||||
|
|
||||||
class HACard {
|
class HACard extends StatelessWidget {
|
||||||
String _entityId;
|
|
||||||
List _entities;
|
|
||||||
String _friendlyName;
|
|
||||||
|
|
||||||
List get entities => _entities;
|
final List<Entity> entities;
|
||||||
String get friendlyName => _friendlyName;
|
final String friendlyName;
|
||||||
|
|
||||||
HACard(String groupId, String friendlyName) {
|
const HACard({
|
||||||
_entityId = groupId;
|
Key key,
|
||||||
_entities = [];
|
this.entities,
|
||||||
_friendlyName = friendlyName;
|
this.friendlyName
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
List<Widget> body = [];
|
||||||
|
body.add(_buildCardHeader());
|
||||||
|
body.addAll(_buildCardBody(context));
|
||||||
|
return Card(
|
||||||
|
child: new Column(mainAxisSize: MainAxisSize.min, children: body)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addEntity(String entityId) {
|
Widget _buildCardHeader() {
|
||||||
_entities.add(entityId);
|
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) {
|
List<Widget> _buildCardBody(BuildContext context) {
|
||||||
_entities.addAll(entities);
|
List<Widget> 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -8,6 +8,11 @@ class Entity {
|
|||||||
"unknown": Colors.black12,
|
"unknown": Colors.black12,
|
||||||
"playing": Colors.amber
|
"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 RIGHT_WIDGET_PADDING = 14.0;
|
||||||
static const LEFT_WIDGET_PADDING = 8.0;
|
static const LEFT_WIDGET_PADDING = 8.0;
|
||||||
static const EXTENDED_WIDGET_HEIGHT = 50.0;
|
static const EXTENDED_WIDGET_HEIGHT = 50.0;
|
||||||
@ -25,6 +30,8 @@ class Entity {
|
|||||||
String assumedState;
|
String assumedState;
|
||||||
DateTime _lastUpdated;
|
DateTime _lastUpdated;
|
||||||
|
|
||||||
|
List<Entity> childEntities = [];
|
||||||
|
|
||||||
String get displayName =>
|
String get displayName =>
|
||||||
_attributes["friendly_name"] ?? (_attributes["name"] ?? "_");
|
_attributes["friendly_name"] ?? (_attributes["name"] ?? "_");
|
||||||
String get domain => _domain;
|
String get domain => _domain;
|
||||||
@ -37,11 +44,12 @@ class Entity {
|
|||||||
(_domain == "group") &&
|
(_domain == "group") &&
|
||||||
(_attributes != null ? _attributes["view"] ?? false : false);
|
(_attributes != null ? _attributes["view"] ?? false : false);
|
||||||
bool get isGroup => _domain == "group";
|
bool get isGroup => _domain == "group";
|
||||||
|
bool get isBadge => Entity.badgeDomains.contains(_domain);
|
||||||
String get icon => _attributes["icon"] ?? "";
|
String get icon => _attributes["icon"] ?? "";
|
||||||
bool get isOn => state == "on";
|
bool get isOn => state == "on";
|
||||||
String get entityPicture => _attributes["entity_picture"];
|
String get entityPicture => _attributes["entity_picture"];
|
||||||
String get unitOfMeasurement => _attributes["unit_of_measurement"] ?? "";
|
String get unitOfMeasurement => _attributes["unit_of_measurement"] ?? "";
|
||||||
List get childEntities => _attributes["entity_id"] ?? [];
|
List get childEntityIds => _attributes["entity_id"] ?? [];
|
||||||
String get lastUpdated => _getLastUpdatedFormatted();
|
String get lastUpdated => _getLastUpdatedFormatted();
|
||||||
|
|
||||||
Entity(Map rawData) {
|
Entity(Map rawData) {
|
||||||
@ -159,6 +167,8 @@ class _EntityWidgetState extends State<EntityWidget> {
|
|||||||
_buildLastUpdatedWidget()
|
_buildLastUpdatedWidget()
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
} else if (widget.widgetType == EntityWidgetType.badge) {
|
||||||
|
return _buildBadgeWidget(context);
|
||||||
} else {
|
} else {
|
||||||
TheLogger.log("Error", "Unknown entity widget type: ${widget.widgetType}");
|
TheLogger.log("Error", "Unknown entity widget type: ${widget.widgetType}");
|
||||||
}
|
}
|
||||||
@ -247,4 +257,115 @@ class _EntityWidgetState extends State<EntityWidget> {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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: <Widget>[
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0),
|
||||||
|
width: 50.0,
|
||||||
|
height: 50.0,
|
||||||
|
decoration: new BoxDecoration(
|
||||||
|
// Circle shape
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Colors.white,
|
||||||
|
// The border you want
|
||||||
|
border: new Border.all(
|
||||||
|
width: 2.0,
|
||||||
|
color: iconColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
overflow: Overflow.visible,
|
||||||
|
children: <Widget>[
|
||||||
|
Positioned(
|
||||||
|
width: 46.0,
|
||||||
|
height: 46.0,
|
||||||
|
top: 0.0,
|
||||||
|
left: 0.0,
|
||||||
|
child: badgeIcon,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
//width: 50.0,
|
||||||
|
bottom: -9.0,
|
||||||
|
left: -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ class EntityCollection {
|
|||||||
Map<String, Entity> _entities;
|
Map<String, Entity> _entities;
|
||||||
List<String> viewList;
|
List<String> viewList;
|
||||||
|
|
||||||
|
bool get isEmpty => _entities.isEmpty;
|
||||||
|
|
||||||
EntityCollection() {
|
EntityCollection() {
|
||||||
_entities = {};
|
_entities = {};
|
||||||
viewList = [];
|
viewList = [];
|
||||||
@ -57,6 +59,16 @@ class EntityCollection {
|
|||||||
return _entities[entityId];
|
return _entities[entityId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Entity> getAll(List ids) {
|
||||||
|
List<Entity> result = [];
|
||||||
|
_entities.forEach((id, Entity entity){
|
||||||
|
if (ids.contains(id)) {
|
||||||
|
result.add(entity);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
bool isExist(String entityId) {
|
bool isExist(String entityId) {
|
||||||
return _entities[entityId] != null;
|
return _entities[entityId] != null;
|
||||||
}
|
}
|
||||||
@ -76,7 +88,7 @@ class EntityCollection {
|
|||||||
entities.forEach((entiyId) {
|
entities.forEach((entiyId) {
|
||||||
bool foundInGroup = false;
|
bool foundInGroup = false;
|
||||||
result["userGroups"].forEach((userGroupId) {
|
result["userGroups"].forEach((userGroupId) {
|
||||||
if (_entities[userGroupId].childEntities.contains(entiyId)) {
|
if (_entities[userGroupId].childEntityIds.contains(entiyId)) {
|
||||||
foundInGroup = true;
|
foundInGroup = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -14,7 +14,7 @@ class HomeAssistant {
|
|||||||
int _subscriptionMessageId = 0;
|
int _subscriptionMessageId = 0;
|
||||||
int _configMessageId = 0;
|
int _configMessageId = 0;
|
||||||
EntityCollection _entities;
|
EntityCollection _entities;
|
||||||
UIBuilder _uiBuilder;
|
ViewBuilder _viewBuilder;
|
||||||
Map _instanceConfig = {};
|
Map _instanceConfig = {};
|
||||||
|
|
||||||
Completer _fetchCompleter;
|
Completer _fetchCompleter;
|
||||||
@ -33,13 +33,11 @@ class HomeAssistant {
|
|||||||
|
|
||||||
String get locationName => _instanceConfig["location_name"] ?? "";
|
String get locationName => _instanceConfig["location_name"] ?? "";
|
||||||
int get viewsCount => _entities.viewList.length ?? 0;
|
int get viewsCount => _entities.viewList.length ?? 0;
|
||||||
UIBuilder get uiBuilder => _uiBuilder;
|
|
||||||
|
|
||||||
EntityCollection get entities => _entities;
|
EntityCollection get entities => _entities;
|
||||||
|
|
||||||
HomeAssistant() {
|
HomeAssistant() {
|
||||||
_entities = EntityCollection();
|
_entities = EntityCollection();
|
||||||
_uiBuilder = UIBuilder();
|
|
||||||
_messageQueue = SendMessageQueue(messageExpirationTime);
|
_messageQueue = SendMessageQueue(messageExpirationTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,9 +300,13 @@ class HomeAssistant {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_entities.parse(response["result"]);
|
_entities.parse(response["result"]);
|
||||||
_uiBuilder.build(_entities);
|
_viewBuilder = ViewBuilder(entityCollection: _entities);
|
||||||
_statesCompleter.complete();
|
_statesCompleter.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget buildViews(BuildContext context) {
|
||||||
|
return _viewBuilder.buildWidget(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SendMessageQueue {
|
class SendMessageQueue {
|
||||||
|
240
lib/main.dart
240
lib/main.dart
@ -27,10 +27,9 @@ part 'entity.page.dart';
|
|||||||
part 'utils.class.dart';
|
part 'utils.class.dart';
|
||||||
part 'mdi.class.dart';
|
part 'mdi.class.dart';
|
||||||
part 'entity_collection.class.dart';
|
part 'entity_collection.class.dart';
|
||||||
part 'ui_builder_class.dart';
|
part 'view_builder.class.dart';
|
||||||
part 'view_class.dart';
|
part 'view_class.dart';
|
||||||
part 'card_class.dart';
|
part 'card_class.dart';
|
||||||
part 'badge_class.dart';
|
|
||||||
|
|
||||||
EventBus eventBus = new EventBus();
|
EventBus eventBus = new EventBus();
|
||||||
const String appName = "HA Client";
|
const String appName = "HA Client";
|
||||||
@ -99,14 +98,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
StreamSubscription _settingsSubscription;
|
StreamSubscription _settingsSubscription;
|
||||||
StreamSubscription _serviceCallSubscription;
|
StreamSubscription _serviceCallSubscription;
|
||||||
StreamSubscription _showEntityPageSubscription;
|
StreamSubscription _showEntityPageSubscription;
|
||||||
|
StreamSubscription _refreshDataSubscription;
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
bool _settingsLoaded = false;
|
bool _settingsLoaded = false;
|
||||||
|
|
||||||
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();
|
||||||
@ -193,6 +188,12 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
_showEntityPage(event.entity);
|
_showEntityPage(event.entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_refreshDataSubscription == null) {
|
||||||
|
_refreshDataSubscription = eventBus.on<RefreshDataEvent>().listen((event){
|
||||||
|
_refreshData();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_refreshData() async {
|
_refreshData() async {
|
||||||
@ -212,6 +213,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
_setErrorState(e);
|
_setErrorState(e);
|
||||||
});
|
});
|
||||||
|
eventBus.fire(RefreshDataFinishedEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
_setErrorState(e) {
|
_setErrorState(e) {
|
||||||
@ -235,217 +237,22 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildViews() {
|
List<Tab> buildUIViewTabs() {
|
||||||
List<Widget> result = [];
|
//TODO move somewhere to ViewBuilder
|
||||||
if ((_entities != null) && (!_homeAssistant.uiBuilder.isEmpty)) {
|
List<Tab> result = [];
|
||||||
_homeAssistant.uiBuilder.views.forEach((viewId, view) {
|
if (!_entities.isEmpty) {
|
||||||
|
if (!_entities.hasDefaultView) {
|
||||||
result.add(
|
result.add(
|
||||||
RefreshIndicator(
|
Tab(
|
||||||
color: Colors.amber,
|
icon:
|
||||||
child: ListView(
|
Icon(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
MaterialDesignIcons.createIconDataFromIconName("mdi:home-assistant"),
|
||||||
children: _buildSingleView(view),
|
size: 24.0,
|
||||||
),
|
)
|
||||||
onRefresh: () => _refreshData(),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _buildSingleView(View view) {
|
|
||||||
List<Widget> 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));
|
|
||||||
}
|
}
|
||||||
});
|
_entities.viewList.forEach((viewId) {
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _buildBadges( Map<String, Badge> badges) {
|
|
||||||
List<Widget> 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: <Widget>[
|
|
||||||
Container(
|
|
||||||
margin: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0),
|
|
||||||
width: 50.0,
|
|
||||||
height: 50.0,
|
|
||||||
decoration: new BoxDecoration(
|
|
||||||
// Circle shape
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
color: Colors.white,
|
|
||||||
// The border you want
|
|
||||||
border: new Border.all(
|
|
||||||
width: 2.0,
|
|
||||||
color: iconColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Stack(
|
|
||||||
overflow: Overflow.visible,
|
|
||||||
children: <Widget>[
|
|
||||||
Positioned(
|
|
||||||
width: 46.0,
|
|
||||||
height: 46.0,
|
|
||||||
top: 0.0,
|
|
||||||
left: 0.0,
|
|
||||||
child: badgeIcon,
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
//width: 50.0,
|
|
||||||
bottom: -9.0,
|
|
||||||
left: -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<Widget> 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<Widget> _buildCardBody(List ids) {
|
|
||||||
List<Widget> 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<Tab> buildUIViewTabs() {
|
|
||||||
List<Tab> result = [];
|
|
||||||
if ((_entities != null) && (!_homeAssistant.uiBuilder.isEmpty)) {
|
|
||||||
_homeAssistant.uiBuilder.views.forEach((viewId, view) {
|
|
||||||
result.add(
|
result.add(
|
||||||
Tab(
|
Tab(
|
||||||
icon: MaterialDesignIcons.createIconWidgetFromEntityData(_entities.get(viewId), 24.0, null) ??
|
icon: MaterialDesignIcons.createIconWidgetFromEntityData(_entities.get(viewId), 24.0, null) ??
|
||||||
@ -652,9 +459,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
:
|
:
|
||||||
TabBarView(
|
_homeAssistant.buildViews(context)
|
||||||
children: _buildViews()
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -679,6 +484,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
if (_settingsSubscription != null) _settingsSubscription.cancel();
|
if (_settingsSubscription != null) _settingsSubscription.cancel();
|
||||||
if (_serviceCallSubscription != null) _serviceCallSubscription.cancel();
|
if (_serviceCallSubscription != null) _serviceCallSubscription.cancel();
|
||||||
if (_showEntityPageSubscription != null) _showEntityPageSubscription.cancel();
|
if (_showEntityPageSubscription != null) _showEntityPageSubscription.cancel();
|
||||||
|
if (_refreshDataSubscription != null) _refreshDataSubscription.cancel();
|
||||||
_homeAssistant.disconnect();
|
_homeAssistant.disconnect();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
part of 'main.dart';
|
|
||||||
|
|
||||||
class UIBuilder {
|
|
||||||
EntityCollection _entities;
|
|
||||||
Map<String, View> _views;
|
|
||||||
static List badgeDomains = ["alarm_control_panel", "binary_sensor", "device_tracker", "updater", "sun", "timer", "sensor"];
|
|
||||||
|
|
||||||
bool get isEmpty => _views.length == 0;
|
|
||||||
Map<String, View> 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<String, List<String>> 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<String> 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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -56,6 +56,14 @@ class SettingsChangedEvent {
|
|||||||
SettingsChangedEvent(this.reconnect);
|
SettingsChangedEvent(this.reconnect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RefreshDataEvent {
|
||||||
|
RefreshDataEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class RefreshDataFinishedEvent {
|
||||||
|
RefreshDataFinishedEvent();
|
||||||
|
}
|
||||||
|
|
||||||
class ServiceCallEvent {
|
class ServiceCallEvent {
|
||||||
String domain;
|
String domain;
|
||||||
String service;
|
String service;
|
||||||
|
102
lib/view_builder.class.dart
Normal file
102
lib/view_builder.class.dart
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
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 = [];
|
||||||
|
//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<View> _composeViews() {
|
||||||
|
List<View> result = [];
|
||||||
|
int counter = 0;
|
||||||
|
entityCollection.viewList.forEach((viewId) {
|
||||||
|
counter += 1;
|
||||||
|
//try {
|
||||||
|
Entity viewGroupEntity = entityCollection.get(viewId);
|
||||||
|
List<Entity> 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<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,53 +1,129 @@
|
|||||||
part of 'main.dart';
|
part of 'main.dart';
|
||||||
|
|
||||||
class View {
|
class View {
|
||||||
String _entityId;
|
List<Entity> childEntitiesAsBadges;
|
||||||
int _count;
|
Map<String, CardSkeleton> childEntitiesAsCards;
|
||||||
Map<String, HACard> cards;
|
|
||||||
Map<String, Badge> badges;
|
|
||||||
|
|
||||||
bool get isThereBadges => (badges != null) && (badges.isNotEmpty);
|
int count;
|
||||||
|
List<Entity> entities;
|
||||||
|
|
||||||
View(String groupId, int viewCount) {
|
View({
|
||||||
_entityId = groupId;
|
Key key,
|
||||||
_count = viewCount;
|
this.count,
|
||||||
cards = {};
|
this.entities
|
||||||
badges = {};
|
}) {
|
||||||
|
childEntitiesAsBadges = [];
|
||||||
|
childEntitiesAsCards = {};
|
||||||
|
_composeEntities();
|
||||||
}
|
}
|
||||||
|
|
||||||
void add(Entity entity) {
|
Widget buildWidget(BuildContext context) {
|
||||||
if (!entity.isGroup) {
|
return ViewWidget(
|
||||||
_addEntityWithoutGroup(entity);
|
badges: childEntitiesAsBadges,
|
||||||
} else {
|
cards: childEntitiesAsCards,
|
||||||
_addCardWithEntities(entity);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addBadge(String entityId) {
|
void _composeEntities() {
|
||||||
badges.addAll({entityId: Badge(entityId)});
|
entities.forEach((Entity entity){
|
||||||
}
|
if (!entity.isGroup) {
|
||||||
|
if (entity.isBadge) {
|
||||||
void _addEntityWithoutGroup(Entity entity) {
|
childEntitiesAsBadges.add(entity);
|
||||||
if (UIBuilder.isBadge(entity.domain)) {
|
} else {
|
||||||
//This is badge
|
String groupIdToAdd = "${entity.domain}.${entity.domain}$count";
|
||||||
_addBadge(entity.entityId);
|
if (childEntitiesAsCards[groupIdToAdd] == null) {
|
||||||
} else {
|
childEntitiesAsCards[groupIdToAdd] = CardSkeleton(
|
||||||
//This is a standalone entity
|
displayName: entity.domain,
|
||||||
String groupIdToAdd = "${entity.domain}.${entity.domain}$_count";
|
);
|
||||||
if (cards[groupIdToAdd] == null) {
|
}
|
||||||
_addCard(groupIdToAdd, 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);
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _addCard(String entityId, String friendlyName) {
|
|
||||||
cards.addAll({"$entityId": HACard(entityId, friendlyName)});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _addCardWithEntities(Entity entity) {
|
|
||||||
cards.addAll({"${entity.entityId}": HACard(entity.entityId, entity.displayName)});
|
|
||||||
cards[entity.entityId].addEntities(entity.childEntities);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ViewWidget extends StatelessWidget {
|
||||||
|
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
|
||||||
|
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 (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;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EntityWidget> _buildBadges(BuildContext context, List<Entity> badges) {
|
||||||
|
List<EntityWidget> result = [];
|
||||||
|
badges.forEach((Entity entity) {
|
||||||
|
result.add(entity.buildWidget(context, EntityWidgetType.badge));
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _refreshData() {
|
||||||
|
Completer refreshCompleter = Completer();
|
||||||
|
|
||||||
|
eventBus.fire(RefreshDataEvent());
|
||||||
|
eventBus.on<RefreshDataFinishedEvent>().listen((event) {
|
||||||
|
refreshCompleter.complete();
|
||||||
|
});
|
||||||
|
|
||||||
|
return refreshCompleter.future;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CardSkeleton {
|
||||||
|
String displayName;
|
||||||
|
List<Entity> childEntities;
|
||||||
|
|
||||||
|
CardSkeleton({Key key, this.displayName, this.childEntities}) {
|
||||||
|
childEntities = [];
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user