WIP: Cards build optimization

This commit is contained in:
Yegor Vialov 2020-04-25 17:38:21 +00:00
parent f488c0810b
commit 02bfaf7db6
15 changed files with 309 additions and 230 deletions

View File

@ -0,0 +1,55 @@
part of '../main.dart';
class AlarmPanelCard extends StatelessWidget {
final HACard card;
const AlarmPanelCard({Key key, this.card}) : super(key: key);
@override
Widget build(BuildContext context) {
List<Widget> body = [];
body.add(CardHeader(
name: card.name ?? "",
subtitle: Text("${card.linkedEntityWrapper.entity.displayState}",
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
EntityIcon(
size: 50.0,
),
Container(
width: 26.0,
child: IconButton(
padding: EdgeInsets.all(0.0),
alignment: Alignment.centerRight,
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
"mdi:dots-vertical")),
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity: card.linkedEntityWrapper.entity))
)
)
]
),
));
body.add(
AlarmControlPanelControlsWidget(
extended: true,
states: card.states,
)
);
return CardWrapper(
child: EntityModel(
entityWrapper: card.linkedEntityWrapper,
handleTap: null,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: body
)
)
);
}
}

View File

@ -116,10 +116,4 @@ class HACard {
}).toList(); }).toList();
} }
Widget build(BuildContext context) {
return CardWidget(
card: this,
);
}
} }

View File

@ -1,10 +1,10 @@
part of '../main.dart'; part of '../main.dart';
class CardWidget extends StatelessWidget { class LovelaceCard extends StatelessWidget {
final HACard card; final HACard card;
const CardWidget({ const LovelaceCard({
Key key, Key key,
this.card this.card
}) : super(key: key); }) : super(key: key);
@ -44,7 +44,7 @@ class CardWidget extends StatelessWidget {
switch (card.type) { switch (card.type) {
case CardType.ENTITIES: { case CardType.ENTITIES: {
return _buildEntitiesCard(context); return EntitiesCard(card: card);
} }
case CardType.GLANCE: { case CardType.GLANCE: {
@ -52,7 +52,7 @@ class CardWidget extends StatelessWidget {
} }
case CardType.MEDIA_CONTROL: { case CardType.MEDIA_CONTROL: {
return _buildMediaControlsCard(context); return MediaControlsCard(card: card);
} }
case CardType.ENTITY_BUTTON: { case CardType.ENTITY_BUTTON: {
@ -67,227 +67,31 @@ class CardWidget extends StatelessWidget {
return GaugeCard(card: card); return GaugeCard(card: card);
} }
/* case CardType.LIGHT: {
return _buildLightCard(context);
}*/
case CardType.MARKDOWN: { case CardType.MARKDOWN: {
return _buildMarkdownCard(context); return MarkdownCard(card: card);
} }
case CardType.ALARM_PANEL: { case CardType.ALARM_PANEL: {
return _buildAlarmPanelCard(context); return AlarmPanelCard(card: card);
} }
case CardType.HORIZONTAL_STACK: { case CardType.HORIZONTAL_STACK: {
if (card.childCards.isNotEmpty) { return HorizontalStackCard(card: card);
List<Widget> children = [];
children = card.childCards.map((childCard) => Flexible(
fit: FlexFit.tight,
child: childCard.build(context),
)
).toList();
return IntrinsicHeight(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: children,
),
);
}
return Container(height: 0.0, width: 0.0,);
} }
case CardType.VERTICAL_STACK: { case CardType.VERTICAL_STACK: {
if (card.childCards.isNotEmpty) { return VerticalStackCard(card: card);
List<Widget> children = card.childCards.map((childCard) => childCard.build(context)).toList();
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: children,
);
}
return Container(height: 0.0, width: 0.0,);
} }
default: { default: {
if ((card.linkedEntityWrapper == null) && (card.entities.isNotEmpty)) { if ((card.linkedEntityWrapper == null) && (card.entities.isNotEmpty)) {
return _buildEntitiesCard(context); return EntitiesCard(card: card);
} else { } else {
return _buildUnsupportedCard(context); return UnsupportedCard(card: card);
} }
} }
} }
} }
Widget _buildEntitiesCard(BuildContext context) {
List<EntityWrapper> entitiesToShow = card.getEntitiesToShow();
if (entitiesToShow.isEmpty && !card.showEmpty) {
return Container(height: 0.0, width: 0.0,);
}
List<Widget> body = [];
Widget headerSwitch;
if (card.showHeaderToggle) {
bool headerToggleVal = entitiesToShow.any((EntityWrapper en){ return en.entity.state == EntityState.on; });
List<String> entitiesToToggle = entitiesToShow.where((EntityWrapper enw) {
return <String>["switch", "light", "automation", "input_boolean"].contains(enw.entity.domain);
}).map((EntityWrapper en) {
return en.entity.entityId;
}).toList();
headerSwitch = Switch(
value: headerToggleVal,
onChanged: (val) {
if (entitiesToToggle.isNotEmpty) {
ConnectionManager().callService(
domain: "homeassistant",
service: val ? "turn_on" : "turn_off",
entityId: entitiesToToggle
);
}
},
);
}
body.add(
CardHeader(
name: card.name,
trailing: headerSwitch
)
);
body.addAll(
entitiesToShow.map((EntityWrapper entity) {
return Padding(
padding: EdgeInsets.fromLTRB(0.0, 4.0, 0.0, 4.0),
child: EntityModel(
entityWrapper: entity,
handleTap: true,
child: entity.entity.buildDefaultWidget(context)
),
);
})
);
return CardWrapper(
child: Padding(
padding: EdgeInsets.only(
right: Sizes.rightWidgetPadding,
left: Sizes.leftWidgetPadding,
bottom: Sizes.rowPadding,
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: body
),
)
);
}
Widget _buildMarkdownCard(BuildContext context) {
if (card.content == null) {
return Container(height: 0.0, width: 0.0,);
}
List<Widget> body = [];
body.add(CardHeader(name: card.name));
body.add(MarkdownBody(data: card.content));
return CardWrapper(
child: Padding(
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
child: new Column(mainAxisSize: MainAxisSize.min, children: body),
)
);
}
Widget _buildAlarmPanelCard(BuildContext context) {
List<Widget> body = [];
body.add(CardHeader(
name: card.name ?? "",
subtitle: Text("${card.linkedEntityWrapper.entity.displayState}",
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
EntityIcon(
size: 50.0,
),
Container(
width: 26.0,
child: IconButton(
padding: EdgeInsets.all(0.0),
alignment: Alignment.centerRight,
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
"mdi:dots-vertical")),
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity: card.linkedEntityWrapper.entity))
)
)
]
),
));
body.add(
AlarmControlPanelControlsWidget(
extended: true,
states: card.states,
)
);
return CardWrapper(
child: EntityModel(
entityWrapper: card.linkedEntityWrapper,
handleTap: null,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: body
)
)
);
}
Widget _buildMediaControlsCard(BuildContext context) {
return CardWrapper(
child: EntityModel(
entityWrapper: card.linkedEntityWrapper,
handleTap: null,
child: MediaPlayerWidget()
)
);
}
Widget _buildUnsupportedCard(BuildContext context) {
List<Widget> body = [];
body.add(
CardHeader(
name: card.name ?? ""
)
);
List<Widget> result = [];
if (card.linkedEntityWrapper != null) {
result.addAll(<Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
child: EntityModel(
entityWrapper: card.linkedEntityWrapper,
handleTap: true,
child: card.linkedEntityWrapper.entity.buildDefaultWidget(context)
),
)
]);
} else {
result.addAll(<Widget>[
Padding(
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
child: Text("'${card.type}' card is not supported yet"),
),
]);
}
body.addAll(result);
return CardWrapper(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: body
)
);
}
} }

View File

@ -0,0 +1,72 @@
part of '../main.dart';
class EntitiesCard extends StatelessWidget {
final HACard card;
const EntitiesCard({Key key, this.card}) : super(key: key);
@override
Widget build(BuildContext context) {
List<EntityWrapper> entitiesToShow = card.getEntitiesToShow();
if (entitiesToShow.isEmpty && !card.showEmpty) {
return Container(height: 0.0, width: 0.0,);
}
List<Widget> body = [];
Widget headerSwitch;
if (card.showHeaderToggle) {
bool headerToggleVal = entitiesToShow.any((EntityWrapper en){ return en.entity.state == EntityState.on; });
List<String> entitiesToToggle = entitiesToShow.where((EntityWrapper enw) {
return <String>["switch", "light", "automation", "input_boolean"].contains(enw.entity.domain);
}).map((EntityWrapper en) {
return en.entity.entityId;
}).toList();
headerSwitch = Switch(
value: headerToggleVal,
onChanged: (val) {
if (entitiesToToggle.isNotEmpty) {
ConnectionManager().callService(
domain: "homeassistant",
service: val ? "turn_on" : "turn_off",
entityId: entitiesToToggle
);
}
},
);
}
body.add(
CardHeader(
name: card.name,
trailing: headerSwitch,
emptyPadding: Sizes.rowPadding
)
);
body.addAll(
entitiesToShow.map((EntityWrapper entity) {
return Padding(
padding: EdgeInsets.fromLTRB(0.0, 4.0, 0.0, 4.0),
child: EntityModel(
entityWrapper: entity,
handleTap: true,
child: entity.entity.buildDefaultWidget(context)
),
);
})
);
return CardWrapper(
child: Padding(
padding: EdgeInsets.only(
right: Sizes.rightWidgetPadding,
left: Sizes.leftWidgetPadding,
bottom: Sizes.rowPadding,
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: body
),
)
);
}
}

View File

@ -0,0 +1,30 @@
part of '../main.dart';
class HorizontalStackCard extends StatelessWidget {
final HACard card;
const HorizontalStackCard({Key key, this.card}) : super(key: key);
@override
Widget build(BuildContext context) {
if (card.childCards.isNotEmpty) {
List<Widget> children = [];
children = card.childCards.map((childCard) => Flexible(
fit: FlexFit.tight,
child: LovelaceCard(card: childCard)
)
).toList();
return IntrinsicHeight(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: children,
),
);
}
return Container(height: 0.0, width: 0.0,);
}
}

View File

@ -0,0 +1,31 @@
part of '../main.dart';
class MarkdownCard extends StatelessWidget {
final HACard card;
const MarkdownCard({Key key, this.card}) : super(key: key);
@override
Widget build(BuildContext context) {
if (card.content == null) {
return Container(height: 0.0, width: 0.0,);
}
return CardWrapper(
child: Padding(
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
CardHeader(name: card.name),
MarkdownBody(
data: card.content,
)
],
),
)
);
}
}

View File

@ -0,0 +1,20 @@
part of '../main.dart';
class MediaControlsCard extends StatelessWidget {
final HACard card;
const MediaControlsCard({Key key, this.card}) : super(key: key);
@override
Widget build(BuildContext context) {
return CardWrapper(
child: EntityModel(
entityWrapper: card.linkedEntityWrapper,
handleTap: null,
child: MediaPlayerWidget()
)
);
}
}

View File

@ -0,0 +1,47 @@
part of '../main.dart';
class UnsupportedCard extends StatelessWidget {
final HACard card;
const UnsupportedCard({Key key, this.card}) : super(key: key);
@override
Widget build(BuildContext context) {
List<Widget> body = [];
body.add(
CardHeader(
name: card.name ?? ""
)
);
List<Widget> result = [];
if (card.linkedEntityWrapper != null) {
result.addAll(<Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
child: EntityModel(
entityWrapper: card.linkedEntityWrapper,
handleTap: true,
child: card.linkedEntityWrapper.entity.buildDefaultWidget(context)
),
)
]);
} else {
result.addAll(<Widget>[
Padding(
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
child: Text("'${card.type}' card is not supported yet"),
),
]);
}
body.addAll(result);
return CardWrapper(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: body
)
);
}
}

View File

@ -0,0 +1,23 @@
part of '../main.dart';
class VerticalStackCard extends StatelessWidget {
final HACard card;
const VerticalStackCard({Key key, this.card}) : super(key: key);
@override
Widget build(BuildContext context) {
if (card.childCards.isNotEmpty) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: card.childCards.map(
(childCard) => LovelaceCard(card: childCard)
).toList(),
);
}
return Container(height: 0.0, width: 0.0,);
}
}

View File

@ -5,8 +5,9 @@ class CardHeader extends StatelessWidget {
final String name; final String name;
final Widget trailing; final Widget trailing;
final Widget subtitle; final Widget subtitle;
final double emptyPadding;
const CardHeader({Key key, this.name, this.trailing, this.subtitle}) : super(key: key); const CardHeader({Key key, this.name, this.emptyPadding: 0, this.trailing, this.subtitle}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -21,7 +22,7 @@ class CardHeader extends StatelessWidget {
style: Theme.of(context).textTheme.headline), style: Theme.of(context).textTheme.headline),
); );
} else { } else {
result = new Container(width: 0.0, height: Sizes.rowPadding); result = new Container(width: 0.0, height: emptyPadding);
} }
return result; return result;
} }

View File

@ -76,7 +76,7 @@ class Entity {
String entityPicture; String entityPicture;
String state; String state;
String displayState; String displayState;
DateTime _lastUpdated; DateTime lastUpdatedTimestamp;
int statelessType = 0; int statelessType = 0;
List<Entity> childEntities = []; List<Entity> childEntities = [];
@ -144,7 +144,7 @@ class Entity {
Entity.weblink({String url, String name, String icon}) { Entity.weblink({String url, String name, String icon}) {
statelessType = StatelessEntityType.WEBLINK; statelessType = StatelessEntityType.WEBLINK;
entityId = "custom.custom"; //TODO wtf?? entityId = "custom.custom";
attributes = {"hidden": false, "friendly_name": "${name ?? url}", "icon": "${icon ?? 'mdi:link'}"}; attributes = {"hidden": false, "friendly_name": "${name ?? url}", "icon": "${icon ?? 'mdi:link'}"};
} }
@ -155,7 +155,7 @@ class Entity {
deviceClass = attributes["device_class"]; deviceClass = attributes["device_class"];
state = rawData["state"] is bool ? (rawData["state"] ? EntityState.on : EntityState.off) : rawData["state"]; state = rawData["state"] is bool ? (rawData["state"] ? EntityState.on : EntityState.off) : rawData["state"];
displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? (state.toLowerCase() == 'unknown' ? '-' : state); displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? (state.toLowerCase() == 'unknown' ? '-' : state);
_lastUpdated = DateTime.tryParse(rawData["last_updated"]); lastUpdatedTimestamp = DateTime.tryParse(rawData["last_updated"]);
entityPicture = _getEntityPictureUrl(webHost); entityPicture = _getEntityPictureUrl(webHost);
} }
@ -227,11 +227,11 @@ class Entity {
} }
String _getLastUpdatedFormatted() { String _getLastUpdatedFormatted() {
if (_lastUpdated == null) { if (lastUpdatedTimestamp == null) {
return "-"; return "-";
} else { } else {
DateTime now = DateTime.now(); DateTime now = DateTime.now();
Duration d = now.difference(_lastUpdated); Duration d = now.difference(lastUpdatedTimestamp);
String text; String text;
int v; int v;
if (d.inDays == 0) { if (d.inDays == 0) {

View File

@ -12,11 +12,11 @@ class EntityModel extends InheritedWidget {
final bool handleTap; final bool handleTap;
static EntityModel of(BuildContext context) { static EntityModel of(BuildContext context) {
return context.inheritFromWidgetOfExactType(EntityModel); return context.dependOnInheritedWidgetOfExactType<EntityModel>();
} }
@override @override
bool updateShouldNotify(InheritedWidget oldWidget) { bool updateShouldNotify(EntityModel oldWidget) {
return true; return entityWrapper.entity.lastUpdatedTimestamp != oldWidget.entityWrapper.entity.lastUpdatedTimestamp;
} }
} }

View File

@ -27,7 +27,7 @@ class _TimerStateState extends State<TimerState> {
try { try {
int passed = DateTime int passed = DateTime
.now() .now()
.difference(entity._lastUpdated) .difference(entity.lastUpdatedTimestamp)
.inSeconds; .inSeconds;
remaining = Duration(seconds: entity.duration.inSeconds - passed); remaining = Duration(seconds: entity.duration.inSeconds - passed);
} catch (e) { } catch (e) {

View File

@ -137,6 +137,13 @@ part 'types/ha_error.dart';
part 'types/event_bus_events.dart'; part 'types/event_bus_events.dart';
part 'cards/gauge_card.dart'; part 'cards/gauge_card.dart';
part 'cards/widgets/card_wrapper.widget.dart'; part 'cards/widgets/card_wrapper.widget.dart';
part 'cards/entities_card.dart';
part 'cards/alarm_panel_card.dart';
part 'cards/horizontal_srack_card.dart';
part 'cards/markdown_card.dart';
part 'cards/media_control_card.dart';
part 'cards/unsupported_card.dart';
part 'cards/vertical_stack_card.dart';
part 'cards/glance_card.dart'; part 'cards/glance_card.dart';
part 'pages/play_media.page.dart'; part 'pages/play_media.page.dart';
part 'entities/entity_page_layout.widget.dart'; part 'entities/entity_page_layout.widget.dart';

View File

@ -21,16 +21,11 @@ class ViewWidget extends StatelessWidget {
if (this.view.cards.isNotEmpty) { if (this.view.cards.isNotEmpty) {
cardsContainer = DynamicMultiColumnLayout( cardsContainer = DynamicMultiColumnLayout(
minColumnWidth: Sizes.minViewColumnWidth, minColumnWidth: Sizes.minViewColumnWidth,
children: this.view.cards.map((card) => card.build(context)).toList(), children: this.view.cards.map((card) => LovelaceCard(card: card)).toList(),
); );
} else { } else {
cardsContainer = Container(); cardsContainer = Container();
} }
return ListView(
shrinkWrap: false,
padding: EdgeInsets.all(0),
children: this.view.cards.map((card) => card.build(context)).toList()
);
return ListView( return ListView(
shrinkWrap: true, shrinkWrap: true,
padding: EdgeInsets.all(0), padding: EdgeInsets.all(0),
@ -44,7 +39,7 @@ class ViewWidget extends StatelessWidget {
Widget _buildPanelChild(BuildContext context) { Widget _buildPanelChild(BuildContext context) {
if (this.view.cards != null && this.view.cards.isNotEmpty) { if (this.view.cards != null && this.view.cards.isNotEmpty) {
return this.view.cards[0].build(context); return LovelaceCard(card: this.view.cards[0]);
} else { } else {
return Container(width: 0, height: 0); return Container(width: 0, height: 0);
} }