Full ui structure refactoring. InheritedWidget as entity model
This commit is contained in:
@ -45,7 +45,7 @@ class HACard extends StatelessWidget {
|
|||||||
result.add(
|
result.add(
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0),
|
padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0),
|
||||||
child: entity.buildWidget(context, EntityWidgetType.regular),
|
child: entity.buildDefaultWidget(context),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
|
@ -53,7 +53,7 @@ class _EntityViewPageState extends State<EntityViewPage> {
|
|||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: EdgeInsets.all(10.0),
|
padding: EdgeInsets.all(10.0),
|
||||||
child: widget.entity.buildWidget(context, EntityWidgetType.extended)
|
child: widget.entity.buildEntityPageWidget(context)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class _ButtonEntityWidgetState extends _EntityWidgetState {
|
|
||||||
|
|
||||||
@override
|
|
||||||
void setNewState(newValue) {
|
|
||||||
eventBus.fire(new ServiceCallEvent(widget.entity.domain, "turn_on", widget.entity.entityId, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget _buildActionWidget(BuildContext context) {
|
|
||||||
return FlatButton(
|
|
||||||
onPressed: (() {
|
|
||||||
setNewState(null);
|
|
||||||
}),
|
|
||||||
child: Text(
|
|
||||||
"EXECUTE",
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
style:
|
|
||||||
new TextStyle(fontSize: stateFontSize, color: Colors.blue),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,231 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class _ClimateEntityWidgetState extends _EntityWidgetState {
|
|
||||||
|
|
||||||
List<String> _operationList = [];
|
|
||||||
double _temperature1 = 0.0;
|
|
||||||
String _operationMode = "";
|
|
||||||
bool _awayMode = false;
|
|
||||||
bool _showPending;
|
|
||||||
bool _changedHere;
|
|
||||||
double _temperatureStep = 0.2;
|
|
||||||
Timer _resetTimer;
|
|
||||||
|
|
||||||
@override
|
|
||||||
double widgetHeight = 38.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
_operationList.clear();
|
|
||||||
if (widget.entity.attributes["operation_list"] != null) {
|
|
||||||
widget.entity.attributes["operation_list"].forEach((value){
|
|
||||||
_operationList.add(value.toString());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_resetVars();
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _resetVars() {
|
|
||||||
var temp1 = widget.entity.attributes['temperature'] ?? widget.entity.attributes['target_temp_low'];
|
|
||||||
if (temp1 is int) {
|
|
||||||
_temperature1 = temp1.toDouble();
|
|
||||||
} else if (temp1 is double) {
|
|
||||||
_temperature1 = temp1;
|
|
||||||
}
|
|
||||||
_operationMode = widget.entity.attributes['operation_mode'];
|
|
||||||
_awayMode = widget.entity.attributes['away_mode'] == "on";
|
|
||||||
_showPending = false;
|
|
||||||
_changedHere = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget _buildSecondRowWidget() {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
super._buildSecondRowWidget(),
|
|
||||||
_buildAdditionalControls()
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _temperatureUp() {
|
|
||||||
_temperature1 += _temperatureStep;
|
|
||||||
_setTemperature();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _temperatureDown() {
|
|
||||||
_temperature1 -= _temperatureStep;
|
|
||||||
_setTemperature();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _setTemperature() {
|
|
||||||
setState(() {
|
|
||||||
_temperature1 = double.parse(_temperature1.toStringAsFixed(1));
|
|
||||||
_changedHere = true;
|
|
||||||
eventBus.fire(new ServiceCallEvent(widget.entity.domain, "set_temperature", widget.entity.entityId,{"temperature": "${_temperature1.toStringAsFixed(1)}"}));
|
|
||||||
_resetStateTimer();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _setOperationMode(value) {
|
|
||||||
setState(() {
|
|
||||||
_operationMode = value;
|
|
||||||
_changedHere = true;
|
|
||||||
eventBus.fire(new ServiceCallEvent(widget.entity.domain, "set_operation_mode", widget.entity.entityId,{"operation_mode": "$_operationMode"}));
|
|
||||||
_resetStateTimer();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _setAwayMode(value) {
|
|
||||||
setState(() {
|
|
||||||
_awayMode = value;
|
|
||||||
_changedHere = true;
|
|
||||||
eventBus.fire(new ServiceCallEvent(widget.entity.domain, "set_away_mode", widget.entity.entityId,{"away_mode": "${_awayMode ? 'on' : 'off'}"}));
|
|
||||||
_resetStateTimer();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _resetStateTimer() {
|
|
||||||
if (_resetTimer!=null) {
|
|
||||||
_resetTimer.cancel();
|
|
||||||
}
|
|
||||||
_resetTimer = Timer(Duration(seconds: 3), () {
|
|
||||||
setState(() {});
|
|
||||||
_resetVars();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_buildAdditionalControls() {
|
|
||||||
if (_changedHere) {
|
|
||||||
_showPending = (_temperature1 != widget.entity.attributes['temperature']);
|
|
||||||
_changedHere = false;
|
|
||||||
} else {
|
|
||||||
_resetTimer?.cancel();
|
|
||||||
_resetVars();
|
|
||||||
}
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(leftWidgetPadding, rowPadding, rightWidgetPadding, 0.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text("Target temperature for ${_operationMode != 'off' ? _operationMode : ''}", style: TextStyle(
|
|
||||||
fontSize: stateFontSize
|
|
||||||
)),
|
|
||||||
Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
"$_temperature1",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: largeFontSize,
|
|
||||||
color: _showPending ? Colors.red : Colors.black
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.keyboard_arrow_up),
|
|
||||||
iconSize: 30.0,
|
|
||||||
onPressed: () => _temperatureUp(),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.keyboard_arrow_down),
|
|
||||||
iconSize: 30.0,
|
|
||||||
onPressed: () => _temperatureDown(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text("Operation", style: TextStyle(
|
|
||||||
fontSize: stateFontSize
|
|
||||||
)),
|
|
||||||
DropdownButton<String>(
|
|
||||||
value: "$_operationMode",
|
|
||||||
iconSize: 30.0,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: largeFontSize,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
items: this._operationList.map((String value) {
|
|
||||||
return new DropdownMenuItem<String>(
|
|
||||||
value: value,
|
|
||||||
child: new Text(value),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: (_) => _setOperationMode(_),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(top: rowPadding),
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
"Away mode",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: stateFontSize
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Switch(
|
|
||||||
onChanged: (value) => _setAwayMode(value),
|
|
||||||
value: _awayMode,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget _buildActionWidget(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding:
|
|
||||||
EdgeInsets.fromLTRB(0.0, 0.0, rightWidgetPadding, 0.0),
|
|
||||||
child: GestureDetector(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: <Widget>[
|
|
||||||
Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
"${widget.entity.state}",
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
style: new TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: stateFontSize,
|
|
||||||
)),
|
|
||||||
Text(
|
|
||||||
" ${widget.entity.attributes["temperature"]}",
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
style: new TextStyle(
|
|
||||||
fontSize: stateFontSize,
|
|
||||||
))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"Currently: ${widget.entity.attributes["current_temperature"]}",
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
style: new TextStyle(
|
|
||||||
fontSize: stateFontSize,
|
|
||||||
color: Colors.black45
|
|
||||||
))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: openEntityPage,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_resetTimer?.cancel();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class _DateTimeEntityWidgetState extends _EntityWidgetState {
|
|
||||||
bool get hasDate => widget.entity.attributes["has_date"] ?? false;
|
|
||||||
bool get hasTime => widget.entity.attributes["has_time"] ?? false;
|
|
||||||
int get year => widget.entity.attributes["year"] ?? 1970;
|
|
||||||
int get month => widget.entity.attributes["month"] ?? 1;
|
|
||||||
int get day => widget.entity.attributes["day"] ?? 1;
|
|
||||||
int get hour => widget.entity.attributes["hour"] ?? 0;
|
|
||||||
int get minute => widget.entity.attributes["minute"] ?? 0;
|
|
||||||
int get second => widget.entity.attributes["second"] ?? 0;
|
|
||||||
String get formattedState => _getFormattedState();
|
|
||||||
DateTime get dateTimeState => _getDateTimeState();
|
|
||||||
|
|
||||||
DateTime _getDateTimeState() {
|
|
||||||
return DateTime(this.year, this.month, this.day, this.hour, this.minute, this.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getFormattedState() {
|
|
||||||
String formattedState = "";
|
|
||||||
if (this.hasDate) {
|
|
||||||
formattedState += formatDate(dateTimeState, [M, ' ', d, ', ', yyyy]);
|
|
||||||
}
|
|
||||||
if (this.hasTime) {
|
|
||||||
formattedState += " "+formatDate(dateTimeState, [HH, ':', nn]);
|
|
||||||
}
|
|
||||||
return formattedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void setNewState(newValue) {
|
|
||||||
eventBus.fire(new ServiceCallEvent(widget.entity.domain, "set_datetime", widget.entity.entityId,
|
|
||||||
newValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget _buildActionWidget(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding:
|
|
||||||
EdgeInsets.fromLTRB(0.0, 0.0, rightWidgetPadding, 0.0),
|
|
||||||
child: GestureDetector(
|
|
||||||
child: Text(
|
|
||||||
"$formattedState",
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
style: new TextStyle(
|
|
||||||
fontSize: stateFontSize,
|
|
||||||
)),
|
|
||||||
onTap: () => _handleStateTap(context),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleStateTap(BuildContext context) {
|
|
||||||
if (hasDate) {
|
|
||||||
_showDatePicker(context).then((date) {
|
|
||||||
if (date != null) {
|
|
||||||
if (hasTime) {
|
|
||||||
_showTimePicker(context).then((time){
|
|
||||||
setNewState({"date": "${formatDate(date, [yyyy, '-', mm, '-', dd])}", "time": "${formatDate(DateTime(1970, 1, 1, time.hour, time.minute), [HH, ':', nn])}"});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setNewState({"date": "${formatDate(date, [yyyy, '-', mm, '-', dd])}"});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (hasTime) {
|
|
||||||
_showTimePicker(context).then((time){
|
|
||||||
if (time != null) {
|
|
||||||
setNewState({"time": "${formatDate(DateTime(1970, 1, 1, time.hour, time.minute), [HH, ':', nn])}"});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
TheLogger.log("Warning", "${widget.entity.entityId} has no date and no time");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future _showDatePicker(BuildContext context) {
|
|
||||||
return showDatePicker(
|
|
||||||
context: context,
|
|
||||||
initialDate: dateTimeState,
|
|
||||||
firstDate: DateTime(1970),
|
|
||||||
lastDate: DateTime(2037) //Unix timestamp will finish at Jan 19, 2038
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future _showTimePicker(BuildContext context) {
|
|
||||||
return showTimePicker(
|
|
||||||
context: context,
|
|
||||||
initialTime: TimeOfDay.fromDateTime(dateTimeState)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,6 +15,18 @@ class Entity {
|
|||||||
};
|
};
|
||||||
static List badgeDomains = ["alarm_control_panel", "binary_sensor", "device_tracker", "updater", "sun", "timer", "sensor"];
|
static List badgeDomains = ["alarm_control_panel", "binary_sensor", "device_tracker", "updater", "sun", "timer", "sensor"];
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
Map attributes;
|
Map attributes;
|
||||||
String domain;
|
String domain;
|
||||||
String entityId;
|
String entityId;
|
||||||
@ -24,11 +36,11 @@ class Entity {
|
|||||||
|
|
||||||
List<Entity> childEntities = [];
|
List<Entity> childEntities = [];
|
||||||
|
|
||||||
|
List<String> attributesToShow = ["all"];
|
||||||
|
|
||||||
String get displayName =>
|
String get displayName =>
|
||||||
attributes["friendly_name"] ?? (attributes["name"] ?? "_");
|
attributes["friendly_name"] ?? (attributes["name"] ?? "_");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
String get deviceClass => attributes["device_class"] ?? null;
|
String get deviceClass => attributes["device_class"] ?? null;
|
||||||
bool get isView =>
|
bool get isView =>
|
||||||
(domain == "group") &&
|
(domain == "group") &&
|
||||||
@ -55,10 +67,52 @@ class Entity {
|
|||||||
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
|
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
EntityWidget buildWidget(BuildContext context, int widgetType) {
|
Widget buildDefaultWidget(BuildContext context) {
|
||||||
return EntityWidget(
|
return EntityModel(
|
||||||
entity: this,
|
entity: this,
|
||||||
widgetType: widgetType,
|
child: DefaultEntityContainer(
|
||||||
|
state: _buildStatePart(context)
|
||||||
|
),
|
||||||
|
handleTap: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStatePart(BuildContext context) {
|
||||||
|
return SimpleEntityState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStatePartForPage(BuildContext context) {
|
||||||
|
return _buildStatePart(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||||
|
return Container(width: 0.0, height: 0.0,);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildEntityPageWidget(BuildContext context) {
|
||||||
|
return EntityModel(
|
||||||
|
entity: this,
|
||||||
|
child: EntityPageContainer(
|
||||||
|
children: <Widget> [
|
||||||
|
DefaultEntityContainer(
|
||||||
|
state: _buildStatePartForPage(context)
|
||||||
|
),
|
||||||
|
LastUpdatedWidget(),
|
||||||
|
Divider(),
|
||||||
|
_buildAdditionalControlsForPage(context),
|
||||||
|
Divider(),
|
||||||
|
EntityAttributesList()
|
||||||
|
]
|
||||||
|
),
|
||||||
|
handleTap: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildBadgeWidget(BuildContext context) {
|
||||||
|
return EntityModel(
|
||||||
|
entity: this,
|
||||||
|
child: Badge(),
|
||||||
|
handleTap: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,341 +154,165 @@ class Entity {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class EntityWidgetType {
|
class SwitchEntity extends Entity {
|
||||||
static final int regular = 1;
|
SwitchEntity(Map rawData) : super(rawData);
|
||||||
static final int extended = 2;
|
|
||||||
static final int badge = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
class EntityWidget extends StatefulWidget {
|
|
||||||
|
|
||||||
EntityWidget({Key key, this.entity, this.widgetType}) : super(key: key);
|
|
||||||
|
|
||||||
final Entity entity;
|
|
||||||
final int widgetType;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_EntityWidgetState createState() {
|
Widget _buildStatePart(BuildContext context) {
|
||||||
switch (entity.domain) {
|
return SwitchControlWidget();
|
||||||
case 'sun': {
|
}
|
||||||
return _SunEntityWidgetState();
|
|
||||||
}
|
}
|
||||||
case "automation":
|
|
||||||
case "input_boolean":
|
class ButtonEntity extends Entity {
|
||||||
case "switch":
|
ButtonEntity(Map rawData) : super(rawData);
|
||||||
case "light": {
|
|
||||||
return _SwitchEntityWidgetState();
|
@override
|
||||||
}
|
Widget _buildStatePart(BuildContext context) {
|
||||||
case "script":
|
return ButtonControlWidget();
|
||||||
case "scene": {
|
}
|
||||||
return _ButtonEntityWidgetState();
|
|
||||||
}
|
}
|
||||||
case "input_datetime": {
|
|
||||||
return _DateTimeEntityWidgetState();
|
class TextEntity extends Entity {
|
||||||
}
|
TextEntity(Map rawData) : super(rawData);
|
||||||
case "input_select": {
|
|
||||||
return _SelectEntityWidgetState();
|
int get valueMinLength => attributes["min"] ?? -1;
|
||||||
}
|
int get valueMaxLength => attributes["max"] ?? -1;
|
||||||
case "input_number": {
|
String get valuePattern => attributes["pattern"] ?? null;
|
||||||
return _SliderEntityWidgetState();
|
bool get isTextField => attributes["mode"] == "text";
|
||||||
}
|
bool get isPasswordField => attributes["mode"] == "password";
|
||||||
case "input_text": {
|
|
||||||
return _TextEntityWidgetState();
|
@override
|
||||||
}
|
Widget _buildStatePart(BuildContext context) {
|
||||||
case "climate": {
|
return TextControlWidget();
|
||||||
return _ClimateEntityWidgetState();
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return _EntityWidgetState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EntityWidgetState extends State<EntityWidget> {
|
class SunEntity extends Entity {
|
||||||
|
SunEntity(Map rawData) : super(rawData);
|
||||||
|
}
|
||||||
|
|
||||||
List<String> attributesToShow = ["all"];
|
class SliderEntity extends Entity {
|
||||||
double rightWidgetPadding = 14.0;
|
SliderEntity(Map rawData) : super(rawData);
|
||||||
double leftWidgetPadding = 8.0;
|
|
||||||
double extendedWidgetHeight = 50.0;
|
double get minValue => attributes["min"] ?? 0.0;
|
||||||
double widgetHeight = 34.0;
|
double get maxValue => attributes["max"] ?? 100.0;
|
||||||
double iconSize = 28.0;
|
double get valueStep => attributes["step"] ?? 1.0;
|
||||||
double stateFontSize = 16.0;
|
double get doubleState => double.tryParse(state) ?? 0.0;
|
||||||
double nameFontSize = 16.0;
|
|
||||||
double smallFontSize = 14.0;
|
|
||||||
double largeFontSize = 24.0;
|
|
||||||
double inputWidth = 160.0;
|
|
||||||
double rowPadding = 10.0;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget _buildStatePart(BuildContext context) {
|
||||||
if (widget.widgetType == EntityWidgetType.regular) {
|
return Expanded(
|
||||||
return _buildMainWidget(context);
|
//width: 200.0,
|
||||||
} else if (widget.widgetType == EntityWidgetType.extended) {
|
|
||||||
return _buildExtendedWidget(context);
|
|
||||||
} else if (widget.widgetType == EntityWidgetType.badge) {
|
|
||||||
return _buildBadgeWidget(context);
|
|
||||||
} else {
|
|
||||||
TheLogger.log("Error", "Unknown entity widget type: ${widget.widgetType}");
|
|
||||||
return Container(width: 0.0, height: 0.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildExtendedWidget(BuildContext context) {
|
|
||||||
return ListView(
|
|
||||||
children: <Widget>[
|
|
||||||
_buildMainWidget(context),
|
|
||||||
_buildSecondRowWidget(),
|
|
||||||
Divider(),
|
|
||||||
_buildAttributesWidget()
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildMainWidget(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
height: widgetHeight,
|
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
GestureDetector(
|
SliderControlWidget(expanded: true,),
|
||||||
child: _buildIconWidget(),
|
SimpleEntityState(),
|
||||||
onTap: widget.widgetType == EntityWidgetType.extended ? null : openEntityPage,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: GestureDetector(
|
|
||||||
child: _buildNameWidget(),
|
|
||||||
onTap: widget.widgetType == EntityWidgetType.extended ? null : openEntityPage,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_buildActionWidget(context)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAttributesWidget() {
|
@override
|
||||||
List<Widget> attrs = [];
|
Widget _buildStatePartForPage(BuildContext context) {
|
||||||
if (attributesToShow.contains("all")) {
|
return SimpleEntityState();
|
||||||
widget.entity.attributes.forEach((name, value){
|
}
|
||||||
attrs.add(
|
|
||||||
_buildAttributeWidget("$name", "$value")
|
@override
|
||||||
);
|
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||||
});
|
return SliderControlWidget(expanded: false,);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClimateEntity extends Entity {
|
||||||
|
|
||||||
|
@override
|
||||||
|
double widgetHeight = 38.0;
|
||||||
|
|
||||||
|
List<String> get operationList => (attributes["operation_list"] as List).cast<String>();
|
||||||
|
double get temperature => _getTemperature();
|
||||||
|
String get operationMode => attributes['operation_mode'] ?? "";
|
||||||
|
bool get awayMode => attributes['away_mode'] == "on";
|
||||||
|
|
||||||
|
ClimateEntity(Map rawData) : super(rawData);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget _buildStatePart(BuildContext context) {
|
||||||
|
return ClimateStateWidget();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||||
|
return ClimateControlWidget();
|
||||||
|
}
|
||||||
|
|
||||||
|
double _getTemperature() {
|
||||||
|
var temp1 = attributes['temperature'] ?? attributes['target_temp_low'];
|
||||||
|
if (temp1 is int) {
|
||||||
|
return temp1.toDouble();
|
||||||
|
} else if (temp1 is double) {
|
||||||
|
return temp1;
|
||||||
} else {
|
} else {
|
||||||
attributesToShow.forEach((String attr) {
|
return 0.0;
|
||||||
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(
|
|
||||||
padding: EdgeInsets.fromLTRB(leftWidgetPadding, rowPadding, 0.0, 0.0),
|
|
||||||
child: Text(
|
|
||||||
"$name",
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(0.0, rowPadding, rightWidgetPadding, 0.0),
|
|
||||||
child: Text(
|
|
||||||
"$value",
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void openEntityPage() {
|
|
||||||
eventBus.fire(new ShowEntityPageEvent(widget.entity));
|
|
||||||
}
|
|
||||||
|
|
||||||
void setNewState(newState) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Widget buildAdditionalWidget() {
|
|
||||||
return _buildSecondRowWidget();
|
|
||||||
}*/
|
|
||||||
|
|
||||||
Widget _buildIconWidget() {
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(leftWidgetPadding, 0.0, 12.0, 0.0),
|
|
||||||
child: MaterialDesignIcons.createIconWidgetFromEntityData(
|
|
||||||
widget.entity,
|
|
||||||
iconSize,
|
|
||||||
Entity.STATE_ICONS_COLORS[widget.entity.state] ?? Entity.STATE_ICONS_COLORS["default"]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSecondRowWidget() {
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(
|
|
||||||
leftWidgetPadding, smallFontSize, 0.0, 0.0),
|
|
||||||
child: Text(
|
|
||||||
'${widget.entity.lastUpdated}',
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
style:
|
|
||||||
TextStyle(fontSize: smallFontSize, color: Colors.black26),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildNameWidget() {
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.only(right: 10.0),
|
|
||||||
child: Text(
|
|
||||||
"${widget.entity.displayName}",
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
softWrap: false,
|
|
||||||
style: TextStyle(fontSize: nameFontSize),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildActionWidget(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding:
|
|
||||||
EdgeInsets.fromLTRB(0.0, 0.0, rightWidgetPadding, 0.0),
|
|
||||||
child: GestureDetector(
|
|
||||||
child: Text(
|
|
||||||
"${widget.entity.state}${widget.entity.unitOfMeasurement}",
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
style: new TextStyle(
|
|
||||||
fontSize: stateFontSize,
|
|
||||||
)),
|
|
||||||
onTap: openEntityPage,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
|
||||||
"${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 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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: openEntityPage,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SelectEntity extends Entity {
|
||||||
|
|
||||||
|
List<String> get listOptions => attributes["options"]!= null ? (attributes["options"] as List).cast<String>() : [];
|
||||||
|
|
||||||
|
SelectEntity(Map rawData) : super(rawData);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget _buildStatePart(BuildContext context) {
|
||||||
|
return SelectControlWidget();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class DateTimeEntity extends Entity {
|
||||||
|
|
||||||
|
bool get hasDate => attributes["has_date"] ?? false;
|
||||||
|
bool get hasTime => attributes["has_time"] ?? false;
|
||||||
|
int get year => attributes["year"] ?? 1970;
|
||||||
|
int get month => attributes["month"] ?? 1;
|
||||||
|
int get day => attributes["day"] ?? 1;
|
||||||
|
int get hour => attributes["hour"] ?? 0;
|
||||||
|
int get minute => attributes["minute"] ?? 0;
|
||||||
|
int get second => attributes["second"] ?? 0;
|
||||||
|
String get formattedState => _getFormattedState();
|
||||||
|
DateTime get dateTimeState => _getDateTimeState();
|
||||||
|
|
||||||
|
DateTimeEntity(Map rawData) : super(rawData);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget _buildStatePart(BuildContext context) {
|
||||||
|
return DateTimeStateWidget();
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime _getDateTimeState() {
|
||||||
|
return DateTime(this.year, this.month, this.day, this.hour, this.minute, this.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getFormattedState() {
|
||||||
|
String formattedState = "";
|
||||||
|
if (this.hasDate) {
|
||||||
|
formattedState += formatDate(dateTimeState, [M, ' ', d, ', ', yyyy]);
|
||||||
|
}
|
||||||
|
if (this.hasTime) {
|
||||||
|
formattedState += " "+formatDate(dateTimeState, [HH, ':', nn]);
|
||||||
|
}
|
||||||
|
return formattedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setNewState(newValue) {
|
||||||
|
eventBus.fire(new ServiceCallEvent(domain, "set_datetime",entityId,
|
||||||
|
newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class _SelectEntityWidgetState extends _EntityWidgetState {
|
|
||||||
List<String> _listOptions = [];
|
|
||||||
|
|
||||||
@override
|
|
||||||
void setNewState(newValue) {
|
|
||||||
eventBus.fire(new ServiceCallEvent(widget.entity.domain, "select_option", widget.entity.entityId,
|
|
||||||
{"option": "$newValue"}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget _buildActionWidget(BuildContext context) {
|
|
||||||
Widget ctrl;
|
|
||||||
_listOptions.clear();
|
|
||||||
if (widget.entity.attributes["options"] != null) {
|
|
||||||
widget.entity.attributes["options"].forEach((value){
|
|
||||||
_listOptions.add(value.toString());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (_listOptions.isNotEmpty) {
|
|
||||||
ctrl = DropdownButton<String>(
|
|
||||||
value: widget.entity.state,
|
|
||||||
items: this._listOptions.map((String value) {
|
|
||||||
return new DropdownMenuItem<String>(
|
|
||||||
value: value,
|
|
||||||
child: new Text(value),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: (_) {
|
|
||||||
setNewState(_);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ctrl = Text('---');
|
|
||||||
}
|
|
||||||
return Expanded(
|
|
||||||
//width: Entity.INPUT_WIDTH,
|
|
||||||
child: ctrl,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class _SliderEntityWidgetState extends _EntityWidgetState {
|
|
||||||
int _multiplier = 1;
|
|
||||||
|
|
||||||
double get minValue => widget.entity.attributes["min"] ?? 0.0;
|
|
||||||
double get maxValue => widget.entity.attributes["max"] ?? 100.0;
|
|
||||||
double get valueStep => widget.entity.attributes["step"] ?? 1.0;
|
|
||||||
double get doubleState => double.tryParse(widget.entity.state) ?? 0.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
if (valueStep < 1) {
|
|
||||||
_multiplier = 10;
|
|
||||||
} else if (valueStep < 0.1) {
|
|
||||||
_multiplier = 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void setNewState(newValue) {
|
|
||||||
eventBus.fire(new ServiceCallEvent(widget.entity.domain, "set_value", widget.entity.entityId,
|
|
||||||
{"value": "${newValue.toString()}"}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget _buildSecondRowWidget() {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
super._buildSecondRowWidget(),
|
|
||||||
_buildExtendedSlider()
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildExtendedSlider() {
|
|
||||||
return Slider(
|
|
||||||
min: this.minValue * _multiplier,
|
|
||||||
max: this.maxValue * _multiplier,
|
|
||||||
value: (doubleState <= this.maxValue) &&
|
|
||||||
(doubleState >= this.minValue)
|
|
||||||
? doubleState * _multiplier
|
|
||||||
: this.minValue * _multiplier,
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
widget.entity.state =
|
|
||||||
(value.roundToDouble() / _multiplier).toString();
|
|
||||||
});
|
|
||||||
/*eventBus.fire(new StateChangedEvent(widget.entity.entityId,
|
|
||||||
(value.roundToDouble() / _multiplier).toString(), true));*/
|
|
||||||
},
|
|
||||||
onChangeEnd: (value) {
|
|
||||||
setNewState(value.roundToDouble() / _multiplier);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget _buildActionWidget(BuildContext context) {
|
|
||||||
Widget stateWidget = Padding(
|
|
||||||
padding: EdgeInsets.only(right: rightWidgetPadding),
|
|
||||||
child: Text("${widget.entity.state}${widget.entity.unitOfMeasurement}",
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
style: new TextStyle(
|
|
||||||
fontSize: stateFontSize,
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
if (widget.widgetType == EntityWidgetType.regular) {
|
|
||||||
return Expanded(
|
|
||||||
//width: 200.0,
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: Slider(
|
|
||||||
min: this.minValue * _multiplier,
|
|
||||||
max: this.maxValue * _multiplier,
|
|
||||||
value: (doubleState <= this.maxValue) &&
|
|
||||||
(doubleState >= this.minValue)
|
|
||||||
? doubleState * _multiplier
|
|
||||||
: this.minValue * _multiplier,
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
widget.entity.state =
|
|
||||||
(value.roundToDouble() / _multiplier).toString();
|
|
||||||
});
|
|
||||||
/*eventBus.fire(new StateChangedEvent(widget.entity.entityId,
|
|
||||||
(value.roundToDouble() / _multiplier).toString(), true));*/
|
|
||||||
},
|
|
||||||
onChangeEnd: (value) {
|
|
||||||
setNewState(value.roundToDouble() / _multiplier);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
stateWidget
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return stateWidget;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
440
lib/entity_class/stateful_widgets.dart
Normal file
440
lib/entity_class/stateful_widgets.dart
Normal file
@ -0,0 +1,440 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class SwitchControlWidget extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_SwitchControlWidgetState createState() => _SwitchControlWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SwitchControlWidgetState extends State<SwitchControlWidget> {
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setNewState(newValue, Entity entity) {
|
||||||
|
setState(() {
|
||||||
|
entity.assumedState = newValue ? 'on' : 'off';
|
||||||
|
});
|
||||||
|
Timer(Duration(seconds: 2), (){
|
||||||
|
setState(() {
|
||||||
|
entity.assumedState = entity.state;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
eventBus.fire(new ServiceCallEvent(
|
||||||
|
entity.domain, (newValue as bool) ? "turn_on" : "turn_off", entity.entityId, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
return Switch(
|
||||||
|
value: entityModel.entity.assumedState == 'on',
|
||||||
|
onChanged: ((switchState) {
|
||||||
|
_setNewState(switchState, entityModel.entity);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ButtonControlWidget extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_ButtonControlWidgetState createState() => _ButtonControlWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ButtonControlWidgetState extends State<ButtonControlWidget> {
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setNewState(Entity entity) {
|
||||||
|
eventBus.fire(new ServiceCallEvent(entity.domain, "turn_on", entity.entityId, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
return FlatButton(
|
||||||
|
onPressed: (() {
|
||||||
|
_setNewState(entityModel.entity);
|
||||||
|
}),
|
||||||
|
child: Text(
|
||||||
|
"EXECUTE",
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style:
|
||||||
|
new TextStyle(fontSize: entityModel.entity.stateFontSize, color: Colors.blue),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextControlWidget extends StatefulWidget {
|
||||||
|
|
||||||
|
TextControlWidget({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_TextControlWidgetState createState() => _TextControlWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TextControlWidgetState extends State<TextControlWidget> {
|
||||||
|
String _tmpValue;
|
||||||
|
String _entityState;
|
||||||
|
String _entityDomain;
|
||||||
|
String _entityId;
|
||||||
|
int _minLength;
|
||||||
|
int _maxLength;
|
||||||
|
FocusNode _focusNode = FocusNode();
|
||||||
|
bool validValue = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_focusNode.addListener(_focusListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setNewState(newValue, domain, entityId) {
|
||||||
|
if (validate(newValue, _minLength, _maxLength)) {
|
||||||
|
eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId,
|
||||||
|
{"value": "$newValue"}));
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_tmpValue = _entityState;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validate(newValue, minLength, maxLength) {
|
||||||
|
if (newValue is String) {
|
||||||
|
validValue = (newValue.length >= minLength) &&
|
||||||
|
(maxLength == -1 ||
|
||||||
|
(newValue.length <= maxLength));
|
||||||
|
} else {
|
||||||
|
validValue = true;
|
||||||
|
}
|
||||||
|
return validValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _focusListener() {
|
||||||
|
if (!_focusNode.hasFocus && (_tmpValue != _entityState)) {
|
||||||
|
setNewState(_tmpValue, _entityDomain, _entityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
final TextEntity entity = entityModel.entity;
|
||||||
|
_entityState = entity.state;
|
||||||
|
_entityDomain = entity.domain;
|
||||||
|
_entityId = entity.entityId;
|
||||||
|
_minLength = entity.valueMinLength;
|
||||||
|
_maxLength = entity.valueMaxLength;
|
||||||
|
|
||||||
|
if (!_focusNode.hasFocus && (_tmpValue != entity.state)) {
|
||||||
|
_tmpValue = entity.state;
|
||||||
|
}
|
||||||
|
if (entity.isTextField || entity.isPasswordField) {
|
||||||
|
return Expanded(
|
||||||
|
//width: Entity.INPUT_WIDTH,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: _focusNode,
|
||||||
|
obscureText: entity.isPasswordField,
|
||||||
|
controller: new TextEditingController.fromValue(
|
||||||
|
new TextEditingValue(
|
||||||
|
text: _tmpValue,
|
||||||
|
selection:
|
||||||
|
new TextSelection.collapsed(offset: _tmpValue.length)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
_tmpValue = value;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
TheLogger.log("Warning", "Unsupported input mode for ${entity.entityId}");
|
||||||
|
return SimpleEntityState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_focusNode.removeListener(_focusListener);
|
||||||
|
_focusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SliderControlWidget extends StatefulWidget {
|
||||||
|
|
||||||
|
final bool expanded;
|
||||||
|
|
||||||
|
SliderControlWidget({Key key, @required this.expanded}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SliderControlWidgetState createState() => _SliderControlWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SliderControlWidgetState extends State<SliderControlWidget> {
|
||||||
|
int _multiplier = 1;
|
||||||
|
|
||||||
|
void setNewState(newValue, domain, entityId) {
|
||||||
|
eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId,
|
||||||
|
{"value": "${newValue.toString()}"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
final SliderEntity entity = entityModel.entity;
|
||||||
|
if (entity.valueStep < 1) {
|
||||||
|
_multiplier = 10;
|
||||||
|
} else if (entity.valueStep < 0.1) {
|
||||||
|
_multiplier = 100;
|
||||||
|
}
|
||||||
|
Widget slider = Slider(
|
||||||
|
min: entity.minValue * _multiplier,
|
||||||
|
max: entity.maxValue * _multiplier,
|
||||||
|
value: (entity.doubleState <= entity.maxValue) &&
|
||||||
|
(entity.doubleState >= entity.minValue)
|
||||||
|
? entity.doubleState * _multiplier
|
||||||
|
: entity.minValue * _multiplier,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
entity.state =
|
||||||
|
(value.roundToDouble() / _multiplier).toString();
|
||||||
|
});
|
||||||
|
eventBus.fire(new StateChangedEvent(entity.entityId,
|
||||||
|
(value.roundToDouble() / _multiplier).toString(), true));
|
||||||
|
|
||||||
|
},
|
||||||
|
onChangeEnd: (value) {
|
||||||
|
setNewState(value.roundToDouble() / _multiplier, entity.domain, entity.entityId);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (widget.expanded) {
|
||||||
|
return Expanded(
|
||||||
|
child: slider,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return slider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClimateControlWidget extends StatefulWidget {
|
||||||
|
|
||||||
|
ClimateControlWidget({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ClimateControlWidgetState createState() => _ClimateControlWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||||
|
|
||||||
|
bool _showPending = false;
|
||||||
|
bool _changedHere = false;
|
||||||
|
Timer _resetTimer;
|
||||||
|
double _tmpTemp = 0.0;
|
||||||
|
String _tmpOperationMode = "";
|
||||||
|
bool _tmpAwayMode = false;
|
||||||
|
double _temperatureStep = 0.2;
|
||||||
|
|
||||||
|
void _resetVars(ClimateEntity entity) {
|
||||||
|
_tmpTemp = entity.temperature;
|
||||||
|
_tmpOperationMode = entity.operationMode;
|
||||||
|
_tmpAwayMode = entity.awayMode;
|
||||||
|
_showPending = false;
|
||||||
|
_changedHere = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _temperatureUp(ClimateEntity entity) {
|
||||||
|
_tmpTemp += _temperatureStep;
|
||||||
|
_setTemperature(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _temperatureDown(ClimateEntity entity) {
|
||||||
|
_tmpTemp -= _temperatureStep;
|
||||||
|
_setTemperature(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setTemperature(ClimateEntity entity) {
|
||||||
|
setState(() {
|
||||||
|
_tmpTemp = double.parse(_tmpTemp.toStringAsFixed(1));
|
||||||
|
_changedHere = true;
|
||||||
|
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"temperature": "${_tmpTemp.toStringAsFixed(1)}"}));
|
||||||
|
_resetStateTimer(entity);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setOperationMode(ClimateEntity entity, value) {
|
||||||
|
setState(() {
|
||||||
|
_tmpOperationMode = value;
|
||||||
|
_changedHere = true;
|
||||||
|
eventBus.fire(new ServiceCallEvent(entity.domain, "set_operation_mode", entity.entityId,{"operation_mode": "$_tmpOperationMode"}));
|
||||||
|
_resetStateTimer(entity);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setAwayMode(ClimateEntity entity, value) {
|
||||||
|
setState(() {
|
||||||
|
_tmpAwayMode = value;
|
||||||
|
_changedHere = true;
|
||||||
|
eventBus.fire(new ServiceCallEvent(entity.domain, "set_away_mode", entity.entityId,{"away_mode": "${_tmpAwayMode ? 'on' : 'off'}"}));
|
||||||
|
_resetStateTimer(entity);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _resetStateTimer(ClimateEntity entity) {
|
||||||
|
if (_resetTimer!=null) {
|
||||||
|
_resetTimer.cancel();
|
||||||
|
}
|
||||||
|
_resetTimer = Timer(Duration(seconds: 3), () {
|
||||||
|
setState(() {});
|
||||||
|
_resetVars(entity);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
final ClimateEntity entity = entityModel.entity;
|
||||||
|
if (_changedHere) {
|
||||||
|
_showPending = (_tmpTemp != entity.temperature);
|
||||||
|
_changedHere = false;
|
||||||
|
} else {
|
||||||
|
_resetTimer?.cancel();
|
||||||
|
_resetVars(entity);
|
||||||
|
}
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(entity.leftWidgetPadding, entity.rowPadding, entity.rightWidgetPadding, 0.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text("Target temperature for ${_tmpOperationMode != 'off' ? _tmpOperationMode : ''}", style: TextStyle(
|
||||||
|
fontSize: entity.stateFontSize
|
||||||
|
)),
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"$_tmpTemp",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: entity.largeFontSize,
|
||||||
|
color: _showPending ? Colors.red : Colors.black
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.keyboard_arrow_up),
|
||||||
|
iconSize: 30.0,
|
||||||
|
onPressed: () => _temperatureUp(entity),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.keyboard_arrow_down),
|
||||||
|
iconSize: 30.0,
|
||||||
|
onPressed: () => _temperatureDown(entity),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text("Operation", style: TextStyle(
|
||||||
|
fontSize: entity.stateFontSize
|
||||||
|
)),
|
||||||
|
DropdownButton<String>(
|
||||||
|
value: "$_tmpOperationMode",
|
||||||
|
iconSize: 30.0,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: entity.largeFontSize,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
items: entity.operationList.map((String value) {
|
||||||
|
return new DropdownMenuItem<String>(
|
||||||
|
value: value,
|
||||||
|
child: new Text(value),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (mode) => _setOperationMode(entity, mode),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: entity.rowPadding),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"Away mode",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: entity.stateFontSize
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Switch(
|
||||||
|
onChanged: (value) => _setAwayMode(entity, value),
|
||||||
|
value: _tmpAwayMode,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_resetTimer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectControlWidget extends StatefulWidget {
|
||||||
|
|
||||||
|
SelectControlWidget({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SelectControlWidgetState createState() => _SelectControlWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SelectControlWidgetState extends State<SelectControlWidget> {
|
||||||
|
|
||||||
|
void setNewState(domain, entityId, newValue) {
|
||||||
|
eventBus.fire(new ServiceCallEvent(domain, "select_option", entityId,
|
||||||
|
{"option": "$newValue"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
final SelectEntity entity = entityModel.entity;
|
||||||
|
Widget ctrl;
|
||||||
|
if (entity.listOptions.isNotEmpty) {
|
||||||
|
ctrl = DropdownButton<String>(
|
||||||
|
value: entity.state,
|
||||||
|
items: entity.listOptions.map((String value) {
|
||||||
|
return new DropdownMenuItem<String>(
|
||||||
|
value: value,
|
||||||
|
child: new Text(value),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (_) {
|
||||||
|
setNewState(entity.domain, entity.entityId,_);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ctrl = Text('---');
|
||||||
|
}
|
||||||
|
return Expanded(
|
||||||
|
//width: Entity.INPUT_WIDTH,
|
||||||
|
child: ctrl,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
437
lib/entity_class/stateless_widgets.dart
Normal file
437
lib/entity_class/stateless_widgets.dart
Normal file
@ -0,0 +1,437 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class EntityWidgetsSizes {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class EntityModel extends InheritedWidget {
|
||||||
|
|
||||||
|
const EntityModel({
|
||||||
|
Key key,
|
||||||
|
@required this.entity,
|
||||||
|
@required this.handleTap,
|
||||||
|
@required Widget child,
|
||||||
|
}) : super(key: key, child: child);
|
||||||
|
|
||||||
|
final Entity entity;
|
||||||
|
final bool handleTap;
|
||||||
|
|
||||||
|
static EntityModel of(BuildContext context) {
|
||||||
|
return context.inheritFromWidgetOfExactType(EntityModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool updateShouldNotify(InheritedWidget oldWidget) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultEntityContainer extends StatelessWidget {
|
||||||
|
|
||||||
|
DefaultEntityContainer({
|
||||||
|
Key key,
|
||||||
|
@required this.state,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final Widget state;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
return SizedBox(
|
||||||
|
height: entityModel.entity.widgetHeight,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
EntityIcon(),
|
||||||
|
Expanded(
|
||||||
|
child: EntityName(),
|
||||||
|
),
|
||||||
|
state
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class EntityPageContainer extends StatelessWidget {
|
||||||
|
|
||||||
|
EntityPageContainer({Key key, @required this.children}) : super(key: key);
|
||||||
|
|
||||||
|
final List<Widget> children;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView(
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleEntityState extends StatelessWidget {
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
return Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsets.fromLTRB(0.0, 0.0, entityModel.entity.rightWidgetPadding, 0.0),
|
||||||
|
child: GestureDetector(
|
||||||
|
child: Text(
|
||||||
|
"${entityModel.entity.state}${entityModel.entity.unitOfMeasurement}",
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: new TextStyle(
|
||||||
|
fontSize: entityModel.entity.stateFontSize,
|
||||||
|
)),
|
||||||
|
onTap: () => entityModel.handleTap ? eventBus.fire(new ShowEntityPageEvent(entityModel.entity)) : null,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class EntityName extends StatelessWidget {
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
return GestureDetector(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(right: 10.0),
|
||||||
|
child: Text(
|
||||||
|
"${entityModel.entity.displayName}",
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
softWrap: false,
|
||||||
|
style: TextStyle(fontSize: entityModel.entity.nameFontSize),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () => entityModel.handleTap ? eventBus.fire(new ShowEntityPageEvent(entityModel.entity)) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class EntityIcon extends StatelessWidget {
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
return GestureDetector(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(entityModel.entity.leftWidgetPadding, 0.0, 12.0, 0.0),
|
||||||
|
//TODO: move createIconWidgetFromEntityData into this widget
|
||||||
|
child: MaterialDesignIcons.createIconWidgetFromEntityData(
|
||||||
|
entityModel.entity,
|
||||||
|
entityModel.entity.iconSize,
|
||||||
|
Entity.STATE_ICONS_COLORS[entityModel.entity.state] ?? Entity.STATE_ICONS_COLORS["default"]),
|
||||||
|
),
|
||||||
|
onTap: () => entityModel.handleTap ? eventBus.fire(new ShowEntityPageEvent(entityModel.entity)) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class LastUpdatedWidget extends StatelessWidget {
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(
|
||||||
|
entityModel.entity.leftWidgetPadding, 0.0, 0.0, 0.0),
|
||||||
|
child: Text(
|
||||||
|
'${entityModel.entity.lastUpdated}',
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: entityModel.entity.smallFontSize, color: Colors.black26),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class EntityAttributesList extends StatelessWidget {
|
||||||
|
|
||||||
|
EntityAttributesList({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
List<Widget> attrs = [];
|
||||||
|
if ((entityModel.entity.attributesToShow == null) || (entityModel.entity.attributesToShow.contains("all"))) {
|
||||||
|
entityModel.entity.attributes.forEach((name, value){
|
||||||
|
attrs.add(
|
||||||
|
_buildSingleAttribute(entityModel.entity, "$name", "$value")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
entityModel.entity.attributesToShow.forEach((String attr) {
|
||||||
|
String attrValue = entityModel.entity.getAttribute("$attr");
|
||||||
|
if (attrValue != null) {
|
||||||
|
attrs.add(
|
||||||
|
_buildSingleAttribute(entityModel.entity, "$attr", "$attrValue")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Column(
|
||||||
|
children: attrs,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSingleAttribute(Entity entity, String name, String value) {
|
||||||
|
return Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(entity.leftWidgetPadding, entity.rowPadding, 0.0, 0.0),
|
||||||
|
child: Text(
|
||||||
|
"$name",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0, entity.rowPadding, entity.rightWidgetPadding, 0.0),
|
||||||
|
child: Text(
|
||||||
|
"$value",
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Badge extends StatelessWidget {
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
double iconSize = 26.0;
|
||||||
|
Widget badgeIcon;
|
||||||
|
String onBadgeTextValue;
|
||||||
|
Color iconColor = Entity.badgeColors[entityModel.entity.domain] ?? Entity.badgeColors["default"];
|
||||||
|
switch (entityModel.entity.domain) {
|
||||||
|
case "sun": {
|
||||||
|
badgeIcon = entityModel.entity.state == "below_horizon" ?
|
||||||
|
Icon(
|
||||||
|
MaterialDesignIcons.createIconDataFromIconCode(0xf0dc),
|
||||||
|
size: iconSize,
|
||||||
|
) :
|
||||||
|
Icon(
|
||||||
|
MaterialDesignIcons.createIconDataFromIconCode(0xf5a8),
|
||||||
|
size: iconSize,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "sensor": {
|
||||||
|
onBadgeTextValue = entityModel.entity.unitOfMeasurement;
|
||||||
|
badgeIcon = Center(
|
||||||
|
child: Text(
|
||||||
|
"${entityModel.entity.state}",
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
softWrap: false,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 17.0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "device_tracker": {
|
||||||
|
badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData(entityModel.entity, iconSize,Colors.black);
|
||||||
|
onBadgeTextValue = entityModel.entity.state;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData(entityModel.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 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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(
|
||||||
|
"${entityModel.entity.displayName}",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 12.0),
|
||||||
|
softWrap: true,
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () => eventBus.fire(new ShowEntityPageEvent(entityModel.entity))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClimateStateWidget extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
final ClimateEntity entity = entityModel.entity;
|
||||||
|
return Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsets.fromLTRB(0.0, 0.0, entityModel.entity.rightWidgetPadding, 0.0),
|
||||||
|
child: GestureDetector(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
"${entity.state}",
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: new TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: entityModel.entity.stateFontSize,
|
||||||
|
)),
|
||||||
|
Text(
|
||||||
|
" ${entity.attributes["temperature"]}",
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: new TextStyle(
|
||||||
|
fontSize: entityModel.entity.stateFontSize,
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Currently: ${entity.attributes["current_temperature"]}",
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: new TextStyle(
|
||||||
|
fontSize: entityModel.entity.stateFontSize,
|
||||||
|
color: Colors.black45
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () => entityModel.handleTap ? eventBus.fire(new ShowEntityPageEvent(entity)) : null,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class DateTimeStateWidget extends StatelessWidget {
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
final DateTimeEntity entity = entityModel.entity;
|
||||||
|
return Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsets.fromLTRB(0.0, 0.0, entity.rightWidgetPadding, 0.0),
|
||||||
|
child: GestureDetector(
|
||||||
|
child: Text(
|
||||||
|
"${entity.formattedState}",
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: new TextStyle(
|
||||||
|
fontSize: entity.stateFontSize,
|
||||||
|
)),
|
||||||
|
onTap: () => _handleStateTap(context, entity),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleStateTap(BuildContext context, DateTimeEntity entity) {
|
||||||
|
if (entity.hasDate) {
|
||||||
|
_showDatePicker(context, entity).then((date) {
|
||||||
|
if (date != null) {
|
||||||
|
if (entity.hasTime) {
|
||||||
|
_showTimePicker(context, entity).then((time){
|
||||||
|
entity.setNewState({"date": "${formatDate(date, [yyyy, '-', mm, '-', dd])}", "time": "${formatDate(DateTime(1970, 1, 1, time.hour, time.minute), [HH, ':', nn])}"});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
entity.setNewState({"date": "${formatDate(date, [yyyy, '-', mm, '-', dd])}"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (entity.hasTime) {
|
||||||
|
_showTimePicker(context, entity).then((time){
|
||||||
|
if (time != null) {
|
||||||
|
entity.setNewState({"time": "${formatDate(DateTime(1970, 1, 1, time.hour, time.minute), [HH, ':', nn])}"});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
TheLogger.log("Warning", "${entity.entityId} has no date and no time");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _showDatePicker(BuildContext context, DateTimeEntity entity) {
|
||||||
|
return showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: entity.dateTimeState,
|
||||||
|
firstDate: DateTime(1970),
|
||||||
|
lastDate: DateTime(2037) //Unix timestamp will finish at Jan 19, 2038
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _showTimePicker(BuildContext context, DateTimeEntity entity) {
|
||||||
|
return showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime: TimeOfDay.fromDateTime(entity.dateTimeState)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class _SunEntityWidgetState extends _EntityWidgetState {
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map <String, String> _displayStates = {
|
|
||||||
"below_horizon": "Below horizon",
|
|
||||||
"above_horizon": "Above horizon"
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class _SwitchEntityWidgetState extends _EntityWidgetState {
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void setNewState(newValue) {
|
|
||||||
setState(() {
|
|
||||||
widget.entity.assumedState = newValue ? 'on' : 'off';
|
|
||||||
});
|
|
||||||
Timer(Duration(seconds: 2), (){
|
|
||||||
setState(() {
|
|
||||||
widget.entity.assumedState = widget.entity.state;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
eventBus.fire(new ServiceCallEvent(
|
|
||||||
widget.entity.domain, (newValue as bool) ? "turn_on" : "turn_off", widget.entity.entityId, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget _buildActionWidget(BuildContext context) {
|
|
||||||
return Switch(
|
|
||||||
value: widget.entity.assumedState == 'on',
|
|
||||||
onChanged: ((switchState) {
|
|
||||||
setNewState(switchState);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class _TextEntityWidgetState extends _EntityWidgetState {
|
|
||||||
String _tmpValue;
|
|
||||||
FocusNode _focusNode = FocusNode();
|
|
||||||
bool validValue = false;
|
|
||||||
|
|
||||||
int get valueMinLength => widget.entity.attributes["min"] ?? -1;
|
|
||||||
int get valueMaxLength => widget.entity.attributes["max"] ?? -1;
|
|
||||||
String get valuePattern => widget.entity.attributes["pattern"] ?? null;
|
|
||||||
bool get isTextField => widget.entity.attributes["mode"] == "text";
|
|
||||||
bool get isPasswordField => widget.entity.attributes["mode"] == "password";
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_focusNode.addListener(_focusListener);
|
|
||||||
_tmpValue = widget.entity.state;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void setNewState(newValue) {
|
|
||||||
if (validate(newValue)) {
|
|
||||||
eventBus.fire(new ServiceCallEvent(widget.entity.domain, "set_value", widget.entity.entityId,
|
|
||||||
{"value": "$newValue"}));
|
|
||||||
} else {
|
|
||||||
setState(() {
|
|
||||||
_tmpValue = widget.entity.state;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool validate(newValue) {
|
|
||||||
if (newValue is String) {
|
|
||||||
validValue = (newValue.length >= this.valueMinLength) &&
|
|
||||||
(this.valueMaxLength == -1 ||
|
|
||||||
(newValue.length <= this.valueMaxLength));
|
|
||||||
} else {
|
|
||||||
validValue = true;
|
|
||||||
}
|
|
||||||
return validValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _focusListener() {
|
|
||||||
if (!_focusNode.hasFocus && (_tmpValue != widget.entity.state)) {
|
|
||||||
setNewState(_tmpValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget _buildActionWidget(BuildContext context) {
|
|
||||||
if (!_focusNode.hasFocus && (_tmpValue != widget.entity.state)) {
|
|
||||||
_tmpValue = widget.entity.state;
|
|
||||||
}
|
|
||||||
if (this.isTextField || this.isPasswordField) {
|
|
||||||
return Expanded(
|
|
||||||
//width: Entity.INPUT_WIDTH,
|
|
||||||
child: TextField(
|
|
||||||
focusNode: _focusNode,
|
|
||||||
obscureText: this.isPasswordField,
|
|
||||||
controller: new TextEditingController.fromValue(
|
|
||||||
new TextEditingValue(
|
|
||||||
text: _tmpValue,
|
|
||||||
selection:
|
|
||||||
new TextSelection.collapsed(offset: _tmpValue.length)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
onChanged: (value) {
|
|
||||||
_tmpValue = value;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
TheLogger.log("Warning", "Unsupported input mode for ${widget.entity.entityId}");
|
|
||||||
return super._buildActionWidget(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_focusNode.removeListener(_focusListener);
|
|
||||||
_focusNode.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -29,7 +29,39 @@ class EntityCollection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Entity _createEntityInstance(rawEntityData) {
|
Entity _createEntityInstance(rawEntityData) {
|
||||||
return Entity(rawEntityData);
|
switch (rawEntityData["entity_id"].split(".")[0]) {
|
||||||
|
case 'sun': {
|
||||||
|
return SunEntity(rawEntityData);
|
||||||
|
}
|
||||||
|
case "automation":
|
||||||
|
case "input_boolean":
|
||||||
|
case "switch":
|
||||||
|
case "light": {
|
||||||
|
return SwitchEntity(rawEntityData);
|
||||||
|
}
|
||||||
|
case "script":
|
||||||
|
case "scene": {
|
||||||
|
return ButtonEntity(rawEntityData);
|
||||||
|
}
|
||||||
|
case "input_datetime": {
|
||||||
|
return DateTimeEntity(rawEntityData);
|
||||||
|
}
|
||||||
|
case "input_select": {
|
||||||
|
return SelectEntity(rawEntityData);
|
||||||
|
}
|
||||||
|
case "input_number": {
|
||||||
|
return SliderEntity(rawEntityData);
|
||||||
|
}
|
||||||
|
case "input_text": {
|
||||||
|
return TextEntity(rawEntityData);
|
||||||
|
}
|
||||||
|
case "climate": {
|
||||||
|
return ClimateEntity(rawEntityData);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return Entity(rawEntityData);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateState(Map rawStateData) {
|
void updateState(Map rawStateData) {
|
||||||
|
@ -14,14 +14,8 @@ import 'package:date_format/date_format.dart';
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
part 'entity_class/entity.class.dart';
|
part 'entity_class/entity.class.dart';
|
||||||
part 'entity_class/button_entity.class.dart';
|
part 'entity_class/stateless_widgets.dart';
|
||||||
part 'entity_class/datetime_entity.class.dart';
|
part 'entity_class/stateful_widgets.dart';
|
||||||
part 'entity_class/select_entity.class.dart';
|
|
||||||
part 'entity_class/slider_entity.class.dart';
|
|
||||||
part 'entity_class/switch_entity.class.dart';
|
|
||||||
part 'entity_class/text_entity.class.dart';
|
|
||||||
part 'entity_class/sun_entity.class.dart';
|
|
||||||
part 'entity_class/climate_entity.class.dart';
|
|
||||||
|
|
||||||
part 'settings.page.dart';
|
part 'settings.page.dart';
|
||||||
part 'home_assistant.class.dart';
|
part 'home_assistant.class.dart';
|
||||||
|
@ -124,7 +124,7 @@ class ViewWidgetState extends State<ViewWidget> {
|
|||||||
List<Widget> _buildBadges(BuildContext context, List<Entity> badges) {
|
List<Widget> _buildBadges(BuildContext context, List<Entity> badges) {
|
||||||
List<Widget> result = [];
|
List<Widget> result = [];
|
||||||
badges.forEach((Entity entity) {
|
badges.forEach((Entity entity) {
|
||||||
result.add(entity.buildWidget(context, EntityWidgetType.badge));
|
result.add(entity.buildBadgeWidget(context));
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -488,5 +488,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.15"
|
version: "2.1.15"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.0.0 <=2.1.0-dev.3.1.flutter-760a9690c2"
|
dart: ">=2.0.0 <=2.1.0-dev.5.0.flutter-a2eb050044"
|
||||||
flutter: ">=0.1.4 <2.0.0"
|
flutter: ">=0.1.4 <2.0.0"
|
||||||
|
Reference in New Issue
Block a user