2018-10-01 21:57:54 +03:00
|
|
|
part of '../main.dart';
|
|
|
|
|
|
|
|
class Entity {
|
|
|
|
static const STATE_ICONS_COLORS = {
|
|
|
|
"on": Colors.amber,
|
|
|
|
"off": Color.fromRGBO(68, 115, 158, 1.0),
|
2018-10-08 23:30:09 +03:00
|
|
|
"default": Color.fromRGBO(68, 115, 158, 1.0),
|
2018-10-01 21:57:54 +03:00
|
|
|
"unavailable": Colors.black12,
|
|
|
|
"unknown": Colors.black12,
|
|
|
|
"playing": Colors.amber
|
|
|
|
};
|
2018-10-07 02:17:14 +03:00
|
|
|
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"];
|
2018-10-01 21:57:54 +03:00
|
|
|
|
2018-10-07 15:03:51 +03:00
|
|
|
Map attributes;
|
2018-10-11 23:02:05 +03:00
|
|
|
String domain;
|
|
|
|
String entityId;
|
|
|
|
String state;
|
2018-10-02 23:10:40 +03:00
|
|
|
String assumedState;
|
2018-10-01 21:57:54 +03:00
|
|
|
DateTime _lastUpdated;
|
|
|
|
|
2018-10-07 02:17:14 +03:00
|
|
|
List<Entity> childEntities = [];
|
|
|
|
|
2018-10-01 21:57:54 +03:00
|
|
|
String get displayName =>
|
2018-10-07 15:03:51 +03:00
|
|
|
attributes["friendly_name"] ?? (attributes["name"] ?? "_");
|
2018-10-11 23:02:05 +03:00
|
|
|
|
|
|
|
|
2018-10-01 21:57:54 +03:00
|
|
|
|
2018-10-07 15:03:51 +03:00
|
|
|
String get deviceClass => attributes["device_class"] ?? null;
|
2018-10-01 21:57:54 +03:00
|
|
|
bool get isView =>
|
2018-10-11 23:02:05 +03:00
|
|
|
(domain == "group") &&
|
2018-10-07 15:03:51 +03:00
|
|
|
(attributes != null ? attributes["view"] ?? false : false);
|
2018-10-11 23:02:05 +03:00
|
|
|
bool get isGroup => domain == "group";
|
|
|
|
bool get isBadge => Entity.badgeDomains.contains(domain);
|
2018-10-07 15:03:51 +03:00
|
|
|
String get icon => attributes["icon"] ?? "";
|
2018-10-01 21:57:54 +03:00
|
|
|
bool get isOn => state == "on";
|
2018-10-07 15:03:51 +03:00
|
|
|
String get entityPicture => attributes["entity_picture"];
|
|
|
|
String get unitOfMeasurement => attributes["unit_of_measurement"] ?? "";
|
|
|
|
List get childEntityIds => attributes["entity_id"] ?? [];
|
2018-10-01 21:57:54 +03:00
|
|
|
String get lastUpdated => _getLastUpdatedFormatted();
|
|
|
|
|
|
|
|
Entity(Map rawData) {
|
|
|
|
update(rawData);
|
|
|
|
}
|
|
|
|
|
|
|
|
void update(Map rawData) {
|
2018-10-07 15:03:51 +03:00
|
|
|
attributes = rawData["attributes"] ?? {};
|
2018-10-11 23:02:05 +03:00
|
|
|
domain = rawData["entity_id"].split(".")[0];
|
|
|
|
entityId = rawData["entity_id"];
|
|
|
|
state = rawData["state"];
|
|
|
|
assumedState = state;
|
2018-10-01 21:57:54 +03:00
|
|
|
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
|
|
|
|
}
|
|
|
|
|
2018-10-03 16:44:11 +03:00
|
|
|
EntityWidget buildWidget(BuildContext context, int widgetType) {
|
2018-10-02 00:41:40 +03:00
|
|
|
return EntityWidget(
|
|
|
|
entity: this,
|
2018-10-03 16:44:11 +03:00
|
|
|
widgetType: widgetType,
|
2018-10-02 00:41:40 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-10-07 15:03:51 +03:00
|
|
|
String getAttribute(String attributeName) {
|
|
|
|
if (attributes != null) {
|
|
|
|
return attributes["$attributeName"];
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-10-01 21:57:54 +03:00
|
|
|
String _getLastUpdatedFormatted() {
|
|
|
|
if (_lastUpdated == null) {
|
|
|
|
return "-";
|
|
|
|
} else {
|
|
|
|
DateTime now = DateTime.now();
|
|
|
|
Duration d = now.difference(_lastUpdated);
|
|
|
|
String text;
|
|
|
|
int v;
|
|
|
|
if (d.inDays == 0) {
|
|
|
|
if (d.inHours == 0) {
|
|
|
|
if (d.inMinutes == 0) {
|
|
|
|
text = "seconds ago";
|
|
|
|
v = d.inSeconds;
|
|
|
|
} else {
|
|
|
|
text = "minutes ago";
|
|
|
|
v = d.inMinutes;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
text = "hours ago";
|
|
|
|
v = d.inHours;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
text = "days ago";
|
|
|
|
v = d.inDays;
|
|
|
|
}
|
|
|
|
return "$v $text";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-03 16:44:11 +03:00
|
|
|
}
|
2018-10-02 00:41:40 +03:00
|
|
|
|
2018-10-03 16:44:11 +03:00
|
|
|
class EntityWidgetType {
|
|
|
|
static final int regular = 1;
|
|
|
|
static final int extended = 2;
|
|
|
|
static final int badge = 3;
|
2018-10-02 00:41:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
class EntityWidget extends StatefulWidget {
|
2018-10-03 16:44:11 +03:00
|
|
|
|
|
|
|
EntityWidget({Key key, this.entity, this.widgetType}) : super(key: key);
|
2018-10-02 00:41:40 +03:00
|
|
|
|
|
|
|
final Entity entity;
|
2018-10-03 16:44:11 +03:00
|
|
|
final int widgetType;
|
2018-10-02 00:41:40 +03:00
|
|
|
|
|
|
|
@override
|
|
|
|
_EntityWidgetState createState() {
|
|
|
|
switch (entity.domain) {
|
2018-10-07 15:03:51 +03:00
|
|
|
case 'sun': {
|
|
|
|
return _SunEntityWidgetState();
|
|
|
|
}
|
2018-10-02 00:41:40 +03:00
|
|
|
case "automation":
|
2018-10-03 09:50:14 +03:00
|
|
|
case "input_boolean":
|
2018-10-02 00:41:40 +03:00
|
|
|
case "switch":
|
|
|
|
case "light": {
|
|
|
|
return _SwitchEntityWidgetState();
|
|
|
|
}
|
|
|
|
case "script":
|
|
|
|
case "scene": {
|
|
|
|
return _ButtonEntityWidgetState();
|
|
|
|
}
|
|
|
|
case "input_datetime": {
|
|
|
|
return _DateTimeEntityWidgetState();
|
|
|
|
}
|
|
|
|
case "input_select": {
|
|
|
|
return _SelectEntityWidgetState();
|
|
|
|
}
|
|
|
|
case "input_number": {
|
|
|
|
return _SliderEntityWidgetState();
|
|
|
|
}
|
|
|
|
case "input_text": {
|
|
|
|
return _TextEntityWidgetState();
|
|
|
|
}
|
2018-10-11 23:02:05 +03:00
|
|
|
case "climate": {
|
|
|
|
return _ClimateEntityWidgetState();
|
|
|
|
}
|
2018-10-02 00:41:40 +03:00
|
|
|
default: {
|
|
|
|
return _EntityWidgetState();
|
|
|
|
}
|
|
|
|
}
|
2018-10-01 21:57:54 +03:00
|
|
|
}
|
2018-10-02 00:41:40 +03:00
|
|
|
}
|
2018-10-01 21:57:54 +03:00
|
|
|
|
2018-10-02 00:41:40 +03:00
|
|
|
class _EntityWidgetState extends State<EntityWidget> {
|
|
|
|
|
2018-10-07 15:03:51 +03:00
|
|
|
List<String> attributesToShow = ["all"];
|
2018-10-11 23:02:05 +03:00
|
|
|
double rightWidgetPadding = 14.0;
|
|
|
|
double leftWidgetPadding = 8.0;
|
|
|
|
double extendedWidgetHeight = 50.0;
|
|
|
|
double widgetHeight = 34.0;
|
|
|
|
double iconSize = 28.0;
|
|
|
|
double stateFontSize = 16.0;
|
|
|
|
double nameFontSize = 16.0;
|
|
|
|
double smallFontSize = 14.0;
|
|
|
|
double largeFontSize = 24.0;
|
|
|
|
double inputWidth = 160.0;
|
|
|
|
double rowPadding = 10.0;
|
2018-10-07 15:03:51 +03:00
|
|
|
|
2018-10-02 00:41:40 +03:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2018-10-03 16:44:11 +03:00
|
|
|
if (widget.widgetType == EntityWidgetType.regular) {
|
2018-10-02 00:41:40 +03:00
|
|
|
return _buildMainWidget(context);
|
2018-10-03 16:44:11 +03:00
|
|
|
} else if (widget.widgetType == EntityWidgetType.extended) {
|
2018-10-07 12:40:45 +03:00
|
|
|
return _buildExtendedWidget(context);
|
2018-10-07 02:17:14 +03:00
|
|
|
} else if (widget.widgetType == EntityWidgetType.badge) {
|
|
|
|
return _buildBadgeWidget(context);
|
2018-10-03 16:44:11 +03:00
|
|
|
} else {
|
|
|
|
TheLogger.log("Error", "Unknown entity widget type: ${widget.widgetType}");
|
2018-10-07 09:45:04 +03:00
|
|
|
return Container(width: 0.0, height: 0.0);
|
2018-10-02 00:41:40 +03:00
|
|
|
}
|
2018-10-01 21:57:54 +03:00
|
|
|
}
|
|
|
|
|
2018-10-07 12:40:45 +03:00
|
|
|
Widget _buildExtendedWidget(BuildContext context) {
|
|
|
|
return ListView(
|
|
|
|
children: <Widget>[
|
|
|
|
_buildMainWidget(context),
|
2018-10-07 15:03:51 +03:00
|
|
|
_buildSecondRowWidget(),
|
2018-10-11 23:02:05 +03:00
|
|
|
Divider(),
|
2018-10-07 15:03:51 +03:00
|
|
|
_buildAttributesWidget()
|
2018-10-07 12:40:45 +03:00
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-10-02 00:41:40 +03:00
|
|
|
Widget _buildMainWidget(BuildContext context) {
|
2018-10-01 21:57:54 +03:00
|
|
|
return SizedBox(
|
2018-10-11 23:02:05 +03:00
|
|
|
height: widgetHeight,
|
2018-10-01 21:57:54 +03:00
|
|
|
child: Row(
|
|
|
|
children: <Widget>[
|
|
|
|
GestureDetector(
|
|
|
|
child: _buildIconWidget(),
|
2018-10-03 16:44:11 +03:00
|
|
|
onTap: widget.widgetType == EntityWidgetType.extended ? null : openEntityPage,
|
2018-10-01 21:57:54 +03:00
|
|
|
),
|
|
|
|
Expanded(
|
|
|
|
child: GestureDetector(
|
|
|
|
child: _buildNameWidget(),
|
2018-10-03 16:44:11 +03:00
|
|
|
onTap: widget.widgetType == EntityWidgetType.extended ? null : openEntityPage,
|
2018-10-01 21:57:54 +03:00
|
|
|
),
|
|
|
|
),
|
2018-10-03 16:44:11 +03:00
|
|
|
_buildActionWidget(context)
|
2018-10-01 21:57:54 +03:00
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-10-07 15:03:51 +03:00
|
|
|
Widget _buildAttributesWidget() {
|
|
|
|
List<Widget> attrs = [];
|
|
|
|
if (attributesToShow.contains("all")) {
|
|
|
|
widget.entity.attributes.forEach((name, value){
|
|
|
|
attrs.add(
|
|
|
|
_buildAttributeWidget("$name", "$value")
|
|
|
|
);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
attributesToShow.forEach((String attr) {
|
|
|
|
String attrValue = widget.entity.getAttribute("$attr");
|
|
|
|
if (attrValue != null) {
|
|
|
|
attrs.add(
|
|
|
|
_buildAttributeWidget("$attr", "$attrValue")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return Column(
|
|
|
|
children: attrs,
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget _buildAttributeWidget(String name, String value) {
|
|
|
|
return Row(
|
|
|
|
children: <Widget>[
|
|
|
|
Expanded(
|
|
|
|
child: Padding(
|
2018-10-11 23:02:05 +03:00
|
|
|
padding: EdgeInsets.fromLTRB(leftWidgetPadding, rowPadding, 0.0, 0.0),
|
2018-10-07 15:03:51 +03:00
|
|
|
child: Text(
|
|
|
|
"$name",
|
|
|
|
textAlign: TextAlign.left,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
Expanded(
|
|
|
|
child: Padding(
|
2018-10-11 23:02:05 +03:00
|
|
|
padding: EdgeInsets.fromLTRB(0.0, rowPadding, rightWidgetPadding, 0.0),
|
2018-10-07 15:03:51 +03:00
|
|
|
child: Text(
|
|
|
|
"$value",
|
|
|
|
textAlign: TextAlign.right,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-10-02 00:41:40 +03:00
|
|
|
void openEntityPage() {
|
|
|
|
eventBus.fire(new ShowEntityPageEvent(widget.entity));
|
|
|
|
}
|
|
|
|
|
2018-10-02 23:10:40 +03:00
|
|
|
void setNewState(newState) {
|
2018-10-02 00:41:40 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-07 15:03:51 +03:00
|
|
|
/*Widget buildAdditionalWidget() {
|
2018-10-07 12:40:45 +03:00
|
|
|
return _buildSecondRowWidget();
|
2018-10-07 15:03:51 +03:00
|
|
|
}*/
|
2018-10-01 21:57:54 +03:00
|
|
|
|
|
|
|
Widget _buildIconWidget() {
|
|
|
|
return Padding(
|
2018-10-11 23:02:05 +03:00
|
|
|
padding: EdgeInsets.fromLTRB(leftWidgetPadding, 0.0, 12.0, 0.0),
|
2018-10-01 21:57:54 +03:00
|
|
|
child: MaterialDesignIcons.createIconWidgetFromEntityData(
|
2018-10-02 00:41:40 +03:00
|
|
|
widget.entity,
|
2018-10-11 23:02:05 +03:00
|
|
|
iconSize,
|
2018-10-08 23:30:09 +03:00
|
|
|
Entity.STATE_ICONS_COLORS[widget.entity.state] ?? Entity.STATE_ICONS_COLORS["default"]),
|
2018-10-01 21:57:54 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-10-07 12:40:45 +03:00
|
|
|
Widget _buildSecondRowWidget() {
|
2018-10-01 21:57:54 +03:00
|
|
|
return Padding(
|
|
|
|
padding: EdgeInsets.fromLTRB(
|
2018-10-11 23:02:05 +03:00
|
|
|
leftWidgetPadding, smallFontSize, 0.0, 0.0),
|
2018-10-01 21:57:54 +03:00
|
|
|
child: Text(
|
2018-10-02 00:41:40 +03:00
|
|
|
'${widget.entity.lastUpdated}',
|
2018-10-01 21:57:54 +03:00
|
|
|
textAlign: TextAlign.left,
|
|
|
|
style:
|
2018-10-11 23:02:05 +03:00
|
|
|
TextStyle(fontSize: smallFontSize, color: Colors.black26),
|
2018-10-01 21:57:54 +03:00
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget _buildNameWidget() {
|
|
|
|
return Padding(
|
|
|
|
padding: EdgeInsets.only(right: 10.0),
|
|
|
|
child: Text(
|
2018-10-02 00:41:40 +03:00
|
|
|
"${widget.entity.displayName}",
|
2018-10-01 21:57:54 +03:00
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
softWrap: false,
|
2018-10-11 23:02:05 +03:00
|
|
|
style: TextStyle(fontSize: nameFontSize),
|
2018-10-01 21:57:54 +03:00
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-10-03 16:44:11 +03:00
|
|
|
Widget _buildActionWidget(BuildContext context) {
|
2018-10-01 21:57:54 +03:00
|
|
|
return Padding(
|
|
|
|
padding:
|
2018-10-11 23:02:05 +03:00
|
|
|
EdgeInsets.fromLTRB(0.0, 0.0, rightWidgetPadding, 0.0),
|
2018-10-01 21:57:54 +03:00
|
|
|
child: GestureDetector(
|
|
|
|
child: Text(
|
2018-10-02 00:41:40 +03:00
|
|
|
"${widget.entity.state}${widget.entity.unitOfMeasurement}",
|
2018-10-01 21:57:54 +03:00
|
|
|
textAlign: TextAlign.right,
|
|
|
|
style: new TextStyle(
|
2018-10-11 23:02:05 +03:00
|
|
|
fontSize: stateFontSize,
|
2018-10-01 21:57:54 +03:00
|
|
|
)),
|
|
|
|
onTap: openEntityPage,
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2018-10-07 02:17:14 +03:00
|
|
|
|
|
|
|
Widget _buildBadgeWidget(BuildContext context) {
|
|
|
|
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(
|
2018-10-11 23:02:05 +03:00
|
|
|
"${widget.entity.state}",
|
2018-10-07 02:17:14 +03:00
|
|
|
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),
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2018-10-07 15:03:51 +03:00
|
|
|
return GestureDetector(
|
|
|
|
child: 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,
|
2018-10-07 02:17:14 +03:00
|
|
|
),
|
2018-10-07 15:03:51 +03:00
|
|
|
),
|
|
|
|
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,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
],
|
|
|
|
),
|
2018-10-07 02:17:14 +03:00
|
|
|
),
|
2018-10-07 15:03:51 +03:00
|
|
|
Container(
|
|
|
|
width: 60.0,
|
|
|
|
child: Text(
|
|
|
|
"${widget.entity.displayName}",
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
style: TextStyle(fontSize: 12.0),
|
|
|
|
softWrap: true,
|
|
|
|
maxLines: 3,
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
),
|
2018-10-07 02:17:14 +03:00
|
|
|
),
|
2018-10-07 15:03:51 +03:00
|
|
|
],
|
|
|
|
),
|
|
|
|
onTap: openEntityPage,
|
2018-10-07 02:17:14 +03:00
|
|
|
);
|
|
|
|
}
|
2018-10-01 21:57:54 +03:00
|
|
|
}
|