Resolves #208 Gauge card
This commit is contained in:
50
lib/cards/widgets/button_entity_container.dart
Normal file
50
lib/cards/widgets/button_entity_container.dart
Normal file
@ -0,0 +1,50 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class ButtonEntityContainer extends StatelessWidget {
|
||||
|
||||
ButtonEntityContainer({
|
||||
Key key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||
if (entityWrapper.entity.statelessType == StatelessEntityType.MISSED) {
|
||||
return MissedEntityWidget();
|
||||
}
|
||||
if (entityWrapper.entity.statelessType > StatelessEntityType.MISSED) {
|
||||
return Container(width: 0.0, height: 0.0,);
|
||||
}
|
||||
return InkWell(
|
||||
onTap: () => entityWrapper.handleTap(),
|
||||
onLongPress: () => entityWrapper.handleHold(),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.4,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.fitHeight,
|
||||
child: EntityIcon(
|
||||
padding: EdgeInsets.fromLTRB(2.0, 6.0, 2.0, 2.0),
|
||||
size: Sizes.iconSize,
|
||||
)
|
||||
),
|
||||
),
|
||||
_buildName()
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildName() {
|
||||
return EntityName(
|
||||
padding: EdgeInsets.fromLTRB(Sizes.buttonPadding, 0.0, Sizes.buttonPadding, Sizes.rowPadding),
|
||||
textOverflow: TextOverflow.ellipsis,
|
||||
maxLines: 3,
|
||||
wordsWrap: true,
|
||||
textAlign: TextAlign.center,
|
||||
fontSize: Sizes.nameFontSize,
|
||||
);
|
||||
}
|
||||
}
|
329
lib/cards/widgets/card_widget.dart
Normal file
329
lib/cards/widgets/card_widget.dart
Normal file
@ -0,0 +1,329 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class CardWidget extends StatelessWidget {
|
||||
|
||||
final HACard card;
|
||||
|
||||
const CardWidget({
|
||||
Key key,
|
||||
this.card
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (card.linkedEntityWrapper!= null) {
|
||||
if (card.linkedEntityWrapper.entity.isHidden) {
|
||||
return Container(width: 0.0, height: 0.0,);
|
||||
}
|
||||
if (card.linkedEntityWrapper.entity.statelessType == StatelessEntityType.MISSED) {
|
||||
return EntityModel(
|
||||
entityWrapper: card.linkedEntityWrapper,
|
||||
child: MissedEntityWidget(),
|
||||
handleTap: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (card.conditions.isNotEmpty) {
|
||||
bool showCardByConditions = false;
|
||||
for (var condition in card.conditions) {
|
||||
Entity conditionEntity = HomeAssistant().entities.get(condition['entity']);
|
||||
if (conditionEntity != null &&
|
||||
(condition['state'] != null && conditionEntity.state == condition['state']) ||
|
||||
(condition['state_not'] != null && conditionEntity.state != condition['state_not'])
|
||||
) {
|
||||
showCardByConditions = true;
|
||||
}
|
||||
}
|
||||
if (!showCardByConditions) {
|
||||
return Container(width: 0.0, height: 0.0,);
|
||||
}
|
||||
}
|
||||
|
||||
switch (card.type) {
|
||||
|
||||
case CardType.ENTITIES: {
|
||||
return _buildEntitiesCard(context);
|
||||
}
|
||||
|
||||
case CardType.GLANCE: {
|
||||
return _buildGlanceCard(context);
|
||||
}
|
||||
|
||||
case CardType.MEDIA_CONTROL: {
|
||||
return _buildMediaControlsCard(context);
|
||||
}
|
||||
|
||||
case CardType.ENTITY_BUTTON: {
|
||||
return _buildEntityButtonCard(context);
|
||||
}
|
||||
|
||||
case CardType.GAUGE: {
|
||||
return _buildGaugeCard(context);
|
||||
}
|
||||
|
||||
case CardType.MARKDOWN: {
|
||||
return _buildMarkdownCard(context);
|
||||
}
|
||||
|
||||
case CardType.ALARM_PANEL: {
|
||||
return _buildAlarmPanelCard(context);
|
||||
}
|
||||
|
||||
case CardType.HORIZONTAL_STACK: {
|
||||
if (card.childCards.isNotEmpty) {
|
||||
List<Widget> children = [];
|
||||
card.childCards.forEach((card) {
|
||||
if (card.getEntitiesToShow().isNotEmpty || card.showEmpty) {
|
||||
children.add(
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: card.build(context),
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
return Container(height: 0.0, width: 0.0,);
|
||||
}
|
||||
|
||||
case CardType.VERTICAL_STACK: {
|
||||
if (card.childCards.isNotEmpty) {
|
||||
List<Widget> children = [];
|
||||
card.childCards.forEach((card) {
|
||||
children.add(
|
||||
card.build(context)
|
||||
);
|
||||
});
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
return Container(height: 0.0, width: 0.0,);
|
||||
}
|
||||
|
||||
default: {
|
||||
if ((card.linkedEntityWrapper == null) && (card.entities.isNotEmpty)) {
|
||||
return _buildEntitiesCard(context);
|
||||
} else {
|
||||
return _buildUnsupportedCard(context);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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 = [];
|
||||
body.add(CardHeaderWidget(name: card.name));
|
||||
entitiesToShow.forEach((EntityWrapper entity) {
|
||||
if (!entity.entity.isHidden) {
|
||||
body.add(
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(0.0, 4.0, 0.0, 4.0),
|
||||
child: EntityModel(
|
||||
entityWrapper: entity,
|
||||
handleTap: true,
|
||||
child: entity.entity.buildDefaultWidget(context)
|
||||
),
|
||||
));
|
||||
}
|
||||
});
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: Sizes.rightWidgetPadding, left: Sizes.leftWidgetPadding),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: body),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMarkdownCard(BuildContext context) {
|
||||
if (card.content == null) {
|
||||
return Container(height: 0.0, width: 0.0,);
|
||||
}
|
||||
List<Widget> body = [];
|
||||
body.add(CardHeaderWidget(name: card.name));
|
||||
body.add(MarkdownBody(data: card.content));
|
||||
return Card(
|
||||
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(CardHeaderWidget(
|
||||
name: card.name ?? "",
|
||||
subtitle: Text("${card.linkedEntityWrapper.entity.displayState}",
|
||||
style: TextStyle(
|
||||
color: Colors.grey
|
||||
),
|
||||
),
|
||||
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(card.linkedEntityWrapper.entity))
|
||||
)
|
||||
)
|
||||
]
|
||||
),
|
||||
));
|
||||
body.add(
|
||||
AlarmControlPanelControlsWidget(
|
||||
extended: true,
|
||||
states: card.states,
|
||||
)
|
||||
);
|
||||
return Card(
|
||||
child: EntityModel(
|
||||
entityWrapper: card.linkedEntityWrapper,
|
||||
handleTap: null,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: body
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGlanceCard(BuildContext context) {
|
||||
List<EntityWrapper> entitiesToShow = card.getEntitiesToShow();
|
||||
if (entitiesToShow.isEmpty && !card.showEmpty) {
|
||||
return Container(height: 0.0, width: 0.0,);
|
||||
}
|
||||
List<Widget> rows = [];
|
||||
rows.add(CardHeaderWidget(name: card.name));
|
||||
|
||||
List<Widget> result = [];
|
||||
int columnsCount = entitiesToShow.length >= card.columnsCount ? card.columnsCount : entitiesToShow.length;
|
||||
|
||||
entitiesToShow.forEach((EntityWrapper entity) {
|
||||
result.add(
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1/columnsCount,
|
||||
child: EntityModel(
|
||||
entityWrapper: entity,
|
||||
child: GlanceEntityContainer(
|
||||
showName: card.showName,
|
||||
showState: card.showState,
|
||||
),
|
||||
handleTap: true
|
||||
),
|
||||
)
|
||||
);
|
||||
});
|
||||
rows.add(
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, 2*Sizes.rowPadding),
|
||||
child: Wrap(
|
||||
//alignment: WrapAlignment.spaceAround,
|
||||
runSpacing: Sizes.rowPadding*2,
|
||||
children: result,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
return Card(
|
||||
child: new Column(mainAxisSize: MainAxisSize.min, children: rows)
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMediaControlsCard(BuildContext context) {
|
||||
return Card(
|
||||
child: EntityModel(
|
||||
entityWrapper: card.linkedEntityWrapper,
|
||||
handleTap: null,
|
||||
child: MediaPlayerWidget()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEntityButtonCard(BuildContext context) {
|
||||
card.linkedEntityWrapper.displayName = card.name?.toUpperCase() ??
|
||||
card.linkedEntityWrapper.displayName.toUpperCase();
|
||||
return Card(
|
||||
child: EntityModel(
|
||||
entityWrapper: card.linkedEntityWrapper,
|
||||
child: ButtonEntityContainer(),
|
||||
handleTap: true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGaugeCard(BuildContext context) {
|
||||
card.linkedEntityWrapper.displayName = card.name ??
|
||||
card.linkedEntityWrapper.displayName;
|
||||
return Card(
|
||||
child: EntityModel(
|
||||
entityWrapper: card.linkedEntityWrapper,
|
||||
child: GaugeCardBody(
|
||||
min: card.min,
|
||||
max: card.max,
|
||||
unit: card.unit ?? card.linkedEntityWrapper.entity.unitOfMeasurement,
|
||||
severity: card.severity,
|
||||
),
|
||||
handleTap: true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUnsupportedCard(BuildContext context) {
|
||||
List<Widget> body = [];
|
||||
body.add(CardHeaderWidget(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 Card(
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: body
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
149
lib/cards/widgets/gauge_card_body.dart
Normal file
149
lib/cards/widgets/gauge_card_body.dart
Normal file
@ -0,0 +1,149 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class GaugeCardBody extends StatefulWidget {
|
||||
|
||||
final int min;
|
||||
final int max;
|
||||
final String unit;
|
||||
final Map severity;
|
||||
|
||||
GaugeCardBody({Key key, this.min, this.max, this.unit, this.severity}) : super(key: key);
|
||||
|
||||
@override
|
||||
_GaugeCardBodyState createState() => _GaugeCardBodyState();
|
||||
}
|
||||
|
||||
class _GaugeCardBodyState extends State<GaugeCardBody> {
|
||||
|
||||
List<charts.Series> seriesList;
|
||||
|
||||
List<charts.Series<GaugeSegment, String>> _createData(double value) {
|
||||
double fixedValue;
|
||||
if (value > widget.max) {
|
||||
fixedValue = widget.max.toDouble();
|
||||
} else if (value < widget.min) {
|
||||
fixedValue = widget.min.toDouble();
|
||||
} else {
|
||||
fixedValue = value;
|
||||
}
|
||||
double toShow = ((fixedValue - widget.min) / (widget.max - widget.min)) * 100;
|
||||
Color mainColor;
|
||||
if (widget.severity != null) {
|
||||
if (widget.severity["red"] is int && fixedValue >= widget.severity["red"]) {
|
||||
mainColor = Colors.red;
|
||||
} else if (widget.severity["yellow"] is int && fixedValue >= widget.severity["yellow"]) {
|
||||
mainColor = Colors.amber;
|
||||
} else {
|
||||
mainColor = Colors.green;
|
||||
}
|
||||
} else {
|
||||
mainColor = Colors.green;
|
||||
}
|
||||
final data = [
|
||||
GaugeSegment('Main', toShow, mainColor),
|
||||
GaugeSegment('Rest', 100 - toShow, Colors.black45),
|
||||
];
|
||||
|
||||
return [
|
||||
charts.Series<GaugeSegment, String>(
|
||||
id: 'Segments',
|
||||
domainFn: (GaugeSegment segment, _) => segment.segment,
|
||||
measureFn: (GaugeSegment segment, _) => segment.value,
|
||||
colorFn: (GaugeSegment segment, _) => segment.color,
|
||||
// Set a label accessor to control the text of the arc label.
|
||||
labelAccessorFn: (GaugeSegment segment, _) =>
|
||||
segment.segment == 'Main' ? '${segment.value}' : null,
|
||||
data: data,
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||
|
||||
return InkWell(
|
||||
onTap: () => entityWrapper.handleTap(),
|
||||
onLongPress: () => entityWrapper.handleHold(),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.5,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
overflow: Overflow.clip,
|
||||
children: [
|
||||
LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
double verticalOffset;
|
||||
if(constraints.maxWidth > 150.0) {
|
||||
verticalOffset = 0.2;
|
||||
} else if (constraints.maxWidth > 100.0) {
|
||||
verticalOffset = 0.3;
|
||||
} else {
|
||||
verticalOffset = 0.3;
|
||||
}
|
||||
return FractionallySizedBox(
|
||||
heightFactor: 2,
|
||||
widthFactor: 1,
|
||||
alignment: FractionalOffset(0,verticalOffset),
|
||||
child: charts.PieChart(
|
||||
_createData(entityWrapper.entity.doubleState),
|
||||
animate: false,
|
||||
defaultRenderer: charts.ArcRendererConfig(
|
||||
arcRatio: 0.4,
|
||||
startAngle: pi,
|
||||
arcLength: pi,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
double fontSize = constraints.maxHeight / 7;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: 2*fontSize),
|
||||
child: Text(
|
||||
'${entityWrapper.entity.doubleState}${widget.unit}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: fontSize),
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
double fontSize = constraints.maxHeight / 7;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: fontSize),
|
||||
child: Text(
|
||||
'${entityWrapper.displayName}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontSize: fontSize),
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GaugeSegment {
|
||||
final String segment;
|
||||
final double value;
|
||||
final charts.Color color;
|
||||
|
||||
GaugeSegment(this.segment, this.value, Color color)
|
||||
: this.color = charts.Color(
|
||||
r: color.red, g: color.green, b: color.blue, a: color.alpha);
|
||||
}
|
90
lib/cards/widgets/glance_entity_container.dart
Normal file
90
lib/cards/widgets/glance_entity_container.dart
Normal file
@ -0,0 +1,90 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class GlanceEntityContainer extends StatelessWidget {
|
||||
|
||||
final bool showName;
|
||||
final bool showState;
|
||||
final bool nameInTheBottom;
|
||||
final double iconSize;
|
||||
final double nameFontSize;
|
||||
final bool wordsWrapInName;
|
||||
|
||||
GlanceEntityContainer({
|
||||
Key key,
|
||||
@required this.showName,
|
||||
@required this.showState,
|
||||
this.nameInTheBottom: false,
|
||||
this.iconSize: Sizes.iconSize,
|
||||
this.nameFontSize: Sizes.smallFontSize,
|
||||
this.wordsWrapInName: false
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||
if (entityWrapper.entity.statelessType == StatelessEntityType.MISSED) {
|
||||
return MissedEntityWidget();
|
||||
}
|
||||
if (entityWrapper.entity.statelessType > StatelessEntityType.MISSED) {
|
||||
return Container(width: 0.0, height: 0.0,);
|
||||
}
|
||||
List<Widget> result = [];
|
||||
if (!nameInTheBottom) {
|
||||
if (showName) {
|
||||
result.add(_buildName());
|
||||
}
|
||||
} else {
|
||||
if (showState) {
|
||||
result.add(_buildState());
|
||||
}
|
||||
}
|
||||
result.add(
|
||||
EntityIcon(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
size: iconSize,
|
||||
)
|
||||
);
|
||||
if (!nameInTheBottom) {
|
||||
if (showState) {
|
||||
result.add(_buildState());
|
||||
}
|
||||
} else {
|
||||
result.add(_buildName());
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: InkResponse(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minWidth: Sizes.iconSize * 2),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
//mainAxisAlignment: MainAxisAlignment.start,
|
||||
//crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: result,
|
||||
),
|
||||
),
|
||||
onTap: () => entityWrapper.handleTap(),
|
||||
onLongPress: () => entityWrapper.handleHold(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildName() {
|
||||
return EntityName(
|
||||
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
|
||||
textOverflow: TextOverflow.ellipsis,
|
||||
wordsWrap: wordsWrapInName,
|
||||
textAlign: TextAlign.center,
|
||||
fontSize: nameFontSize,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildState() {
|
||||
return SimpleEntityState(
|
||||
textAlign: TextAlign.center,
|
||||
expanded: false,
|
||||
maxLines: 1,
|
||||
padding: EdgeInsets.only(top: Sizes.rowPadding),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user