Project structure change in progress
This commit is contained in:
@ -0,0 +1,13 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class AlarmControlPanelEntity extends Entity {
|
||||
AlarmControlPanelEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
return AlarmControlPanelControlsWidget(
|
||||
extended: false,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,262 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class AlarmControlPanelControlsWidget extends StatefulWidget {
|
||||
|
||||
final bool extended;
|
||||
final List states;
|
||||
|
||||
const AlarmControlPanelControlsWidget({Key key, @required this.extended, this.states}) : super(key: key);
|
||||
|
||||
@override
|
||||
_AlarmControlPanelControlsWidgetWidgetState createState() => _AlarmControlPanelControlsWidgetWidgetState();
|
||||
|
||||
}
|
||||
|
||||
class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPanelControlsWidget> {
|
||||
|
||||
String code = "";
|
||||
List supportedStates;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
supportedStates = widget.states ?? ["arm_home", "arm_away"];
|
||||
}
|
||||
|
||||
|
||||
void _callService(AlarmControlPanelEntity entity, String service) {
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, service, entity.entityId,
|
||||
{"code": "$code"}));
|
||||
setState(() {
|
||||
code = "";
|
||||
});
|
||||
}
|
||||
|
||||
void _pinPadHandler(value) {
|
||||
setState(() {
|
||||
code += "$value";
|
||||
});
|
||||
}
|
||||
|
||||
void _pinPadClear() {
|
||||
setState(() {
|
||||
code = "";
|
||||
});
|
||||
}
|
||||
|
||||
void _askToTrigger(AlarmControlPanelEntity entity) {
|
||||
// flutter defined function
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
// return object of type Dialog
|
||||
return AlertDialog(
|
||||
title: new Text("Are you sure?"),
|
||||
content: new Text("Are you sure want to trigger alarm ${entity.displayName}?"),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: new Text("Yes"),
|
||||
onPressed: () {
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "alarm_trigger", entity.entityId, null));
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: new Text("No"),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final AlarmControlPanelEntity entity = entityModel.entityWrapper.entity;
|
||||
List<Widget> buttons = [];
|
||||
if (entity.state == EntityState.alarm_disarmed) {
|
||||
if (supportedStates.contains("arm_home")) {
|
||||
buttons.add(
|
||||
RaisedButton(
|
||||
onPressed: () => _callService(entity, "alarm_arm_home"),
|
||||
child: Text("ARM HOME"),
|
||||
)
|
||||
);
|
||||
}
|
||||
if (supportedStates.contains("arm_away")) {
|
||||
buttons.add(
|
||||
RaisedButton(
|
||||
onPressed: () => _callService(entity, "alarm_arm_away"),
|
||||
child: Text("ARM AWAY"),
|
||||
)
|
||||
);
|
||||
}
|
||||
if (widget.extended) {
|
||||
if (supportedStates.contains("arm_night")) {
|
||||
buttons.add(
|
||||
RaisedButton(
|
||||
onPressed: () => _callService(entity, "alarm_arm_night"),
|
||||
child: Text("ARM NIGHT"),
|
||||
)
|
||||
);
|
||||
}
|
||||
if (supportedStates.contains("arm_custom_bypass")) {
|
||||
buttons.add(
|
||||
RaisedButton(
|
||||
onPressed: () =>
|
||||
_callService(entity, "alarm_arm_custom_bypass"),
|
||||
child: Text("ARM CUSTOM BYPASS"),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buttons.add(
|
||||
RaisedButton(
|
||||
onPressed: () => _callService(entity, "alarm_disarm"),
|
||||
child: Text("DISARM"),
|
||||
)
|
||||
);
|
||||
}
|
||||
Widget pinPad;
|
||||
if (entity.attributes["code_format"] == null) {
|
||||
pinPad = Container(width: 0.0, height: 0.0,);
|
||||
} else {
|
||||
pinPad = Padding(
|
||||
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Wrap(
|
||||
spacing: 5.0,
|
||||
children: <Widget>[
|
||||
RaisedButton(
|
||||
onPressed: () => _pinPadHandler("1"),
|
||||
child: Text("1"),
|
||||
),
|
||||
RaisedButton(
|
||||
onPressed: () => _pinPadHandler("2"),
|
||||
child: Text("2"),
|
||||
),
|
||||
RaisedButton(
|
||||
onPressed: () => _pinPadHandler("3"),
|
||||
child: Text("3"),
|
||||
)
|
||||
],
|
||||
),
|
||||
Wrap(
|
||||
spacing: 5.0,
|
||||
children: <Widget>[
|
||||
RaisedButton(
|
||||
onPressed: () => _pinPadHandler("4"),
|
||||
child: Text("4"),
|
||||
),
|
||||
RaisedButton(
|
||||
onPressed: () => _pinPadHandler("5"),
|
||||
child: Text("5"),
|
||||
),
|
||||
RaisedButton(
|
||||
onPressed: () => _pinPadHandler("6"),
|
||||
child: Text("6"),
|
||||
)
|
||||
],
|
||||
),
|
||||
Wrap(
|
||||
spacing: 5.0,
|
||||
children: <Widget>[
|
||||
RaisedButton(
|
||||
onPressed: () => _pinPadHandler("7"),
|
||||
child: Text("7"),
|
||||
),
|
||||
RaisedButton(
|
||||
onPressed: () => _pinPadHandler("8"),
|
||||
child: Text("8"),
|
||||
),
|
||||
RaisedButton(
|
||||
onPressed: () => _pinPadHandler("9"),
|
||||
child: Text("9"),
|
||||
)
|
||||
],
|
||||
),
|
||||
Wrap(
|
||||
spacing: 5.0,
|
||||
alignment: WrapAlignment.end,
|
||||
children: <Widget>[
|
||||
RaisedButton(
|
||||
onPressed: () => _pinPadHandler("0"),
|
||||
child: Text("0"),
|
||||
),
|
||||
RaisedButton(
|
||||
onPressed: () => _pinPadClear(),
|
||||
child: Text("CLEAR"),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
Widget inputWrapper;
|
||||
if (entity.attributes["code_format"] == null) {
|
||||
inputWrapper = Container(width: 0.0, height: 0.0,);
|
||||
} else {
|
||||
inputWrapper = Container(
|
||||
width: 150.0,
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Alarm Code"
|
||||
),
|
||||
//focusNode: _focusNode,
|
||||
obscureText: true,
|
||||
controller: new TextEditingController.fromValue(
|
||||
new TextEditingValue(
|
||||
text: code,
|
||||
selection:
|
||||
new TextSelection.collapsed(offset: code.length)
|
||||
)
|
||||
),
|
||||
onChanged: (value) {
|
||||
code = value;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
Widget buttonsWrapper = Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: Sizes.rowPadding),
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 15.0,
|
||||
runSpacing: Sizes.rowPadding,
|
||||
children: buttons
|
||||
)
|
||||
);
|
||||
Widget triggerButton = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
FlatButton(
|
||||
child: Text(
|
||||
"TRIGGER",
|
||||
style: TextStyle(color: Colors.redAccent)
|
||||
),
|
||||
onPressed: () => _askToTrigger(entity),
|
||||
)
|
||||
]
|
||||
);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
widget.extended ? buttonsWrapper : inputWrapper,
|
||||
widget.extended ? inputWrapper : buttonsWrapper,
|
||||
widget.extended ? pinPad : triggerButton
|
||||
]
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
27
lib/entities/automation/automation_entity.class.dart
Normal file
27
lib/entities/automation/automation_entity.class.dart
Normal file
@ -0,0 +1,27 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class AutomationEntity extends Entity {
|
||||
AutomationEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return SwitchStateWidget();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
FlatServiceButton(
|
||||
serviceDomain: domain,
|
||||
entityId: entityId,
|
||||
text: "TRIGGER",
|
||||
serviceName: "trigger",
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
16
lib/entities/button/button_entity.class.dart
Normal file
16
lib/entities/button/button_entity.class.dart
Normal file
@ -0,0 +1,16 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class ButtonEntity extends Entity {
|
||||
ButtonEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return FlatServiceButton(
|
||||
entityId: entityId,
|
||||
serviceDomain: domain,
|
||||
serviceName: 'turn_on',
|
||||
text: domain == "scene" ? "ACTIVATE" : "EXECUTE",
|
||||
);
|
||||
}
|
||||
}
|
17
lib/entities/camera/camera_entity.class.dart
Normal file
17
lib/entities/camera/camera_entity.class.dart
Normal file
@ -0,0 +1,17 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class CameraEntity extends Entity {
|
||||
|
||||
static const SUPPORT_ON_OFF = 1;
|
||||
|
||||
CameraEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
bool get supportOnOff => ((supportedFeatures &
|
||||
CameraEntity.SUPPORT_ON_OFF) ==
|
||||
CameraEntity.SUPPORT_ON_OFF);
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
return CameraStreamView();
|
||||
}
|
||||
}
|
114
lib/entities/climate/climate_entity.class.dart
Normal file
114
lib/entities/climate/climate_entity.class.dart
Normal file
@ -0,0 +1,114 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class ClimateEntity extends Entity {
|
||||
|
||||
@override
|
||||
EntityHistoryConfig historyConfig = EntityHistoryConfig(
|
||||
chartType: EntityHistoryWidgetType.numericAttributes,
|
||||
numericState: false,
|
||||
numericAttributesToShow: ["current_temperature"]
|
||||
);
|
||||
|
||||
static const SUPPORT_TARGET_TEMPERATURE = 1;
|
||||
static const SUPPORT_TARGET_TEMPERATURE_RANGE = 2;
|
||||
static const SUPPORT_TARGET_HUMIDITY = 4;
|
||||
static const SUPPORT_FAN_MODE = 8;
|
||||
static const SUPPORT_PRESET_MODE = 16;
|
||||
static const SUPPORT_SWING_MODE = 32;
|
||||
static const SUPPORT_AUX_HEAT = 64;
|
||||
|
||||
|
||||
//static const SUPPORT_OPERATION_MODE = 16;
|
||||
//static const SUPPORT_HOLD_MODE = 256;
|
||||
//static const SUPPORT_AWAY_MODE = 1024;
|
||||
//static const SUPPORT_ON_OFF = 4096;
|
||||
|
||||
ClimateEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
bool get supportTargetTemperature => ((supportedFeatures &
|
||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE) ==
|
||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE);
|
||||
bool get supportTargetTemperatureRange => ((supportedFeatures &
|
||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_RANGE) ==
|
||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_RANGE);
|
||||
bool get supportTargetHumidity => ((supportedFeatures &
|
||||
ClimateEntity.SUPPORT_TARGET_HUMIDITY) ==
|
||||
ClimateEntity.SUPPORT_TARGET_HUMIDITY);
|
||||
bool get supportFanMode =>
|
||||
((supportedFeatures & ClimateEntity.SUPPORT_FAN_MODE) ==
|
||||
ClimateEntity.SUPPORT_FAN_MODE);
|
||||
bool get supportSwingMode =>
|
||||
((supportedFeatures & ClimateEntity.SUPPORT_SWING_MODE) ==
|
||||
ClimateEntity.SUPPORT_SWING_MODE);
|
||||
bool get supportPresetMode =>
|
||||
((supportedFeatures & ClimateEntity.SUPPORT_PRESET_MODE) ==
|
||||
ClimateEntity.SUPPORT_PRESET_MODE);
|
||||
bool get supportAuxHeat =>
|
||||
((supportedFeatures & ClimateEntity.SUPPORT_AUX_HEAT) ==
|
||||
ClimateEntity.SUPPORT_AUX_HEAT);
|
||||
|
||||
List<String> get hvacModes => attributes["hvac_modes"] != null
|
||||
? (attributes["hvac_modes"] as List).cast<String>()
|
||||
: null;
|
||||
List<String> get fanModes => attributes["fan_modes"] != null
|
||||
? (attributes["fan_modes"] as List).cast<String>()
|
||||
: null;
|
||||
List<String> get presetModes => attributes["preset_modes"] != null
|
||||
? (attributes["preset_modes"] as List).cast<String>()
|
||||
: null;
|
||||
List<String> get swingModes => attributes["swing_modes"] != null
|
||||
? (attributes["swing_modes"] as List).cast<String>()
|
||||
: null;
|
||||
double get temperature => _getDoubleAttributeValue('temperature');
|
||||
double get currentTemperature => _getDoubleAttributeValue('current_temperature');
|
||||
double get targetHigh => _getDoubleAttributeValue('target_temp_high');
|
||||
double get targetLow => _getDoubleAttributeValue('target_temp_low');
|
||||
double get maxTemp => _getDoubleAttributeValue('max_temp') ?? 100.0;
|
||||
double get minTemp => _getDoubleAttributeValue('min_temp') ?? -100.0;
|
||||
double get targetHumidity => _getDoubleAttributeValue('humidity');
|
||||
double get maxHumidity => _getDoubleAttributeValue('max_humidity');
|
||||
double get minHumidity => _getDoubleAttributeValue('min_humidity');
|
||||
double get temperatureStep => _getDoubleAttributeValue('target_temp_step') ?? 0.5;
|
||||
String get hvacAction => attributes['hvac_action'];
|
||||
String get fanMode => attributes['fan_mode'];
|
||||
String get presetMode => attributes['preset_mode'];
|
||||
String get swingMode => attributes['swing_mode'];
|
||||
bool get awayMode => attributes['away_mode'] == "on";
|
||||
//bool get isOff => state == EntityState.off;
|
||||
bool get auxHeat => attributes['aux_heat'] == "on";
|
||||
|
||||
@override
|
||||
void update(Map rawData, String webHost) {
|
||||
super.update(rawData, webHost);
|
||||
if (supportTargetTemperature) {
|
||||
historyConfig.numericAttributesToShow.add("temperature");
|
||||
}
|
||||
if (supportTargetTemperatureRange) {
|
||||
historyConfig.numericAttributesToShow.add("target_temp_high");
|
||||
historyConfig.numericAttributesToShow.add("target_temp_low");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return ClimateStateWidget();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
return ClimateControlWidget();
|
||||
}
|
||||
|
||||
@override
|
||||
double _getDoubleAttributeValue(String attributeName) {
|
||||
var temp1 = attributes["$attributeName"];
|
||||
if (temp1 is int) {
|
||||
return temp1.toDouble();
|
||||
} else if (temp1 is double) {
|
||||
return temp1;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
460
lib/entities/climate/widgets/climate_controls.dart
Normal file
460
lib/entities/climate/widgets/climate_controls.dart
Normal file
@ -0,0 +1,460 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
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;
|
||||
Timer _tempThrottleTimer;
|
||||
Timer _targetTempThrottleTimer;
|
||||
double _tmpTemperature = 0.0;
|
||||
double _tmpTargetLow = 0.0;
|
||||
double _tmpTargetHigh = 0.0;
|
||||
double _tmpTargetHumidity = 0.0;
|
||||
String _tmpHVACMode;
|
||||
String _tmpFanMode;
|
||||
String _tmpSwingMode;
|
||||
String _tmpPresetMode;
|
||||
//bool _tmpIsOff = false;
|
||||
bool _tmpAuxHeat = false;
|
||||
|
||||
void _resetVars(ClimateEntity entity) {
|
||||
_tmpTemperature = entity.temperature;
|
||||
_tmpTargetHigh = entity.targetHigh;
|
||||
_tmpTargetLow = entity.targetLow;
|
||||
_tmpHVACMode = entity.state;
|
||||
_tmpFanMode = entity.fanMode;
|
||||
_tmpSwingMode = entity.swingMode;
|
||||
_tmpPresetMode = entity.presetMode;
|
||||
//_tmpIsOff = entity.isOff;
|
||||
_tmpAuxHeat = entity.auxHeat;
|
||||
_tmpTargetHumidity = entity.targetHumidity;
|
||||
|
||||
_showPending = false;
|
||||
_changedHere = false;
|
||||
}
|
||||
|
||||
void _temperatureUp(ClimateEntity entity) {
|
||||
_tmpTemperature = ((_tmpTemperature + entity.temperatureStep) <= entity.maxTemp) ? _tmpTemperature + entity.temperatureStep : entity.maxTemp;
|
||||
_setTemperature(entity);
|
||||
}
|
||||
|
||||
void _temperatureDown(ClimateEntity entity) {
|
||||
_tmpTemperature = ((_tmpTemperature - entity.temperatureStep) >= entity.minTemp) ? _tmpTemperature - entity.temperatureStep : entity.minTemp;
|
||||
_setTemperature(entity);
|
||||
}
|
||||
|
||||
void _targetLowUp(ClimateEntity entity) {
|
||||
_tmpTargetLow = ((_tmpTargetLow + entity.temperatureStep) <= entity.maxTemp) ? _tmpTargetLow + entity.temperatureStep : entity.maxTemp;
|
||||
_setTargetTemp(entity);
|
||||
}
|
||||
|
||||
void _targetLowDown(ClimateEntity entity) {
|
||||
_tmpTargetLow = ((_tmpTargetLow - entity.temperatureStep) >= entity.minTemp) ? _tmpTargetLow - entity.temperatureStep : entity.minTemp;
|
||||
_setTargetTemp(entity);
|
||||
}
|
||||
|
||||
void _targetHighUp(ClimateEntity entity) {
|
||||
_tmpTargetHigh = ((_tmpTargetHigh + entity.temperatureStep) <= entity.maxTemp) ? _tmpTargetHigh + entity.temperatureStep : entity.maxTemp;
|
||||
_setTargetTemp(entity);
|
||||
}
|
||||
|
||||
void _targetHighDown(ClimateEntity entity) {
|
||||
_tmpTargetHigh = ((_tmpTargetHigh - entity.temperatureStep) >= entity.minTemp) ? _tmpTargetHigh - entity.temperatureStep : entity.minTemp;
|
||||
_setTargetTemp(entity);
|
||||
}
|
||||
|
||||
void _setTemperature(ClimateEntity entity) {
|
||||
if (_tempThrottleTimer!=null) {
|
||||
_tempThrottleTimer.cancel();
|
||||
}
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_tmpTemperature = double.parse(_tmpTemperature.toStringAsFixed(1));
|
||||
});
|
||||
_tempThrottleTimer = Timer(Duration(seconds: 2), () {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"temperature": "${_tmpTemperature.toStringAsFixed(1)}"}));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _setTargetTemp(ClimateEntity entity) {
|
||||
if (_targetTempThrottleTimer!=null) {
|
||||
_targetTempThrottleTimer.cancel();
|
||||
}
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_tmpTargetLow = double.parse(_tmpTargetLow.toStringAsFixed(1));
|
||||
_tmpTargetHigh = double.parse(_tmpTargetHigh.toStringAsFixed(1));
|
||||
});
|
||||
_targetTempThrottleTimer = Timer(Duration(seconds: 2), () {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"target_temp_high": "${_tmpTargetHigh.toStringAsFixed(1)}", "target_temp_low": "${_tmpTargetLow.toStringAsFixed(1)}"}));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _setTargetHumidity(ClimateEntity entity, double value) {
|
||||
setState(() {
|
||||
_tmpTargetHumidity = value.roundToDouble();
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_humidity", entity.entityId,{"humidity": "$_tmpTargetHumidity"}));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
}
|
||||
|
||||
void _setHVACMode(ClimateEntity entity, value) {
|
||||
setState(() {
|
||||
_tmpHVACMode = value;
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_hvac_mode", entity.entityId,{"hvac_mode": "$_tmpHVACMode"}));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
}
|
||||
|
||||
void _setSwingMode(ClimateEntity entity, value) {
|
||||
setState(() {
|
||||
_tmpSwingMode = value;
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_swing_mode", entity.entityId,{"swing_mode": "$_tmpSwingMode"}));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
}
|
||||
|
||||
void _setFanMode(ClimateEntity entity, value) {
|
||||
setState(() {
|
||||
_tmpFanMode = value;
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_fan_mode", entity.entityId,{"fan_mode": "$_tmpFanMode"}));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
}
|
||||
|
||||
void _setPresetMode(ClimateEntity entity, value) {
|
||||
setState(() {
|
||||
_tmpPresetMode = value;
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_preset_mode", entity.entityId,{"preset_mode": "$_tmpPresetMode"}));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
}
|
||||
|
||||
/*void _setOnOf(ClimateEntity entity, value) {
|
||||
setState(() {
|
||||
_tmpIsOff = !value;
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "${_tmpIsOff ? 'turn_off' : 'turn_on'}", entity.entityId, null));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
}*/
|
||||
|
||||
void _setAuxHeat(ClimateEntity entity, value) {
|
||||
setState(() {
|
||||
_tmpAuxHeat = value;
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_aux_heat", entity.entityId, {"aux_heat": "$_tmpAuxHeat"}));
|
||||
_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.entityWrapper.entity;
|
||||
if (_changedHere) {
|
||||
_showPending = (_tmpTemperature != entity.temperature || _tmpTargetHigh != entity.targetHigh || _tmpTargetLow != entity.targetLow);
|
||||
_changedHere = false;
|
||||
} else {
|
||||
_resetTimer?.cancel();
|
||||
_resetVars(entity);
|
||||
}
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
//_buildOnOffControl(entity),
|
||||
_buildTemperatureControls(entity),
|
||||
_buildTargetTemperatureControls(entity),
|
||||
_buildHumidityControls(entity),
|
||||
_buildOperationControl(entity),
|
||||
_buildFanControl(entity),
|
||||
_buildSwingControl(entity),
|
||||
_buildPresetModeControl(entity),
|
||||
_buildAuxHeatControl(entity)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPresetModeControl(ClimateEntity entity) {
|
||||
if (entity.supportPresetMode) {
|
||||
return ModeSelectorWidget(
|
||||
options: entity.presetModes,
|
||||
onChange: (mode) => _setPresetMode(entity, mode),
|
||||
caption: "Preset",
|
||||
value: _tmpPresetMode,
|
||||
);
|
||||
} else {
|
||||
return Container(height: 0.0, width: 0.0,);
|
||||
}
|
||||
}
|
||||
|
||||
/*Widget _buildOnOffControl(ClimateEntity entity) {
|
||||
if (entity.supportOnOff) {
|
||||
return ModeSwitchWidget(
|
||||
onChange: (value) => _setOnOf(entity, value),
|
||||
caption: "On / Off",
|
||||
value: !_tmpIsOff
|
||||
);
|
||||
} else {
|
||||
return Container(height: 0.0, width: 0.0,);
|
||||
}
|
||||
}*/
|
||||
|
||||
Widget _buildAuxHeatControl(ClimateEntity entity) {
|
||||
if (entity.supportAuxHeat ) {
|
||||
return ModeSwitchWidget(
|
||||
caption: "Aux heat",
|
||||
onChange: (value) => _setAuxHeat(entity, value),
|
||||
value: _tmpAuxHeat
|
||||
);
|
||||
} else {
|
||||
return Container(height: 0.0, width: 0.0,);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildOperationControl(ClimateEntity entity) {
|
||||
if (entity.hvacModes != null) {
|
||||
return ModeSelectorWidget(
|
||||
onChange: (mode) => _setHVACMode(entity, mode),
|
||||
options: entity.hvacModes,
|
||||
caption: "Operation",
|
||||
value: _tmpHVACMode,
|
||||
);
|
||||
} else {
|
||||
return Container(height: 0.0, width: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildFanControl(ClimateEntity entity) {
|
||||
if (entity.supportFanMode) {
|
||||
return ModeSelectorWidget(
|
||||
options: entity.fanModes,
|
||||
onChange: (mode) => _setFanMode(entity, mode),
|
||||
caption: "Fan mode",
|
||||
value: _tmpFanMode,
|
||||
);
|
||||
} else {
|
||||
return Container(height: 0.0, width: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildSwingControl(ClimateEntity entity) {
|
||||
if (entity.supportSwingMode) {
|
||||
return ModeSelectorWidget(
|
||||
onChange: (mode) => _setSwingMode(entity, mode),
|
||||
options: entity.swingModes,
|
||||
value: _tmpSwingMode,
|
||||
caption: "Swing mode"
|
||||
);
|
||||
} else {
|
||||
return Container(height: 0.0, width: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTemperatureControls(ClimateEntity entity) {
|
||||
if ((entity.supportTargetTemperature) && (entity.temperature != null)) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("Target temperature", style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
TemperatureControlWidget(
|
||||
value: _tmpTemperature,
|
||||
fontColor: _showPending ? Colors.red : Colors.black,
|
||||
onDec: () => _temperatureDown(entity),
|
||||
onInc: () => _temperatureUp(entity),
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0,);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTargetTemperatureControls(ClimateEntity entity) {
|
||||
List<Widget> controls = [];
|
||||
if ((entity.supportTargetTemperatureRange) && (entity.targetLow != null)) {
|
||||
controls.addAll(<Widget>[
|
||||
TemperatureControlWidget(
|
||||
value: _tmpTargetLow,
|
||||
fontColor: _showPending ? Colors.red : Colors.black,
|
||||
onDec: () => _targetLowDown(entity),
|
||||
onInc: () => _targetLowUp(entity),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(height: 10.0),
|
||||
)
|
||||
]);
|
||||
}
|
||||
if ((entity.supportTargetTemperatureRange) && (entity.targetHigh != null)) {
|
||||
controls.add(
|
||||
TemperatureControlWidget(
|
||||
value: _tmpTargetHigh,
|
||||
fontColor: _showPending ? Colors.red : Colors.black,
|
||||
onDec: () => _targetHighDown(entity),
|
||||
onInc: () => _targetHighUp(entity),
|
||||
)
|
||||
);
|
||||
}
|
||||
if (controls.isNotEmpty) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("Target temperature range", style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
Row(
|
||||
children: controls,
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildHumidityControls(ClimateEntity entity) {
|
||||
List<Widget> result = [];
|
||||
if (entity.supportTargetHumidity) {
|
||||
result.addAll(<Widget>[
|
||||
Text(
|
||||
"$_tmpTargetHumidity%",
|
||||
style: TextStyle(fontSize: Sizes.largeFontSize),
|
||||
),
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: _tmpTargetHumidity,
|
||||
max: entity.maxHumidity,
|
||||
min: entity.minHumidity,
|
||||
onChanged: ((double val) {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_tmpTargetHumidity = val.roundToDouble();
|
||||
});
|
||||
}),
|
||||
onChangeEnd: (double v) => _setTargetHumidity(entity, v),
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
if (result.isNotEmpty) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||
child: Text("Target humidity", style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: result,
|
||||
),
|
||||
Container(
|
||||
height: Sizes.rowPadding,
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Container(
|
||||
width: 0.0,
|
||||
height: 0.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_resetTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TemperatureControlWidget extends StatelessWidget {
|
||||
final double value;
|
||||
final double fontSize;
|
||||
final Color fontColor;
|
||||
final onInc;
|
||||
final onDec;
|
||||
|
||||
TemperatureControlWidget(
|
||||
{Key key,
|
||||
@required this.value,
|
||||
@required this.onInc,
|
||||
@required this.onDec,
|
||||
this.fontSize,
|
||||
this.fontColor})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"$value",
|
||||
style: TextStyle(
|
||||
fontSize: fontSize ?? 24.0,
|
||||
color: fontColor ?? Colors.black
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||
'mdi:chevron-up')),
|
||||
iconSize: 30.0,
|
||||
onPressed: () => onInc(),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||
'mdi:chevron-down')),
|
||||
iconSize: 30.0,
|
||||
onPressed: () => onDec(),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
58
lib/entities/climate/widgets/climate_state.widget.dart
Normal file
58
lib/entities/climate/widgets/climate_state.widget.dart
Normal file
@ -0,0 +1,58 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class ClimateStateWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final ClimateEntity entity = entityModel.entityWrapper.entity;
|
||||
String targetTemp = "-";
|
||||
if ((entity.supportTargetTemperature) && (entity.temperature != null)) {
|
||||
targetTemp = "${entity.temperature}";
|
||||
} else if ((entity.supportTargetTemperatureRange) &&
|
||||
(entity.targetLow != null) &&
|
||||
(entity.targetHigh != null)) {
|
||||
targetTemp = "${entity.targetLow} - ${entity.targetHigh}";
|
||||
}
|
||||
String displayState = '';
|
||||
if (entity.hvacAction != null) {
|
||||
displayState = "${entity.hvacAction} (${entity.displayState})";
|
||||
} else {
|
||||
displayState = "${entity.displayState}";
|
||||
}
|
||||
if (entity.presetMode != null) {
|
||||
displayState += " - ${entity.presetMode}";
|
||||
}
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, 0.0, Sizes.rightWidgetPadding, 0.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Text("$displayState",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: Sizes.stateFontSize,
|
||||
)),
|
||||
Text(" $targetTemp",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontSize: Sizes.stateFontSize,
|
||||
))
|
||||
],
|
||||
),
|
||||
entity.currentTemperature != null ?
|
||||
Text("Currently: ${entity.currentTemperature}",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontSize: Sizes.stateFontSize,
|
||||
color: Colors.black45)
|
||||
) :
|
||||
Container(height: 0.0,)
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
60
lib/entities/cover/cover_entity.class.dart
Normal file
60
lib/entities/cover/cover_entity.class.dart
Normal file
@ -0,0 +1,60 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class CoverEntity extends Entity {
|
||||
|
||||
static const SUPPORT_OPEN = 1;
|
||||
static const SUPPORT_CLOSE = 2;
|
||||
static const SUPPORT_SET_POSITION = 4;
|
||||
static const SUPPORT_STOP = 8;
|
||||
static const SUPPORT_OPEN_TILT = 16;
|
||||
static const SUPPORT_CLOSE_TILT = 32;
|
||||
static const SUPPORT_STOP_TILT = 64;
|
||||
static const SUPPORT_SET_TILT_POSITION = 128;
|
||||
|
||||
CoverEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
bool get supportOpen => ((supportedFeatures &
|
||||
CoverEntity.SUPPORT_OPEN) ==
|
||||
CoverEntity.SUPPORT_OPEN);
|
||||
bool get supportClose => ((supportedFeatures &
|
||||
CoverEntity.SUPPORT_CLOSE) ==
|
||||
CoverEntity.SUPPORT_CLOSE);
|
||||
bool get supportSetPosition => ((supportedFeatures &
|
||||
CoverEntity.SUPPORT_SET_POSITION) ==
|
||||
CoverEntity.SUPPORT_SET_POSITION);
|
||||
bool get supportStop => ((supportedFeatures &
|
||||
CoverEntity.SUPPORT_STOP) ==
|
||||
CoverEntity.SUPPORT_STOP);
|
||||
|
||||
bool get supportOpenTilt => ((supportedFeatures &
|
||||
CoverEntity.SUPPORT_OPEN_TILT) ==
|
||||
CoverEntity.SUPPORT_OPEN_TILT);
|
||||
bool get supportCloseTilt => ((supportedFeatures &
|
||||
CoverEntity.SUPPORT_CLOSE_TILT) ==
|
||||
CoverEntity.SUPPORT_CLOSE_TILT);
|
||||
bool get supportStopTilt => ((supportedFeatures &
|
||||
CoverEntity.SUPPORT_STOP_TILT) ==
|
||||
CoverEntity.SUPPORT_STOP_TILT);
|
||||
bool get supportSetTiltPosition => ((supportedFeatures &
|
||||
CoverEntity.SUPPORT_SET_TILT_POSITION) ==
|
||||
CoverEntity.SUPPORT_SET_TILT_POSITION);
|
||||
|
||||
|
||||
double get currentPosition => _getDoubleAttributeValue('current_position');
|
||||
double get currentTiltPosition => _getDoubleAttributeValue('current_tilt_position');
|
||||
bool get canBeOpened => ((state != EntityState.opening) && (state != EntityState.open)) || (state == EntityState.open && currentPosition != null && currentPosition > 0.0 && currentPosition < 100.0);
|
||||
bool get canBeClosed => ((state != EntityState.closing) && (state != EntityState.closed));
|
||||
bool get canTiltBeOpened => currentTiltPosition < 100;
|
||||
bool get canTiltBeClosed => currentTiltPosition > 0;
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return CoverStateWidget();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
return CoverControlWidget();
|
||||
}
|
||||
|
||||
}
|
200
lib/entities/cover/widgets/cover_controls.widget.dart
Normal file
200
lib/entities/cover/widgets/cover_controls.widget.dart
Normal file
@ -0,0 +1,200 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class CoverControlWidget extends StatefulWidget {
|
||||
|
||||
CoverControlWidget({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_CoverControlWidgetState createState() => _CoverControlWidgetState();
|
||||
}
|
||||
|
||||
class _CoverControlWidgetState extends State<CoverControlWidget> {
|
||||
|
||||
double _tmpPosition = 0.0;
|
||||
double _tmpTiltPosition = 0.0;
|
||||
bool _changedHere = false;
|
||||
|
||||
void _setNewPosition(CoverEntity entity, double position) {
|
||||
setState(() {
|
||||
_tmpPosition = position.roundToDouble();
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_cover_position", entity.entityId,{"position": _tmpPosition.round()}));
|
||||
});
|
||||
}
|
||||
|
||||
void _setNewTiltPosition(CoverEntity entity, double position) {
|
||||
setState(() {
|
||||
_tmpTiltPosition = position.roundToDouble();
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_cover_tilt_position", entity.entityId,{"tilt_position": _tmpTiltPosition.round()}));
|
||||
});
|
||||
}
|
||||
|
||||
void _resetVars(CoverEntity entity) {
|
||||
_tmpPosition = entity.currentPosition;
|
||||
_tmpTiltPosition = entity.currentTiltPosition;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final CoverEntity entity = entityModel.entityWrapper.entity;
|
||||
if (_changedHere) {
|
||||
_changedHere = false;
|
||||
} else {
|
||||
_resetVars(entity);
|
||||
}
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
_buildPositionControls(entity),
|
||||
_buildTiltControls(entity)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPositionControls(CoverEntity entity) {
|
||||
if (entity.supportSetPosition) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||
child: Text("Position", style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
),
|
||||
Slider(
|
||||
value: _tmpPosition,
|
||||
min: 0.0,
|
||||
max: 100.0,
|
||||
divisions: 10,
|
||||
onChanged: (double value) {
|
||||
setState(() {
|
||||
_tmpPosition = value.roundToDouble();
|
||||
_changedHere = true;
|
||||
});
|
||||
},
|
||||
onChangeEnd: (double value) => _setNewPosition(entity, value),
|
||||
),
|
||||
Container(height: Sizes.rowPadding,)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTiltControls(CoverEntity entity) {
|
||||
List<Widget> controls = [];
|
||||
if (entity.supportCloseTilt || entity.supportOpenTilt || entity.supportStopTilt) {
|
||||
controls.add(
|
||||
CoverTiltControlsWidget()
|
||||
);
|
||||
}
|
||||
if (entity.supportSetTiltPosition) {
|
||||
controls.addAll(<Widget>[
|
||||
Slider(
|
||||
value: _tmpTiltPosition,
|
||||
min: 0.0,
|
||||
max: 100.0,
|
||||
divisions: 10,
|
||||
onChanged: (double value) {
|
||||
setState(() {
|
||||
_tmpTiltPosition = value.roundToDouble();
|
||||
_changedHere = true;
|
||||
});
|
||||
},
|
||||
onChangeEnd: (double value) => _setNewTiltPosition(entity, value),
|
||||
),
|
||||
Container(height: Sizes.rowPadding,)
|
||||
]);
|
||||
}
|
||||
if (controls.isNotEmpty) {
|
||||
controls.insert(0, Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||
child: Text("Tilt position", style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
));
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: controls,
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CoverTiltControlsWidget extends StatelessWidget {
|
||||
void _open(CoverEntity entity) {
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "open_cover_tilt", entity.entityId, null));
|
||||
}
|
||||
|
||||
void _close(CoverEntity entity) {
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "close_cover_tilt", entity.entityId, null));
|
||||
}
|
||||
|
||||
void _stop(CoverEntity entity) {
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "stop_cover_tilt", entity.entityId, null));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final CoverEntity entity = entityModel.entityWrapper.entity;
|
||||
List<Widget> buttons = [];
|
||||
if (entity.supportOpenTilt) {
|
||||
buttons.add(IconButton(
|
||||
icon: Icon(
|
||||
MaterialDesignIcons.getIconDataFromIconName(
|
||||
"mdi:arrow-top-right"),
|
||||
size: Sizes.iconSize,
|
||||
),
|
||||
onPressed: entity.canTiltBeOpened ? () => _open(entity) : null));
|
||||
} else {
|
||||
buttons.add(Container(
|
||||
width: Sizes.iconSize + 20.0,
|
||||
));
|
||||
}
|
||||
if (entity.supportStopTilt) {
|
||||
buttons.add(IconButton(
|
||||
icon: Icon(
|
||||
MaterialDesignIcons.getIconDataFromIconName("mdi:stop"),
|
||||
size: Sizes.iconSize,
|
||||
),
|
||||
onPressed: () => _stop(entity)));
|
||||
} else {
|
||||
buttons.add(Container(
|
||||
width: Sizes.iconSize + 20.0,
|
||||
));
|
||||
}
|
||||
if (entity.supportCloseTilt) {
|
||||
buttons.add(IconButton(
|
||||
icon: Icon(
|
||||
MaterialDesignIcons.getIconDataFromIconName(
|
||||
"mdi:arrow-bottom-left"),
|
||||
size: Sizes.iconSize,
|
||||
),
|
||||
onPressed: entity.canTiltBeClosed ? () => _close(entity) : null));
|
||||
} else {
|
||||
buttons.add(Container(
|
||||
width: Sizes.iconSize + 20.0,
|
||||
));
|
||||
}
|
||||
|
||||
return Row(
|
||||
children: buttons,
|
||||
);
|
||||
}
|
||||
}
|
65
lib/entities/cover/widgets/cover_state.dart
Normal file
65
lib/entities/cover/widgets/cover_state.dart
Normal file
@ -0,0 +1,65 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class CoverStateWidget extends StatelessWidget {
|
||||
void _open(CoverEntity entity) {
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "open_cover", entity.entityId, null));
|
||||
}
|
||||
|
||||
void _close(CoverEntity entity) {
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "close_cover", entity.entityId, null));
|
||||
}
|
||||
|
||||
void _stop(CoverEntity entity) {
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "stop_cover", entity.entityId, null));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final CoverEntity entity = entityModel.entityWrapper.entity;
|
||||
List<Widget> buttons = [];
|
||||
if (entity.supportOpen) {
|
||||
buttons.add(IconButton(
|
||||
icon: Icon(
|
||||
MaterialDesignIcons.getIconDataFromIconName("mdi:arrow-up"),
|
||||
size: Sizes.iconSize,
|
||||
),
|
||||
onPressed: entity.canBeOpened ? () => _open(entity) : null));
|
||||
} else {
|
||||
buttons.add(Container(
|
||||
width: Sizes.iconSize + 20.0,
|
||||
));
|
||||
}
|
||||
if (entity.supportStop) {
|
||||
buttons.add(IconButton(
|
||||
icon: Icon(
|
||||
MaterialDesignIcons.getIconDataFromIconName("mdi:stop"),
|
||||
size: Sizes.iconSize,
|
||||
),
|
||||
onPressed: () => _stop(entity)));
|
||||
} else {
|
||||
buttons.add(Container(
|
||||
width: Sizes.iconSize + 20.0,
|
||||
));
|
||||
}
|
||||
if (entity.supportClose) {
|
||||
buttons.add(IconButton(
|
||||
icon: Icon(
|
||||
MaterialDesignIcons.getIconDataFromIconName("mdi:arrow-down"),
|
||||
size: Sizes.iconSize,
|
||||
),
|
||||
onPressed: entity.canBeClosed ? () => _close(entity) : null));
|
||||
} else {
|
||||
buttons.add(Container(
|
||||
width: Sizes.iconSize + 20.0,
|
||||
));
|
||||
}
|
||||
|
||||
return Row(
|
||||
children: buttons,
|
||||
);
|
||||
}
|
||||
}
|
42
lib/entities/date_time/date_time_entity.class.dart
Normal file
42
lib/entities/date_time/date_time_entity.class.dart
Normal file
@ -0,0 +1,42 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class DateTimeEntity extends Entity {
|
||||
DateTimeEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
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();
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
75
lib/entities/date_time/widgets/date_time_state.dart
Normal file
75
lib/entities/date_time/widgets/date_time_state.dart
Normal file
@ -0,0 +1,75 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class DateTimeStateWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final DateTimeEntity entity = entityModel.entityWrapper.entity;
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0),
|
||||
child: GestureDetector(
|
||||
child: Text("${entity.formattedState}",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontSize: Sizes.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 {
|
||||
Logger.w( "${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));
|
||||
}
|
||||
}
|
282
lib/entities/entity.class.dart
Normal file
282
lib/entities/entity.class.dart
Normal file
@ -0,0 +1,282 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class StatelessEntityType {
|
||||
static const NONE = 0;
|
||||
static const MISSED = 1;
|
||||
static const DIVIDER = 2;
|
||||
static const SECTION = 3;
|
||||
static const CALL_SERVICE = 4;
|
||||
static const WEBLINK = 5;
|
||||
}
|
||||
|
||||
class Entity {
|
||||
|
||||
static List badgeDomains = [
|
||||
"alarm_control_panel",
|
||||
"binary_sensor",
|
||||
"device_tracker",
|
||||
"updater",
|
||||
"sun",
|
||||
"timer",
|
||||
"sensor"
|
||||
];
|
||||
|
||||
static Map StateByDeviceClass = {
|
||||
"battery.on": "Low",
|
||||
"battery.off": "Normal",
|
||||
"cold.on": "Cold",
|
||||
"cold.off": "Normal",
|
||||
"connectivity.on": "Connected",
|
||||
"connectivity.off": "Disconnected",
|
||||
"door.on": "Open",
|
||||
"door.off": "Closed",
|
||||
"garage_door.on": "Open",
|
||||
"garage_door.off": "Closed",
|
||||
"gas.on": "Detected",
|
||||
"gas.off": "Clear",
|
||||
"heat.on": "Hot",
|
||||
"heat.off": "Normal",
|
||||
"light.on": "Detected",
|
||||
"lignt.off": "No light",
|
||||
"lock.on": "Unlocked",
|
||||
"lock.off": "Locked",
|
||||
"moisture.on": "Wet",
|
||||
"moisture.off": "Dry",
|
||||
"motion.on": "Detected",
|
||||
"motion.off": "Clear",
|
||||
"moving.on": "Moving",
|
||||
"moving.off": "Stopped",
|
||||
"occupancy.on": "Occupied",
|
||||
"occupancy.off": "Clear",
|
||||
"opening.on": "Open",
|
||||
"opening.off": "Closed",
|
||||
"plug.on": "Plugged in",
|
||||
"plug.off": "Unplugged",
|
||||
"power.on": "Powered",
|
||||
"power.off": "No power",
|
||||
"presence.on": "Home",
|
||||
"presence.off": "Away",
|
||||
"problem.on": "Problem",
|
||||
"problem.off": "OK",
|
||||
"safety.on": "Unsafe",
|
||||
"safety.off": "Safe",
|
||||
"smoke.on": "Detected",
|
||||
"smoke.off": "Clear",
|
||||
"sound.on": "Detected",
|
||||
"sound.off": "Clear",
|
||||
"vibration.on": "Detected",
|
||||
"vibration.off": "Clear",
|
||||
"window.on": "Open",
|
||||
"window.off": "Closed"
|
||||
};
|
||||
|
||||
Map attributes;
|
||||
String domain;
|
||||
String entityId;
|
||||
String entityPicture;
|
||||
String state;
|
||||
String displayState;
|
||||
DateTime _lastUpdated;
|
||||
int statelessType = 0;
|
||||
|
||||
List<Entity> childEntities = [];
|
||||
String deviceClass;
|
||||
EntityHistoryConfig historyConfig = EntityHistoryConfig(
|
||||
chartType: EntityHistoryWidgetType.simple
|
||||
);
|
||||
|
||||
String get displayName =>
|
||||
attributes["friendly_name"] ?? (attributes["name"] ?? entityId.split(".")[1].replaceAll("_", " "));
|
||||
|
||||
bool get isView =>
|
||||
(domain == "group") &&
|
||||
(attributes != null ? attributes["view"] ?? false : false);
|
||||
bool get isGroup => domain == "group";
|
||||
bool get isBadge => Entity.badgeDomains.contains(domain);
|
||||
String get icon => attributes["icon"] ?? "";
|
||||
bool get isOn => state == EntityState.on;
|
||||
String get unitOfMeasurement => attributes["unit_of_measurement"] ?? "";
|
||||
List get childEntityIds => attributes["entity_id"] ?? [];
|
||||
String get lastUpdated => _getLastUpdatedFormatted();
|
||||
bool get isHidden => attributes["hidden"] ?? false;
|
||||
double get doubleState => double.tryParse(state) ?? 0.0;
|
||||
int get supportedFeatures => attributes["supported_features"] ?? 0;
|
||||
|
||||
String _getEntityPictureUrl(String webHost) {
|
||||
String result = attributes["entity_picture"];
|
||||
if (result == null) return result;
|
||||
if (!result.startsWith("http")) {
|
||||
if (result.startsWith("/")) {
|
||||
result = "$webHost$result";
|
||||
} else {
|
||||
result = "$webHost/$result";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Entity(Map rawData, String webHost) {
|
||||
update(rawData, webHost);
|
||||
}
|
||||
|
||||
Entity.missed(String entityId) {
|
||||
statelessType = StatelessEntityType.MISSED;
|
||||
attributes = {"hidden": false};
|
||||
this.entityId = entityId;
|
||||
}
|
||||
|
||||
Entity.divider() {
|
||||
statelessType = StatelessEntityType.DIVIDER;
|
||||
attributes = {"hidden": false};
|
||||
}
|
||||
|
||||
Entity.section(String label) {
|
||||
statelessType = StatelessEntityType.SECTION;
|
||||
attributes = {"hidden": false, "friendly_name": "$label"};
|
||||
}
|
||||
|
||||
Entity.callService({String icon, String name, String service, String actionName}) {
|
||||
statelessType = StatelessEntityType.CALL_SERVICE;
|
||||
entityId = service;
|
||||
displayState = actionName?.toUpperCase() ?? "RUN";
|
||||
attributes = {"hidden": false, "friendly_name": "$name", "icon": "$icon"};
|
||||
}
|
||||
|
||||
Entity.weblink({String url, String name, String icon}) {
|
||||
statelessType = StatelessEntityType.WEBLINK;
|
||||
entityId = "custom.custom"; //TODO wtf??
|
||||
attributes = {"hidden": false, "friendly_name": "${name ?? url}", "icon": "${icon ?? 'mdi:link'}"};
|
||||
}
|
||||
|
||||
void update(Map rawData, String webHost) {
|
||||
attributes = rawData["attributes"] ?? {};
|
||||
domain = rawData["entity_id"].split(".")[0];
|
||||
entityId = rawData["entity_id"];
|
||||
deviceClass = attributes["device_class"];
|
||||
state = rawData["state"];
|
||||
displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? (state.toLowerCase() == 'unknown' ? '-' : state);
|
||||
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
|
||||
entityPicture = _getEntityPictureUrl(webHost);
|
||||
}
|
||||
|
||||
double _getDoubleAttributeValue(String attributeName) {
|
||||
var temp1 = attributes["$attributeName"];
|
||||
if (temp1 is int) {
|
||||
return temp1.toDouble();
|
||||
} else if (temp1 is double) {
|
||||
return temp1;
|
||||
} else {
|
||||
return double.tryParse("$temp1");
|
||||
}
|
||||
}
|
||||
|
||||
int _getIntAttributeValue(String attributeName) {
|
||||
var temp1 = attributes["$attributeName"];
|
||||
if (temp1 is int) {
|
||||
return temp1;
|
||||
} else if (temp1 is double) {
|
||||
return temp1.round();
|
||||
} else {
|
||||
return int.tryParse("$temp1");
|
||||
}
|
||||
}
|
||||
|
||||
List<String> getStringListAttributeValue(String attribute) {
|
||||
if (attributes["$attribute"] != null) {
|
||||
List<String> result = (attributes["$attribute"] as List).cast<String>();
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildDefaultWidget(BuildContext context) {
|
||||
return DefaultEntityContainer(
|
||||
state: _buildStatePart(context)
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
entityWrapper: EntityWrapper(entity: this),
|
||||
child: EntityPageContainer(children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: Sizes.rowPadding, left: Sizes.leftWidgetPadding),
|
||||
child: DefaultEntityContainer(state: _buildStatePartForPage(context)),
|
||||
),
|
||||
LastUpdatedWidget(),
|
||||
Divider(),
|
||||
_buildAdditionalControlsForPage(context),
|
||||
Divider(),
|
||||
buildHistoryWidget(),
|
||||
EntityAttributesList()
|
||||
]),
|
||||
handleTap: false,
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildHistoryWidget() {
|
||||
return EntityHistoryWidget(
|
||||
config: historyConfig,
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildBadgeWidget(BuildContext context) {
|
||||
return EntityModel(
|
||||
entityWrapper: EntityWrapper(entity: this),
|
||||
child: BadgeWidget(),
|
||||
handleTap: true,
|
||||
);
|
||||
}
|
||||
|
||||
String getAttribute(String attributeName) {
|
||||
if (attributes != null) {
|
||||
return attributes["$attributeName"];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
114
lib/entities/entity_wrapper.class.dart
Normal file
114
lib/entities/entity_wrapper.class.dart
Normal file
@ -0,0 +1,114 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class EntityWrapper {
|
||||
|
||||
String displayName;
|
||||
String icon;
|
||||
String entityPicture;
|
||||
EntityUIAction uiAction;
|
||||
Entity entity;
|
||||
|
||||
|
||||
EntityWrapper({
|
||||
this.entity,
|
||||
String icon,
|
||||
String displayName,
|
||||
this.uiAction
|
||||
}) {
|
||||
if (entity.statelessType == StatelessEntityType.NONE || entity.statelessType == StatelessEntityType.CALL_SERVICE || entity.statelessType == StatelessEntityType.WEBLINK) {
|
||||
this.icon = icon ?? entity.icon;
|
||||
if (icon == null) {
|
||||
entityPicture = entity.entityPicture;
|
||||
}
|
||||
this.displayName = displayName ?? entity.displayName;
|
||||
if (uiAction == null) {
|
||||
uiAction = EntityUIAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleTap() {
|
||||
switch (uiAction.tapAction) {
|
||||
case EntityUIAction.toggle: {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityUIAction.callService: {
|
||||
if (uiAction.tapService != null) {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent(uiAction.tapService.split(".")[0],
|
||||
uiAction.tapService.split(".")[1], null,
|
||||
uiAction.tapServiceData));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityUIAction.none: {
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityUIAction.moreInfo: {
|
||||
eventBus.fire(
|
||||
new ShowEntityPageEvent(entity));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityUIAction.navigate: {
|
||||
if (uiAction.tapService.startsWith("/")) {
|
||||
//TODO handle local urls
|
||||
Logger.w("Local urls is not supported yet");
|
||||
} else {
|
||||
HAUtils.launchURL(uiAction.tapService);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleHold() {
|
||||
switch (uiAction.holdAction) {
|
||||
case EntityUIAction.toggle: {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityUIAction.callService: {
|
||||
if (uiAction.holdService != null) {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent(uiAction.holdService.split(".")[0],
|
||||
uiAction.holdService.split(".")[1], null,
|
||||
uiAction.holdServiceData));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityUIAction.moreInfo: {
|
||||
eventBus.fire(
|
||||
new ShowEntityPageEvent(entity));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityUIAction.navigate: {
|
||||
if (uiAction.holdService.startsWith("/")) {
|
||||
//TODO handle local urls
|
||||
Logger.w("Local urls is not supported yet");
|
||||
} else {
|
||||
HAUtils.launchURL(uiAction.holdService);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
32
lib/entities/fan/fan_entity.class.dart
Normal file
32
lib/entities/fan/fan_entity.class.dart
Normal file
@ -0,0 +1,32 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class FanEntity extends Entity {
|
||||
|
||||
static const SUPPORT_SET_SPEED = 1;
|
||||
static const SUPPORT_OSCILLATE = 2;
|
||||
static const SUPPORT_DIRECTION = 4;
|
||||
|
||||
FanEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
bool get supportSetSpeed => ((supportedFeatures &
|
||||
FanEntity.SUPPORT_SET_SPEED) ==
|
||||
FanEntity.SUPPORT_SET_SPEED);
|
||||
bool get supportOscillate => ((supportedFeatures &
|
||||
FanEntity.SUPPORT_OSCILLATE) ==
|
||||
FanEntity.SUPPORT_OSCILLATE);
|
||||
bool get supportDirection => ((supportedFeatures &
|
||||
FanEntity.SUPPORT_DIRECTION) ==
|
||||
FanEntity.SUPPORT_DIRECTION);
|
||||
|
||||
List<String> get speedList => getStringListAttributeValue("speed_list");
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return SwitchStateWidget();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
return FanControlsWidget();
|
||||
}
|
||||
}
|
123
lib/entities/fan/widgets/fan_controls.dart
Normal file
123
lib/entities/fan/widgets/fan_controls.dart
Normal file
@ -0,0 +1,123 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class FanControlsWidget extends StatefulWidget {
|
||||
|
||||
@override
|
||||
_FanControlsWidgetState createState() => _FanControlsWidgetState();
|
||||
|
||||
}
|
||||
|
||||
class _FanControlsWidgetState extends State<FanControlsWidget> {
|
||||
|
||||
bool _tmpOscillate;
|
||||
bool _tmpDirectionForward;
|
||||
bool _changedHere = false;
|
||||
String _tmpSpeed;
|
||||
|
||||
void _resetState(FanEntity entity) {
|
||||
_tmpOscillate = entity.attributes["oscillating"] ?? false;
|
||||
_tmpDirectionForward = entity.attributes["direction"] == "forward";
|
||||
_tmpSpeed = entity.attributes["speed"];
|
||||
}
|
||||
|
||||
void _setOscillate(FanEntity entity, bool oscillate) {
|
||||
setState(() {
|
||||
_tmpOscillate = oscillate;
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
"fan", "oscillate", entity.entityId,
|
||||
{"oscillating": oscillate}));
|
||||
});
|
||||
}
|
||||
|
||||
void _setDirection(FanEntity entity, bool forward) {
|
||||
setState(() {
|
||||
_tmpDirectionForward = forward;
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
"fan", "set_direction", entity.entityId,
|
||||
{"direction": forward ? "forward" : "reverse"}));
|
||||
});
|
||||
}
|
||||
|
||||
void _setSpeed(FanEntity entity, String value) {
|
||||
setState(() {
|
||||
_tmpSpeed = value;
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
"fan", "set_speed", entity.entityId,
|
||||
{"speed": value}));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final FanEntity entity = entityModel.entityWrapper.entity;
|
||||
if (!_changedHere) {
|
||||
_resetState(entity);
|
||||
} else {
|
||||
_changedHere = false;
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
_buildSpeedControl(entity),
|
||||
_buildOscillateControl(entity),
|
||||
_buildDirectionControl(entity)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSpeedControl(FanEntity entity) {
|
||||
if (entity.supportSetSpeed && entity.speedList != null && entity.speedList.isNotEmpty) {
|
||||
return ModeSelectorWidget(
|
||||
onChange: (effect) => _setSpeed(entity, effect),
|
||||
caption: "Speed",
|
||||
options: entity.speedList,
|
||||
value: _tmpSpeed
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildOscillateControl(FanEntity entity) {
|
||||
if (entity.supportOscillate) {
|
||||
return ModeSwitchWidget(
|
||||
onChange: (value) => _setOscillate(entity, value),
|
||||
caption: "Oscillate",
|
||||
value: _tmpOscillate,
|
||||
expanded: false,
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildDirectionControl(FanEntity entity) {
|
||||
if (entity.supportDirection) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
onPressed: _tmpDirectionForward ?
|
||||
() => _setDirection(entity, false) :
|
||||
null,
|
||||
icon: Icon(Icons.rotate_left),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: !_tmpDirectionForward ?
|
||||
() => _setDirection(entity, true) :
|
||||
null,
|
||||
icon: Icon(Icons.rotate_right),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
44
lib/entities/group/group_entity.class.dart
Normal file
44
lib/entities/group/group_entity.class.dart
Normal file
@ -0,0 +1,44 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class GroupEntity extends Entity {
|
||||
|
||||
final List<String> _domainsForSwitchableGroup = ["switch", "light", "automation", "input_boolean"];
|
||||
String mutualDomain;
|
||||
bool switchable = false;
|
||||
|
||||
GroupEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
if (switchable) {
|
||||
return SwitchStateWidget(
|
||||
domainForService: "homeassistant",
|
||||
);
|
||||
} else {
|
||||
return super._buildStatePart(context);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void update(Map rawData, String webHost) {
|
||||
super.update(rawData, webHost);
|
||||
if (_isOneDomain()) {
|
||||
mutualDomain = attributes['entity_id'][0].split(".")[0];
|
||||
switchable = _domainsForSwitchableGroup.contains(mutualDomain);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isOneDomain() {
|
||||
bool result = false;
|
||||
if (attributes['entity_id'] != null && attributes['entity_id'] is List && attributes['entity_id'].isNotEmpty) {
|
||||
String firstChildDomain = attributes['entity_id'][0].split(".")[0];
|
||||
result = true;
|
||||
attributes['entity_id'].forEach((childEntityId){
|
||||
if (childEntityId.split(".")[0] != firstChildDomain) {
|
||||
result = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
79
lib/entities/light/light_entity.class.dart
Normal file
79
lib/entities/light/light_entity.class.dart
Normal file
@ -0,0 +1,79 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class LightEntity extends Entity {
|
||||
|
||||
static const SUPPORT_BRIGHTNESS = 1;
|
||||
static const SUPPORT_COLOR_TEMP = 2;
|
||||
static const SUPPORT_EFFECT = 4;
|
||||
static const SUPPORT_FLASH = 8;
|
||||
static const SUPPORT_COLOR = 16;
|
||||
static const SUPPORT_TRANSITION = 32;
|
||||
static const SUPPORT_WHITE_VALUE = 128;
|
||||
|
||||
bool get supportBrightness => ((supportedFeatures &
|
||||
LightEntity.SUPPORT_BRIGHTNESS) ==
|
||||
LightEntity.SUPPORT_BRIGHTNESS);
|
||||
bool get supportColorTemp => ((supportedFeatures &
|
||||
LightEntity.SUPPORT_COLOR_TEMP) ==
|
||||
LightEntity.SUPPORT_COLOR_TEMP);
|
||||
bool get supportEffect => ((supportedFeatures &
|
||||
LightEntity.SUPPORT_EFFECT) ==
|
||||
LightEntity.SUPPORT_EFFECT);
|
||||
bool get supportFlash => ((supportedFeatures &
|
||||
LightEntity.SUPPORT_FLASH) ==
|
||||
LightEntity.SUPPORT_FLASH);
|
||||
bool get supportColor => ((supportedFeatures &
|
||||
LightEntity.SUPPORT_COLOR) ==
|
||||
LightEntity.SUPPORT_COLOR);
|
||||
bool get supportTransition => ((supportedFeatures &
|
||||
LightEntity.SUPPORT_TRANSITION) ==
|
||||
LightEntity.SUPPORT_TRANSITION);
|
||||
bool get supportWhiteValue => ((supportedFeatures &
|
||||
LightEntity.SUPPORT_WHITE_VALUE) ==
|
||||
LightEntity.SUPPORT_WHITE_VALUE);
|
||||
|
||||
int get brightness => _getIntAttributeValue("brightness");
|
||||
int get whiteValue => _getIntAttributeValue("white_value");
|
||||
String get effect => attributes["effect"];
|
||||
int get colorTemp => _getIntAttributeValue("color_temp");
|
||||
double get maxMireds => _getDoubleAttributeValue("max_mireds");
|
||||
double get minMireds => _getDoubleAttributeValue("min_mireds");
|
||||
HSVColor get color => _getColor();
|
||||
bool get isAdditionalControls => ((supportedFeatures != null) && (supportedFeatures != 0));
|
||||
List<String> get effectList => getStringListAttributeValue("effect_list");
|
||||
|
||||
LightEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
HSVColor _getColor() {
|
||||
List hs = attributes["hs_color"];
|
||||
List rgb = attributes["rgb_color"];
|
||||
try {
|
||||
if (hs != null && hs.isNotEmpty) {
|
||||
double sat = hs[1]/100;
|
||||
String ssat = sat.toStringAsFixed(2);
|
||||
return HSVColor.fromAHSV(1.0, hs[0], double.parse(ssat), 1.0);
|
||||
} else if (rgb != null && rgb.isNotEmpty) {
|
||||
return HSVColor.fromColor(Color.fromARGB(255, rgb[0], rgb[1], rgb[2]));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return SwitchStateWidget();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
if (!isAdditionalControls || state == EntityState.unavailable) {
|
||||
return Container(height: 0.0, width: 0.0);
|
||||
} else {
|
||||
return LightControlsWidget();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
218
lib/entities/light/widgets/light_controls.dart
Normal file
218
lib/entities/light/widgets/light_controls.dart
Normal file
@ -0,0 +1,218 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class LightControlsWidget extends StatefulWidget {
|
||||
|
||||
@override
|
||||
_LightControlsWidgetState createState() => _LightControlsWidgetState();
|
||||
|
||||
}
|
||||
|
||||
class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
|
||||
int _tmpBrightness;
|
||||
int _tmpWhiteValue;
|
||||
int _tmpColorTemp = 0;
|
||||
HSVColor _tmpColor = HSVColor.fromAHSV(1.0, 30.0, 0.0, 1.0);
|
||||
bool _changedHere = false;
|
||||
String _tmpEffect;
|
||||
|
||||
void _resetState(LightEntity entity) {
|
||||
_tmpBrightness = entity.brightness ?? 1;
|
||||
_tmpWhiteValue = entity.whiteValue ?? 0;
|
||||
_tmpColorTemp = entity.colorTemp ?? entity.minMireds?.toInt();
|
||||
_tmpColor = entity.color ?? _tmpColor;
|
||||
_tmpEffect = entity.effect;
|
||||
}
|
||||
|
||||
void _setBrightness(LightEntity entity, double value) {
|
||||
setState(() {
|
||||
_tmpBrightness = value.round();
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_on", entity.entityId,
|
||||
{"brightness": _tmpBrightness}));
|
||||
});
|
||||
}
|
||||
|
||||
void _setWhiteValue(LightEntity entity, double value) {
|
||||
setState(() {
|
||||
_tmpWhiteValue = value.round();
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_on", entity.entityId,
|
||||
{"white_value": _tmpWhiteValue}));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
void _setColorTemp(LightEntity entity, double value) {
|
||||
setState(() {
|
||||
_tmpColorTemp = value.round();
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_on", entity.entityId,
|
||||
{"color_temp": _tmpColorTemp}));
|
||||
});
|
||||
}
|
||||
|
||||
void _setColor(LightEntity entity, HSVColor color) {
|
||||
setState(() {
|
||||
_tmpColor = color;
|
||||
_changedHere = true;
|
||||
Logger.d( "HS Color: [${color.hue}, ${color.saturation}]");
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_on", entity.entityId,
|
||||
{"hs_color": [color.hue, color.saturation*100]}));
|
||||
});
|
||||
}
|
||||
|
||||
void _setEffect(LightEntity entity, String value) {
|
||||
setState(() {
|
||||
_tmpEffect = value;
|
||||
_changedHere = true;
|
||||
if (_tmpEffect != null) {
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_on", entity.entityId,
|
||||
{"effect": "$value"}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final LightEntity entity = entityModel.entityWrapper.entity;
|
||||
if (!_changedHere) {
|
||||
_resetState(entity);
|
||||
} else {
|
||||
_changedHere = false;
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
_buildBrightnessControl(entity),
|
||||
_buildWhiteValueControl(entity),
|
||||
_buildColorTempControl(entity),
|
||||
_buildColorControl(entity),
|
||||
_buildEffectControl(entity)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBrightnessControl(LightEntity entity) {
|
||||
if ((entity.supportBrightness) && (_tmpBrightness != null)) {
|
||||
return UniversalSlider(
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_tmpBrightness = value.round();
|
||||
});
|
||||
},
|
||||
min: 1.0,
|
||||
max: 255.0,
|
||||
onChangeEnd: (value) => _setBrightness(entity, value),
|
||||
value: _tmpBrightness == null ? 1.0 : _tmpBrightness.toDouble(),
|
||||
leading: Icon(Icons.brightness_5),
|
||||
title: "Brightness",
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildWhiteValueControl(LightEntity entity) {
|
||||
if ((entity.supportWhiteValue) && (_tmpWhiteValue != null)) {
|
||||
return UniversalSlider(
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_tmpWhiteValue = value.round();
|
||||
});
|
||||
},
|
||||
min: 0.0,
|
||||
max: 255.0,
|
||||
onChangeEnd: (value) => _setWhiteValue(entity, value),
|
||||
value: _tmpWhiteValue == null ? 0.0 : _tmpWhiteValue.toDouble(),
|
||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:file-word-box")),
|
||||
title: "White",
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildColorTempControl(LightEntity entity) {
|
||||
if (entity.supportColorTemp) {
|
||||
return UniversalSlider(
|
||||
title: "Color temperature",
|
||||
leading: Text("Cold", style: TextStyle(color: Colors.lightBlue),),
|
||||
value: _tmpColorTemp == null ? entity.maxMireds : _tmpColorTemp.toDouble(),
|
||||
onChangeEnd: (value) => _setColorTemp(entity, value),
|
||||
max: entity.maxMireds,
|
||||
min: entity.minMireds,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_tmpColorTemp = value.round();
|
||||
});
|
||||
},
|
||||
closing: Text("Warm", style: TextStyle(color: Colors.amberAccent),),
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildColorControl(LightEntity entity) {
|
||||
if (entity.supportColor) {
|
||||
HSVColor savedColor = HomeAssistant().savedColor;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
LightColorPicker(
|
||||
color: _tmpColor,
|
||||
onColorSelected: (color) => _setColor(entity, color),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
FlatButton(
|
||||
color: _tmpColor.toColor(),
|
||||
child: Text('Copy color'),
|
||||
onPressed: _tmpColor == null ? null : () {
|
||||
setState(() {
|
||||
HomeAssistant().savedColor = _tmpColor;
|
||||
});
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
color: savedColor?.toColor() ?? Colors.transparent,
|
||||
child: Text('Paste color'),
|
||||
onPressed: savedColor == null ? null : () {
|
||||
_setColor(entity, savedColor);
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildEffectControl(LightEntity entity) {
|
||||
if ((entity.supportEffect) && (entity.effectList != null)) {
|
||||
return ModeSelectorWidget(
|
||||
onChange: (effect) => _setEffect(entity, effect),
|
||||
caption: "Effect",
|
||||
options: entity.effectList,
|
||||
value: _tmpEffect
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
21
lib/entities/lock/lock_entity.class.dart
Normal file
21
lib/entities/lock/lock_entity.class.dart
Normal file
@ -0,0 +1,21 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class LockEntity extends Entity {
|
||||
LockEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
bool get isLocked => state == "locked";
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return LockStateWidget(
|
||||
assumedState: false,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget _buildStatePartForPage(BuildContext context) {
|
||||
return LockStateWidget(
|
||||
assumedState: true,
|
||||
);
|
||||
}
|
||||
}
|
66
lib/entities/lock/widgets/lock_state.dart
Normal file
66
lib/entities/lock/widgets/lock_state.dart
Normal file
@ -0,0 +1,66 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class LockStateWidget extends StatelessWidget {
|
||||
|
||||
final bool assumedState;
|
||||
|
||||
const LockStateWidget({Key key, this.assumedState: false}) : super(key: key);
|
||||
|
||||
void _lock(Entity entity) {
|
||||
eventBus.fire(new ServiceCallEvent("lock", "lock", entity.entityId, null));
|
||||
}
|
||||
|
||||
void _unlock(Entity entity) {
|
||||
eventBus.fire(new ServiceCallEvent("lock", "unlock", entity.entityId, null));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final LockEntity entity = entityModel.entityWrapper.entity;
|
||||
if (assumedState) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
height: 34.0,
|
||||
child: FlatButton(
|
||||
onPressed: () => _unlock(entity),
|
||||
child: Text("UNLOCK",
|
||||
textAlign: TextAlign.right,
|
||||
style:
|
||||
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
||||
),
|
||||
)
|
||||
),
|
||||
SizedBox(
|
||||
height: 34.0,
|
||||
child: FlatButton(
|
||||
onPressed: () => _lock(entity),
|
||||
child: Text("LOCK",
|
||||
textAlign: TextAlign.right,
|
||||
style:
|
||||
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
||||
),
|
||||
)
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return SizedBox(
|
||||
height: 34.0,
|
||||
child: FlatButton(
|
||||
onPressed: (() {
|
||||
entity.isLocked ? _unlock(entity) : _lock(entity);
|
||||
}),
|
||||
child: Text(
|
||||
entity.isLocked ? "UNLOCK" : "LOCK",
|
||||
textAlign: TextAlign.right,
|
||||
style:
|
||||
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
83
lib/entities/media_player/media_player_entity.class.dart
Normal file
83
lib/entities/media_player/media_player_entity.class.dart
Normal file
@ -0,0 +1,83 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class MediaPlayerEntity extends Entity {
|
||||
|
||||
static const SUPPORT_PAUSE = 1;
|
||||
static const SUPPORT_SEEK = 2;
|
||||
static const SUPPORT_VOLUME_SET = 4;
|
||||
static const SUPPORT_VOLUME_MUTE = 8;
|
||||
static const SUPPORT_PREVIOUS_TRACK = 16;
|
||||
static const SUPPORT_NEXT_TRACK = 32;
|
||||
|
||||
static const SUPPORT_TURN_ON = 128;
|
||||
static const SUPPORT_TURN_OFF = 256;
|
||||
static const SUPPORT_PLAY_MEDIA = 512;
|
||||
static const SUPPORT_VOLUME_STEP = 1024;
|
||||
static const SUPPORT_SELECT_SOURCE = 2048;
|
||||
static const SUPPORT_STOP = 4096;
|
||||
static const SUPPORT_CLEAR_PLAYLIST = 8192;
|
||||
static const SUPPORT_PLAY = 16384;
|
||||
static const SUPPORT_SHUFFLE_SET = 32768;
|
||||
static const SUPPORT_SELECT_SOUND_MODE = 65536;
|
||||
|
||||
MediaPlayerEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
bool get supportPause => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_PAUSE) ==
|
||||
MediaPlayerEntity.SUPPORT_PAUSE);
|
||||
bool get supportSeek => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_SEEK) ==
|
||||
MediaPlayerEntity.SUPPORT_SEEK);
|
||||
bool get supportVolumeSet => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_VOLUME_SET) ==
|
||||
MediaPlayerEntity.SUPPORT_VOLUME_SET);
|
||||
bool get supportVolumeMute => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_VOLUME_MUTE) ==
|
||||
MediaPlayerEntity.SUPPORT_VOLUME_MUTE);
|
||||
bool get supportPreviousTrack => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK) ==
|
||||
MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK);
|
||||
bool get supportNextTrack => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_NEXT_TRACK) ==
|
||||
MediaPlayerEntity.SUPPORT_NEXT_TRACK);
|
||||
|
||||
bool get supportTurnOn => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_TURN_ON) ==
|
||||
MediaPlayerEntity.SUPPORT_TURN_ON);
|
||||
bool get supportTurnOff => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_TURN_OFF) ==
|
||||
MediaPlayerEntity.SUPPORT_TURN_OFF);
|
||||
bool get supportPlayMedia => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_PLAY_MEDIA) ==
|
||||
MediaPlayerEntity.SUPPORT_PLAY_MEDIA);
|
||||
bool get supportVolumeStep => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_VOLUME_STEP) ==
|
||||
MediaPlayerEntity.SUPPORT_VOLUME_STEP);
|
||||
bool get supportSelectSource => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_SELECT_SOURCE) ==
|
||||
MediaPlayerEntity.SUPPORT_SELECT_SOURCE);
|
||||
bool get supportStop => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_STOP) ==
|
||||
MediaPlayerEntity.SUPPORT_STOP);
|
||||
bool get supportClearPlaylist => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST) ==
|
||||
MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST);
|
||||
bool get supportPlay => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_PLAY) ==
|
||||
MediaPlayerEntity.SUPPORT_PLAY);
|
||||
bool get supportShuffleSet => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_SHUFFLE_SET) ==
|
||||
MediaPlayerEntity.SUPPORT_SHUFFLE_SET);
|
||||
bool get supportSelectSoundMode => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE) ==
|
||||
MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE);
|
||||
|
||||
List<String> get soundModeList => getStringListAttributeValue("sound_mode_list");
|
||||
List<String> get sourceList => getStringListAttributeValue("source_list");
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
return MediaPlayerControls();
|
||||
}
|
||||
|
||||
}
|
466
lib/entities/media_player/widgets/media_player_widgets.dart
Normal file
466
lib/entities/media_player/widgets/media_player_widgets.dart
Normal file
@ -0,0 +1,466 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class MediaPlayerWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final EntityModel entityModel = EntityModel.of(context);
|
||||
final MediaPlayerEntity entity = entityModel.entityWrapper.entity;
|
||||
//TheLogger.debug("stop: ${entity.supportStop}, seek: ${entity.supportSeek}");
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Stack(
|
||||
alignment: AlignmentDirectional.topEnd,
|
||||
children: <Widget>[
|
||||
_buildImage(entity),
|
||||
Positioned(
|
||||
bottom: 0.0,
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
child: Container(
|
||||
color: Colors.black45,
|
||||
child: _buildState(entity),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0.0,
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
child: MediaPlayerProgressWidget()
|
||||
)
|
||||
],
|
||||
),
|
||||
MediaPlayerPlaybackControls()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildState(MediaPlayerEntity entity) {
|
||||
TextStyle style = TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.2
|
||||
);
|
||||
List<Widget> states = [];
|
||||
states.add(Text("${entity.displayName}", style: style));
|
||||
String state = entity.state;
|
||||
if (state == null || state == EntityState.off || state == EntityState.unavailable || state == EntityState.idle) {
|
||||
states.add(Text("${entity.state}", style: style.apply(fontSizeDelta: 4.0),));
|
||||
}
|
||||
if (entity.attributes['media_title'] != null) {
|
||||
states.add(Text(
|
||||
"${entity.attributes['media_title']}",
|
||||
style: style.apply(fontSizeDelta: 6.0, fontWeightDelta: 50),
|
||||
maxLines: 1,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
));
|
||||
}
|
||||
if (entity.attributes['media_content_type'] == "music") {
|
||||
states.add(Text("${entity.attributes['media_artist'] ?? entity.attributes['app_name']}", style: style.apply(fontSizeDelta: 4.0),));
|
||||
} else if (entity.attributes['app_name'] != null) {
|
||||
states.add(Text("${entity.attributes['app_name']}", style: style.apply(fontSizeDelta: 4.0),));
|
||||
}
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: states,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildImage(MediaPlayerEntity entity) {
|
||||
String state = entity.state;
|
||||
if (entity.entityPicture != null && state != EntityState.off && state != EntityState.unavailable && state != EntityState.idle) {
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: Image(
|
||||
image: CachedNetworkImageProvider("${entity.entityPicture}"),
|
||||
height: 240.0,
|
||||
//width: 320.0,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
MaterialDesignIcons.getIconDataFromIconName("mdi:movie"),
|
||||
size: 150.0,
|
||||
color: EntityColor.stateColor("$state"),
|
||||
)
|
||||
],
|
||||
);
|
||||
/*return Container(
|
||||
color: Colors.blue,
|
||||
height: 80.0,
|
||||
);*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MediaPlayerPlaybackControls extends StatelessWidget {
|
||||
|
||||
final bool showMenu;
|
||||
final bool showStop;
|
||||
|
||||
const MediaPlayerPlaybackControls({Key key, this.showMenu: true, this.showStop: false}) : super(key: key);
|
||||
|
||||
|
||||
void _setPower(MediaPlayerEntity entity) {
|
||||
if (entity.state != EntityState.unavailable && entity.state != EntityState.unknown) {
|
||||
if (entity.state == EntityState.off) {
|
||||
Logger.d("${entity.entityId} turn_on");
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_on", entity.entityId,
|
||||
null));
|
||||
} else {
|
||||
Logger.d("${entity.entityId} turn_off");
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_off", entity.entityId,
|
||||
null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _callAction(MediaPlayerEntity entity, String action) {
|
||||
Logger.d("${entity.entityId} $action");
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "$action", entity.entityId,
|
||||
null));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MediaPlayerEntity entity = EntityModel.of(context).entityWrapper.entity;
|
||||
List<Widget> result = [];
|
||||
if (entity.supportTurnOn || entity.supportTurnOff) {
|
||||
result.add(
|
||||
IconButton(
|
||||
icon: Icon(Icons.power_settings_new),
|
||||
onPressed: () => _setPower(entity),
|
||||
iconSize: Sizes.iconSize,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
result.add(
|
||||
Container(
|
||||
width: Sizes.iconSize,
|
||||
)
|
||||
);
|
||||
}
|
||||
List <Widget> centeredControlsChildren = [];
|
||||
if (entity.supportPreviousTrack && entity.state != EntityState.off && entity.state != EntityState.unavailable) {
|
||||
centeredControlsChildren.add(
|
||||
IconButton(
|
||||
icon: Icon(Icons.skip_previous),
|
||||
onPressed: () => _callAction(entity, "media_previous_track"),
|
||||
iconSize: Sizes.iconSize,
|
||||
)
|
||||
);
|
||||
}
|
||||
if (entity.supportPlay || entity.supportPause) {
|
||||
if (entity.state == EntityState.playing) {
|
||||
centeredControlsChildren.add(
|
||||
IconButton(
|
||||
icon: Icon(Icons.pause_circle_filled),
|
||||
color: Colors.blue,
|
||||
onPressed: () => _callAction(entity, "media_pause"),
|
||||
iconSize: Sizes.iconSize*1.8,
|
||||
)
|
||||
);
|
||||
} else if (entity.state == EntityState.paused || entity.state == EntityState.idle) {
|
||||
centeredControlsChildren.add(
|
||||
IconButton(
|
||||
icon: Icon(Icons.play_circle_filled),
|
||||
color: Colors.blue,
|
||||
onPressed: () => _callAction(entity, "media_play"),
|
||||
iconSize: Sizes.iconSize*1.8,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
centeredControlsChildren.add(
|
||||
Container(
|
||||
width: Sizes.iconSize*1.8,
|
||||
height: Sizes.iconSize*2.0,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (entity.supportNextTrack && entity.state != EntityState.off && entity.state != EntityState.unavailable) {
|
||||
centeredControlsChildren.add(
|
||||
IconButton(
|
||||
icon: Icon(Icons.skip_next),
|
||||
onPressed: () => _callAction(entity, "media_next_track"),
|
||||
iconSize: Sizes.iconSize,
|
||||
)
|
||||
);
|
||||
}
|
||||
if (centeredControlsChildren.isNotEmpty) {
|
||||
result.add(
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: showMenu ? MainAxisAlignment.center : MainAxisAlignment.end,
|
||||
children: centeredControlsChildren,
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
result.add(
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 10.0,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
if (showMenu) {
|
||||
result.add(
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||
"mdi:dots-vertical")),
|
||||
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity))
|
||||
)
|
||||
);
|
||||
} else if (entity.supportStop && entity.state != EntityState.off && entity.state != EntityState.unavailable) {
|
||||
result.add(
|
||||
IconButton(
|
||||
icon: Icon(Icons.stop),
|
||||
onPressed: () => _callAction(entity, "media_stop")
|
||||
)
|
||||
);
|
||||
}
|
||||
return Row(
|
||||
children: result,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MediaPlayerControls extends StatefulWidget {
|
||||
@override
|
||||
_MediaPlayerControlsState createState() => _MediaPlayerControlsState();
|
||||
}
|
||||
|
||||
class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
||||
|
||||
double _newVolumeLevel;
|
||||
bool _changedHere = false;
|
||||
String _newSoundMode;
|
||||
String _newSource;
|
||||
|
||||
void _setVolume(double value, String entityId) {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_newVolumeLevel = value;
|
||||
eventBus.fire(ServiceCallEvent("media_player", "volume_set", entityId, {"volume_level": value}));
|
||||
});
|
||||
}
|
||||
|
||||
void _setVolumeMute(bool isMuted, String entityId) {
|
||||
eventBus.fire(ServiceCallEvent("media_player", "volume_mute", entityId, {"is_volume_muted": isMuted}));
|
||||
}
|
||||
|
||||
void _setVolumeUp(String entityId) {
|
||||
eventBus.fire(ServiceCallEvent("media_player", "volume_up", entityId, null));
|
||||
}
|
||||
|
||||
void _setVolumeDown(String entityId) {
|
||||
eventBus.fire(ServiceCallEvent("media_player", "volume_down", entityId, null));
|
||||
}
|
||||
|
||||
void _setSoundMode(String value, String entityId) {
|
||||
setState(() {
|
||||
_newSoundMode = value;
|
||||
_changedHere = true;
|
||||
eventBus.fire(ServiceCallEvent("media_player", "select_sound_mode", entityId, {"sound_mode": "$value"}));
|
||||
});
|
||||
}
|
||||
|
||||
void _setSource(String source, String entityId) {
|
||||
setState(() {
|
||||
_newSource = source;
|
||||
_changedHere = true;
|
||||
eventBus.fire(ServiceCallEvent("media_player", "select_source", entityId, {"source": "$source"}));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MediaPlayerEntity entity = EntityModel.of(context).entityWrapper.entity;
|
||||
List<Widget> children = [
|
||||
MediaPlayerPlaybackControls(
|
||||
showMenu: false,
|
||||
)
|
||||
];
|
||||
if (entity.state != EntityState.off && entity.state != EntityState.unknown && entity.state != EntityState.unavailable) {
|
||||
Widget muteWidget;
|
||||
Widget volumeStepWidget;
|
||||
if (entity.supportVolumeMute || entity.attributes["is_volume_muted"] != null) {
|
||||
bool isMuted = entity.attributes["is_volume_muted"] ?? false;
|
||||
muteWidget =
|
||||
IconButton(
|
||||
icon: Icon(isMuted ? Icons.volume_up : Icons.volume_off),
|
||||
onPressed: () => _setVolumeMute(!isMuted, entity.entityId)
|
||||
);
|
||||
} else {
|
||||
muteWidget = Container(width: 0.0, height: 0.0,);
|
||||
}
|
||||
if (entity.supportVolumeStep) {
|
||||
volumeStepWidget = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:plus")),
|
||||
onPressed: () => _setVolumeUp(entity.entityId)
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:minus")),
|
||||
onPressed: () => _setVolumeDown(entity.entityId)
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
volumeStepWidget = Container(width: 0.0, height: 0.0,);
|
||||
}
|
||||
if (entity.supportVolumeSet) {
|
||||
if (!_changedHere) {
|
||||
_newVolumeLevel = entity._getDoubleAttributeValue("volume_level");
|
||||
} else {
|
||||
_changedHere = false;
|
||||
}
|
||||
children.add(
|
||||
UniversalSlider(
|
||||
leading: muteWidget,
|
||||
closing: volumeStepWidget,
|
||||
title: "Volume",
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_newVolumeLevel = value;
|
||||
});
|
||||
},
|
||||
value: _newVolumeLevel,
|
||||
onChangeEnd: (value) => _setVolume(value, entity.entityId),
|
||||
max: 1.0,
|
||||
min: 0.0,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
children.add(Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
muteWidget,
|
||||
volumeStepWidget
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
if (entity.supportSelectSoundMode && entity.soundModeList != null) {
|
||||
if (!_changedHere) {
|
||||
_newSoundMode = entity.attributes["sound_mode"];
|
||||
} else {
|
||||
_changedHere = false;
|
||||
}
|
||||
children.add(
|
||||
ModeSelectorWidget(
|
||||
options: entity.soundModeList,
|
||||
caption: "Sound mode",
|
||||
value: _newSoundMode,
|
||||
onChange: (value) => _setSoundMode(value, entity.entityId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (entity.supportSelectSource && entity.sourceList != null) {
|
||||
if (!_changedHere) {
|
||||
_newSource = entity.attributes["source"];
|
||||
} else {
|
||||
_changedHere = false;
|
||||
}
|
||||
children.add(
|
||||
ModeSelectorWidget(
|
||||
options: entity.sourceList,
|
||||
caption: "Source",
|
||||
value: _newSource,
|
||||
onChange: (value) => _setSource(value, entity.entityId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
return Column(
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MediaPlayerProgressWidget extends StatefulWidget {
|
||||
@override
|
||||
_MediaPlayerProgressWidgetState createState() => _MediaPlayerProgressWidgetState();
|
||||
}
|
||||
|
||||
class _MediaPlayerProgressWidgetState extends State<MediaPlayerProgressWidget> {
|
||||
|
||||
Timer _timer;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final EntityModel entityModel = EntityModel.of(context);
|
||||
final MediaPlayerEntity entity = entityModel.entityWrapper.entity;
|
||||
double progress;
|
||||
try {
|
||||
DateTime lastUpdated = DateTime.parse(
|
||||
entity.attributes["media_position_updated_at"]).toLocal();
|
||||
Duration duration = Duration(seconds: entity._getIntAttributeValue("media_duration") ?? 1);
|
||||
Duration position = Duration(seconds: entity._getIntAttributeValue("media_position") ?? 0);
|
||||
int currentPosition = position.inSeconds;
|
||||
if (entity.state == EntityState.playing) {
|
||||
_timer?.cancel();
|
||||
_timer = Timer(Duration(seconds: 1), () {
|
||||
setState(() {
|
||||
});
|
||||
});
|
||||
int differenceInSeconds = DateTime
|
||||
.now()
|
||||
.difference(lastUpdated)
|
||||
.inSeconds;
|
||||
currentPosition = currentPosition + differenceInSeconds;
|
||||
} else {
|
||||
_timer?.cancel();
|
||||
}
|
||||
progress = currentPosition / duration.inSeconds;
|
||||
return LinearProgressIndicator(
|
||||
value: progress,
|
||||
backgroundColor: Colors.black45,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(EntityColor.stateColor(EntityState.on)),
|
||||
);
|
||||
} catch (e) {
|
||||
_timer?.cancel();
|
||||
progress = 0.0;
|
||||
}
|
||||
return LinearProgressIndicator(
|
||||
value: progress,
|
||||
backgroundColor: Colors.black45,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
14
lib/entities/select/select_entity.class.dart
Normal file
14
lib/entities/select/select_entity.class.dart
Normal file
@ -0,0 +1,14 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class SelectEntity extends Entity {
|
||||
List<String> get listOptions => attributes["options"] != null
|
||||
? (attributes["options"] as List).cast<String>()
|
||||
: [];
|
||||
|
||||
SelectEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return SelectStateWidget();
|
||||
}
|
||||
}
|
49
lib/entities/select/widgets/select_state.dart
Normal file
49
lib/entities/select/widgets/select_state.dart
Normal file
@ -0,0 +1,49 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class SelectStateWidget extends StatefulWidget {
|
||||
|
||||
SelectStateWidget({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SelectStateWidgetState createState() => _SelectStateWidgetState();
|
||||
}
|
||||
|
||||
class _SelectStateWidgetState extends State<SelectStateWidget> {
|
||||
|
||||
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.entityWrapper.entity;
|
||||
Widget ctrl;
|
||||
if (entity.listOptions.isNotEmpty) {
|
||||
ctrl = DropdownButton<String>(
|
||||
value: entity.state,
|
||||
isExpanded: true,
|
||||
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 Flexible(
|
||||
flex: 2,
|
||||
fit: FlexFit.tight,
|
||||
//width: Entity.INPUT_WIDTH,
|
||||
child: ctrl,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
13
lib/entities/sensor/sensor_entity.class.dart
Normal file
13
lib/entities/sensor/sensor_entity.class.dart
Normal file
@ -0,0 +1,13 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class SensorEntity extends Entity {
|
||||
|
||||
@override
|
||||
EntityHistoryConfig historyConfig = EntityHistoryConfig(
|
||||
chartType: EntityHistoryWidgetType.numericState,
|
||||
numericState: true
|
||||
);
|
||||
|
||||
SensorEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
}
|
44
lib/entities/slider/slider_entity.dart
Normal file
44
lib/entities/slider/slider_entity.dart
Normal file
@ -0,0 +1,44 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class SliderEntity extends Entity {
|
||||
SliderEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
double get minValue => _getDoubleAttributeValue("min") ?? 0.0;
|
||||
double get maxValue =>_getDoubleAttributeValue("max") ?? 100.0;
|
||||
double get valueStep => _getDoubleAttributeValue("step") ?? 1.0;
|
||||
|
||||
@override
|
||||
EntityHistoryConfig historyConfig = EntityHistoryConfig(
|
||||
chartType: EntityHistoryWidgetType.numericState,
|
||||
numericState: true
|
||||
);
|
||||
|
||||
/*@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return Expanded(
|
||||
//width: 200.0,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
SliderStateWidget(
|
||||
expanded: true,
|
||||
),
|
||||
SimpleEntityState(
|
||||
expanded: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget _buildStatePartForPage(BuildContext context) {
|
||||
return SimpleEntityState(
|
||||
expanded: false,
|
||||
);
|
||||
}*/
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
return SliderControlsWidget();
|
||||
}
|
||||
}
|
70
lib/entities/slider/widgets/slider_controls.dart
Normal file
70
lib/entities/slider/widgets/slider_controls.dart
Normal file
@ -0,0 +1,70 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class SliderControlsWidget extends StatefulWidget {
|
||||
|
||||
SliderControlsWidget({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SliderControlsWidgetState createState() => _SliderControlsWidgetState();
|
||||
}
|
||||
|
||||
class _SliderControlsWidgetState extends State<SliderControlsWidget> {
|
||||
int _multiplier = 1;
|
||||
double _newValue;
|
||||
bool _changedHere = false;
|
||||
|
||||
void setNewState(newValue, domain, entityId) {
|
||||
setState(() {
|
||||
_newValue = newValue;
|
||||
_changedHere = true;
|
||||
});
|
||||
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.entityWrapper.entity;
|
||||
if (entity.valueStep < 1) {
|
||||
_multiplier = 10;
|
||||
} else if (entity.valueStep < 0.1) {
|
||||
_multiplier = 100;
|
||||
}
|
||||
if (!_changedHere) {
|
||||
_newValue = entity.doubleState;
|
||||
} else {
|
||||
_changedHere = false;
|
||||
}
|
||||
Widget slider = Slider(
|
||||
min: entity.minValue * _multiplier,
|
||||
max: entity.maxValue * _multiplier,
|
||||
value: (_newValue <= entity.maxValue) &&
|
||||
(_newValue >= entity.minValue)
|
||||
? _newValue * _multiplier
|
||||
: entity.minValue * _multiplier,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_newValue = (value.roundToDouble() / _multiplier);
|
||||
_changedHere = true;
|
||||
});
|
||||
},
|
||||
onChangeEnd: (value) {
|
||||
setNewState(value.roundToDouble() / _multiplier, entity.domain, entity.entityId);
|
||||
},
|
||||
);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"$_newValue",
|
||||
style: TextStyle(
|
||||
fontSize: Sizes.largeFontSize,
|
||||
color: Colors.blue
|
||||
),
|
||||
),
|
||||
slider
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
5
lib/entities/sun/sun_entity.class.dart
Normal file
5
lib/entities/sun/sun_entity.class.dart
Normal file
@ -0,0 +1,5 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class SunEntity extends Entity {
|
||||
SunEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
}
|
10
lib/entities/switch/switch_entity.class.dart
Normal file
10
lib/entities/switch/switch_entity.class.dart
Normal file
@ -0,0 +1,10 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class SwitchEntity extends Entity {
|
||||
SwitchEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return SwitchStateWidget();
|
||||
}
|
||||
}
|
89
lib/entities/switch/widget/switch_state.dart
Normal file
89
lib/entities/switch/widget/switch_state.dart
Normal file
@ -0,0 +1,89 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class SwitchStateWidget extends StatefulWidget {
|
||||
|
||||
final String domainForService;
|
||||
|
||||
const SwitchStateWidget({Key key, this.domainForService}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SwitchStateWidgetState createState() => _SwitchStateWidgetState();
|
||||
}
|
||||
|
||||
class _SwitchStateWidgetState extends State<SwitchStateWidget> {
|
||||
|
||||
String newState;
|
||||
bool updatedHere = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _setNewState(newValue, Entity entity) {
|
||||
setState(() {
|
||||
newState = newValue ? EntityState.on : EntityState.off;
|
||||
updatedHere = true;
|
||||
});
|
||||
Timer(Duration(seconds: 2), (){
|
||||
setState(() {
|
||||
newState = entity.state;
|
||||
updatedHere = true;
|
||||
//TheLogger.debug("Timer@!!");
|
||||
});
|
||||
});
|
||||
String domain;
|
||||
if (widget.domainForService != null) {
|
||||
domain = widget.domainForService;
|
||||
} else {
|
||||
domain = entity.domain;
|
||||
}
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
domain, (newValue as bool) ? "turn_on" : "turn_off", entity.entityId, null));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final entity = entityModel.entityWrapper.entity;
|
||||
if (!updatedHere) {
|
||||
newState = entity.state;
|
||||
} else {
|
||||
updatedHere = false;
|
||||
}
|
||||
if (entity.state == EntityState.unavailable || entity.state == EntityState.unknown) {
|
||||
return SimpleEntityState();
|
||||
} else if ((entity.attributes["assumed_state"] == null) || (entity.attributes["assumed_state"] == false)) {
|
||||
return SizedBox(
|
||||
height: 32.0,
|
||||
child: Switch(
|
||||
value: newState == EntityState.on,
|
||||
onChanged: ((switchState) {
|
||||
_setNewState(switchState, entity);
|
||||
}),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return SizedBox(
|
||||
height: 32.0,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
onPressed: () => _setNewState(false, entity),
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:flash-off")),
|
||||
color: newState == EntityState.on ? Colors.black : Colors.blue,
|
||||
iconSize: Sizes.iconSize,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => _setNewState(true, entity),
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:flash")),
|
||||
color: newState == EntityState.on ? Colors.blue : Colors.black,
|
||||
iconSize: Sizes.iconSize
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
16
lib/entities/text/text_entity.class.dart
Normal file
16
lib/entities/text/text_entity.class.dart
Normal file
@ -0,0 +1,16 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class TextEntity extends Entity {
|
||||
TextEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
int get valueMinLength => attributes["min"] ?? -1;
|
||||
int get valueMaxLength => attributes["max"] ?? -1;
|
||||
String get valuePattern => attributes["pattern"] ?? null;
|
||||
bool get isTextField => attributes["mode"] == "text";
|
||||
bool get isPasswordField => attributes["mode"] == "password";
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return TextInputStateWidget();
|
||||
}
|
||||
}
|
100
lib/entities/text/widgets/text_input_state.dart
Normal file
100
lib/entities/text/widgets/text_input_state.dart
Normal file
@ -0,0 +1,100 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class TextInputStateWidget extends StatefulWidget {
|
||||
|
||||
TextInputStateWidget({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_TextInputStateWidgetState createState() => _TextInputStateWidgetState();
|
||||
}
|
||||
|
||||
class _TextInputStateWidgetState extends State<TextInputStateWidget> {
|
||||
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.entityWrapper.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 Flexible(
|
||||
fit: FlexFit.tight,
|
||||
flex: 2,
|
||||
//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 {
|
||||
Logger.w( "Unsupported input mode for ${entity.entityId}");
|
||||
return SimpleEntityState();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusNode.removeListener(_focusListener);
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
45
lib/entities/timer/timer_entity.class.dart
Normal file
45
lib/entities/timer/timer_entity.class.dart
Normal file
@ -0,0 +1,45 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class TimerEntity extends Entity {
|
||||
TimerEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
Duration duration;
|
||||
|
||||
@override
|
||||
void update(Map rawData, String webHost) {
|
||||
super.update(rawData, webHost);
|
||||
String durationSource = "${attributes["duration"]}";
|
||||
if (durationSource != null && durationSource.isNotEmpty) {
|
||||
try {
|
||||
List<String> durationList = durationSource.split(":");
|
||||
if (durationList.length == 1) {
|
||||
duration = Duration(seconds: int.tryParse(durationList[0] ?? 0));
|
||||
} else if (durationList.length == 2) {
|
||||
duration = Duration(
|
||||
hours: int.tryParse(durationList[0]) ?? 0,
|
||||
minutes: int.tryParse(durationList[1]) ?? 0
|
||||
);
|
||||
} else if (durationList.length == 3) {
|
||||
duration = Duration(
|
||||
hours: int.tryParse(durationList[0]) ?? 0,
|
||||
minutes: int.tryParse(durationList[1]) ?? 0,
|
||||
seconds: int.tryParse(durationList[2]) ?? 0
|
||||
);
|
||||
} else {
|
||||
Logger.e("Strange $entityId duration format: $durationSource");
|
||||
duration = Duration(seconds: 0);
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.e("Error parsing duration for $entityId: ${e.toString()}");
|
||||
duration = Duration(seconds: 0);
|
||||
}
|
||||
} else {
|
||||
duration = Duration(seconds: 0);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return TimerState();
|
||||
}
|
||||
}
|
65
lib/entities/timer/widgets/timer_state.dart
Normal file
65
lib/entities/timer/widgets/timer_state.dart
Normal file
@ -0,0 +1,65 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class TimerState extends StatefulWidget {
|
||||
//final bool expanded;
|
||||
//final TextAlign textAlign;
|
||||
//final EdgeInsetsGeometry padding;
|
||||
//final int maxLines;
|
||||
|
||||
const TimerState({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_TimerStateState createState() => _TimerStateState();
|
||||
|
||||
}
|
||||
|
||||
class _TimerStateState extends State<TimerState> {
|
||||
|
||||
Timer timer;
|
||||
Duration remaining = Duration(seconds: 0);
|
||||
|
||||
void checkState(TimerEntity entity) {
|
||||
if (entity.state == EntityState.active) {
|
||||
//Logger.d("Timer is active");
|
||||
if (timer == null || !timer.isActive) {
|
||||
timer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
setState(() {
|
||||
try {
|
||||
int passed = DateTime
|
||||
.now()
|
||||
.difference(entity._lastUpdated)
|
||||
.inSeconds;
|
||||
remaining = Duration(seconds: entity.duration.inSeconds - passed);
|
||||
} catch (e) {
|
||||
Logger.e("Error calculating ${entity.entityId} remaining time: ${e.toString()}");
|
||||
remaining = Duration(seconds: 0);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
timer?.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
EntityModel model = EntityModel.of(context);
|
||||
TimerEntity entity = model.entityWrapper.entity;
|
||||
checkState(entity);
|
||||
if (entity.state != EntityState.active) {
|
||||
return SimpleEntityState();
|
||||
} else {
|
||||
return SimpleEntityState(
|
||||
customValue: "${remaining.toString().split('.')[0]}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user