From 02bfaf7db6c65605060fa9cd7676c8d725cebe0b Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Sat, 25 Apr 2020 17:38:21 +0000 Subject: [PATCH] WIP: Cards build optimization --- lib/cards/alarm_panel_card.dart | 55 +++++ lib/cards/card.class.dart | 6 - lib/cards/card_widget.dart | 216 +------------------- lib/cards/entities_card.dart | 72 +++++++ lib/cards/horizontal_srack_card.dart | 30 +++ lib/cards/markdown_card.dart | 31 +++ lib/cards/media_control_card.dart | 20 ++ lib/cards/unsupported_card.dart | 47 +++++ lib/cards/vertical_stack_card.dart | 23 +++ lib/cards/widgets/card_header.widget.dart | 5 +- lib/entities/entity.class.dart | 10 +- lib/entities/entity_model.widget.dart | 6 +- lib/entities/timer/widgets/timer_state.dart | 2 +- lib/main.dart | 7 + lib/viewWidget.widget.dart | 9 +- 15 files changed, 309 insertions(+), 230 deletions(-) create mode 100644 lib/cards/alarm_panel_card.dart create mode 100644 lib/cards/entities_card.dart create mode 100644 lib/cards/horizontal_srack_card.dart create mode 100644 lib/cards/markdown_card.dart create mode 100644 lib/cards/media_control_card.dart create mode 100644 lib/cards/unsupported_card.dart create mode 100644 lib/cards/vertical_stack_card.dart diff --git a/lib/cards/alarm_panel_card.dart b/lib/cards/alarm_panel_card.dart new file mode 100644 index 0000000..7024f58 --- /dev/null +++ b/lib/cards/alarm_panel_card.dart @@ -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 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 + ) + ) + ); + } + + +} \ No newline at end of file diff --git a/lib/cards/card.class.dart b/lib/cards/card.class.dart index 03ba884..286ae50 100644 --- a/lib/cards/card.class.dart +++ b/lib/cards/card.class.dart @@ -116,10 +116,4 @@ class HACard { }).toList(); } - Widget build(BuildContext context) { - return CardWidget( - card: this, - ); - } - } \ No newline at end of file diff --git a/lib/cards/card_widget.dart b/lib/cards/card_widget.dart index 64cb4c4..f090119 100644 --- a/lib/cards/card_widget.dart +++ b/lib/cards/card_widget.dart @@ -1,10 +1,10 @@ part of '../main.dart'; -class CardWidget extends StatelessWidget { +class LovelaceCard extends StatelessWidget { final HACard card; - const CardWidget({ + const LovelaceCard({ Key key, this.card }) : super(key: key); @@ -44,7 +44,7 @@ class CardWidget extends StatelessWidget { switch (card.type) { case CardType.ENTITIES: { - return _buildEntitiesCard(context); + return EntitiesCard(card: card); } case CardType.GLANCE: { @@ -52,7 +52,7 @@ class CardWidget extends StatelessWidget { } case CardType.MEDIA_CONTROL: { - return _buildMediaControlsCard(context); + return MediaControlsCard(card: card); } case CardType.ENTITY_BUTTON: { @@ -67,227 +67,31 @@ class CardWidget extends StatelessWidget { return GaugeCard(card: card); } -/* case CardType.LIGHT: { - return _buildLightCard(context); - }*/ - case CardType.MARKDOWN: { - return _buildMarkdownCard(context); + return MarkdownCard(card: card); } case CardType.ALARM_PANEL: { - return _buildAlarmPanelCard(context); + return AlarmPanelCard(card: card); } case CardType.HORIZONTAL_STACK: { - if (card.childCards.isNotEmpty) { - List 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,); + return HorizontalStackCard(card: card); } case CardType.VERTICAL_STACK: { - if (card.childCards.isNotEmpty) { - List 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,); + return VerticalStackCard(card: card); } default: { if ((card.linkedEntityWrapper == null) && (card.entities.isNotEmpty)) { - return _buildEntitiesCard(context); + return EntitiesCard(card: card); } else { - return _buildUnsupportedCard(context); + return UnsupportedCard(card: card); } } } } - Widget _buildEntitiesCard(BuildContext context) { - List entitiesToShow = card.getEntitiesToShow(); - if (entitiesToShow.isEmpty && !card.showEmpty) { - return Container(height: 0.0, width: 0.0,); - } - List body = []; - Widget headerSwitch; - if (card.showHeaderToggle) { - bool headerToggleVal = entitiesToShow.any((EntityWrapper en){ return en.entity.state == EntityState.on; }); - List entitiesToToggle = entitiesToShow.where((EntityWrapper enw) { - return ["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 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 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 body = []; - body.add( - CardHeader( - name: card.name ?? "" - ) - ); - List result = []; - if (card.linkedEntityWrapper != null) { - result.addAll([ - 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([ - 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 - ) - ); - } - } diff --git a/lib/cards/entities_card.dart b/lib/cards/entities_card.dart new file mode 100644 index 0000000..8b12a1c --- /dev/null +++ b/lib/cards/entities_card.dart @@ -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 entitiesToShow = card.getEntitiesToShow(); + if (entitiesToShow.isEmpty && !card.showEmpty) { + return Container(height: 0.0, width: 0.0,); + } + List body = []; + Widget headerSwitch; + if (card.showHeaderToggle) { + bool headerToggleVal = entitiesToShow.any((EntityWrapper en){ return en.entity.state == EntityState.on; }); + List entitiesToToggle = entitiesToShow.where((EntityWrapper enw) { + return ["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 + ), + ) + ); + } + + +} \ No newline at end of file diff --git a/lib/cards/horizontal_srack_card.dart b/lib/cards/horizontal_srack_card.dart new file mode 100644 index 0000000..05157fe --- /dev/null +++ b/lib/cards/horizontal_srack_card.dart @@ -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 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,); + } + + +} \ No newline at end of file diff --git a/lib/cards/markdown_card.dart b/lib/cards/markdown_card.dart new file mode 100644 index 0000000..68a8f58 --- /dev/null +++ b/lib/cards/markdown_card.dart @@ -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: [ + CardHeader(name: card.name), + MarkdownBody( + data: card.content, + ) + ], + ), + ) + ); + } + + +} \ No newline at end of file diff --git a/lib/cards/media_control_card.dart b/lib/cards/media_control_card.dart new file mode 100644 index 0000000..aaf225b --- /dev/null +++ b/lib/cards/media_control_card.dart @@ -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() + ) + ); + } + + +} \ No newline at end of file diff --git a/lib/cards/unsupported_card.dart b/lib/cards/unsupported_card.dart new file mode 100644 index 0000000..af60056 --- /dev/null +++ b/lib/cards/unsupported_card.dart @@ -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 body = []; + body.add( + CardHeader( + name: card.name ?? "" + ) + ); + List result = []; + if (card.linkedEntityWrapper != null) { + result.addAll([ + 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([ + 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 + ) + ); + } + + +} \ No newline at end of file diff --git a/lib/cards/vertical_stack_card.dart b/lib/cards/vertical_stack_card.dart new file mode 100644 index 0000000..d47dafa --- /dev/null +++ b/lib/cards/vertical_stack_card.dart @@ -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,); + } + + +} \ No newline at end of file diff --git a/lib/cards/widgets/card_header.widget.dart b/lib/cards/widgets/card_header.widget.dart index 736f663..3f4554e 100644 --- a/lib/cards/widgets/card_header.widget.dart +++ b/lib/cards/widgets/card_header.widget.dart @@ -5,8 +5,9 @@ class CardHeader extends StatelessWidget { final String name; final Widget trailing; 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 Widget build(BuildContext context) { @@ -21,7 +22,7 @@ class CardHeader extends StatelessWidget { style: Theme.of(context).textTheme.headline), ); } else { - result = new Container(width: 0.0, height: Sizes.rowPadding); + result = new Container(width: 0.0, height: emptyPadding); } return result; } diff --git a/lib/entities/entity.class.dart b/lib/entities/entity.class.dart index 4aee940..dd6b193 100644 --- a/lib/entities/entity.class.dart +++ b/lib/entities/entity.class.dart @@ -76,7 +76,7 @@ class Entity { String entityPicture; String state; String displayState; - DateTime _lastUpdated; + DateTime lastUpdatedTimestamp; int statelessType = 0; List childEntities = []; @@ -144,7 +144,7 @@ class Entity { Entity.weblink({String url, String name, String icon}) { statelessType = StatelessEntityType.WEBLINK; - entityId = "custom.custom"; //TODO wtf?? + entityId = "custom.custom"; attributes = {"hidden": false, "friendly_name": "${name ?? url}", "icon": "${icon ?? 'mdi:link'}"}; } @@ -155,7 +155,7 @@ class Entity { deviceClass = attributes["device_class"]; state = rawData["state"] is bool ? (rawData["state"] ? EntityState.on : EntityState.off) : rawData["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); } @@ -227,11 +227,11 @@ class Entity { } String _getLastUpdatedFormatted() { - if (_lastUpdated == null) { + if (lastUpdatedTimestamp == null) { return "-"; } else { DateTime now = DateTime.now(); - Duration d = now.difference(_lastUpdated); + Duration d = now.difference(lastUpdatedTimestamp); String text; int v; if (d.inDays == 0) { diff --git a/lib/entities/entity_model.widget.dart b/lib/entities/entity_model.widget.dart index 1b5477a..177be07 100644 --- a/lib/entities/entity_model.widget.dart +++ b/lib/entities/entity_model.widget.dart @@ -12,11 +12,11 @@ class EntityModel extends InheritedWidget { final bool handleTap; static EntityModel of(BuildContext context) { - return context.inheritFromWidgetOfExactType(EntityModel); + return context.dependOnInheritedWidgetOfExactType(); } @override - bool updateShouldNotify(InheritedWidget oldWidget) { - return true; + bool updateShouldNotify(EntityModel oldWidget) { + return entityWrapper.entity.lastUpdatedTimestamp != oldWidget.entityWrapper.entity.lastUpdatedTimestamp; } } \ No newline at end of file diff --git a/lib/entities/timer/widgets/timer_state.dart b/lib/entities/timer/widgets/timer_state.dart index 4ae65ef..c427952 100644 --- a/lib/entities/timer/widgets/timer_state.dart +++ b/lib/entities/timer/widgets/timer_state.dart @@ -27,7 +27,7 @@ class _TimerStateState extends State { try { int passed = DateTime .now() - .difference(entity._lastUpdated) + .difference(entity.lastUpdatedTimestamp) .inSeconds; remaining = Duration(seconds: entity.duration.inSeconds - passed); } catch (e) { diff --git a/lib/main.dart b/lib/main.dart index 76115d5..da469fb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -137,6 +137,13 @@ part 'types/ha_error.dart'; part 'types/event_bus_events.dart'; part 'cards/gauge_card.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 'pages/play_media.page.dart'; part 'entities/entity_page_layout.widget.dart'; diff --git a/lib/viewWidget.widget.dart b/lib/viewWidget.widget.dart index 2fc6fa3..d1f6f7a 100644 --- a/lib/viewWidget.widget.dart +++ b/lib/viewWidget.widget.dart @@ -21,16 +21,11 @@ class ViewWidget extends StatelessWidget { if (this.view.cards.isNotEmpty) { cardsContainer = DynamicMultiColumnLayout( minColumnWidth: Sizes.minViewColumnWidth, - children: this.view.cards.map((card) => card.build(context)).toList(), + children: this.view.cards.map((card) => LovelaceCard(card: card)).toList(), ); } else { cardsContainer = Container(); } - return ListView( - shrinkWrap: false, - padding: EdgeInsets.all(0), - children: this.view.cards.map((card) => card.build(context)).toList() - ); return ListView( shrinkWrap: true, padding: EdgeInsets.all(0), @@ -44,7 +39,7 @@ class ViewWidget extends StatelessWidget { Widget _buildPanelChild(BuildContext context) { if (this.view.cards != null && this.view.cards.isNotEmpty) { - return this.view.cards[0].build(context); + return LovelaceCard(card: this.view.cards[0]); } else { return Container(width: 0, height: 0); }