Compare commits

...

25 Commits

Author SHA1 Message Date
4bb616b327 Build number 2019-01-31 21:14:20 +02:00
38219618ba version 0.3.14 2019-01-31 21:05:11 +02:00
6774b53758 Disable unfinished camera support 2019-01-31 20:57:33 +02:00
29a94c882f WIP MJPEG stream handling 2019-01-31 01:04:13 +02:00
5897fa3a99 WIP #143 2019-01-30 00:25:41 +02:00
7af92c2dc9 WIP #143 Camera support 2019-01-29 22:03:08 +02:00
1094177a42 Resolves #282 Trigger button for alarm 2019-01-29 18:51:28 +02:00
5e814e8109 Resolves #204 Alarm panel card support 2019-01-29 15:00:15 +02:00
24c7675fa4 Resolves #142 Alarm control panel support 2019-01-29 11:54:26 +02:00
dc3ca38c78 WIP #142 Alarm control panel 2019-01-28 16:48:49 +02:00
96b528e055 Update README.md 2019-01-28 15:05:19 +02:00
3858036631 Resolves #139 Trigger for automations 2019-01-25 23:48:31 +02:00
19d42ceeb3 Fix names null 2019-01-25 23:30:23 +02:00
a2836a3603 Resolves #257 2019-01-25 23:08:12 +02:00
2a45758a6d Resolves #268 Badges for Lovelace UI 2019-01-25 22:55:41 +02:00
dc1bf4d878 WIP #266 fix icons 2019-01-25 22:45:54 +02:00
e82ba60c4e WIP #266 Card parsing proper error handling and toString for some fields 2019-01-25 22:41:26 +02:00
09199d30e8 Resolves #274 Use Lovelace UI by default 2019-01-25 22:29:16 +02:00
724d32dbe2 Resolves #277 Remove legacy password support 2019-01-25 22:27:13 +02:00
949c8ee44e Resolves #264 Throttle for sending thermostat temperature 2019-01-25 22:19:11 +02:00
1a446d34c7 Resolves #262 Cler 2019-01-25 22:19:11 +02:00
22a5847285 Resolves #270 Current light effect for lights 2019-01-24 12:26:38 +02:00
1c8f770f10 Resolves #121 Markdown card support 2019-01-23 23:34:45 +02:00
be5ea55f6b Fix light color controls appearence issue 2018-12-29 17:44:11 +02:00
c65ade9827 Fix icons for entity button 2018-12-25 11:48:37 +02:00
27 changed files with 779 additions and 213 deletions

View File

@ -4,7 +4,7 @@
Home Assistant Android client on Dart with Flutter. Home Assistant Android client on Dart with Flutter.
Visit [www.keyboardcrumbs.io](http://www.keyboardcrumbs.io/ha-client) for more info. Visit [www.vynn.co](https://www.vynn.co/ha-client) for more info.
Join [Google Group](https://groups.google.com/d/forum/ha-client-alpha-testing) to become an alpha tester Join [Google Group](https://groups.google.com/d/forum/ha-client-alpha-testing) to become an alpha tester

View File

@ -31,6 +31,11 @@ keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android { android {
compileSdkVersion 27 compileSdkVersion 27
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions { lintOptions {
disable 'InvalidPackage' disable 'InvalidPackage'
} }

View File

@ -0,0 +1,12 @@
part of '../main.dart';
class AlarmControlPanelEntity extends Entity {
AlarmControlPanelEntity(Map rawData) : super(rawData);
@override
Widget _buildAdditionalControlsForPage(BuildContext context) {
return AlarmControlPanelControlsWidget(
extended: false,
);
}
}

View File

@ -0,0 +1,24 @@
part of '../main.dart';
class AutomationEntity extends Entity {
AutomationEntity(Map rawData) : super(rawData);
@override
Widget _buildStatePart(BuildContext context) {
return SwitchStateWidget();
}
@override
Widget _buildAdditionalControlsForPage(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
FlatServiceButton(
text: "TRIGGER",
serviceName: "trigger",
)
],
);
}
}

View File

@ -5,6 +5,8 @@ class ButtonEntity extends Entity {
@override @override
Widget _buildStatePart(BuildContext context) { Widget _buildStatePart(BuildContext context) {
return ButtonStateWidget(); return FlatServiceButton(
text: "EXECUTE",
);
} }
} }

View File

@ -0,0 +1,19 @@
part of '../main.dart';
class CameraEntity extends Entity {
static const SUPPORT_ON_OFF = 1;
CameraEntity(Map rawData) : super(rawData);
bool get supportOnOff => ((attributes["supported_features"] &
CameraEntity.SUPPORT_ON_OFF) ==
CameraEntity.SUPPORT_ON_OFF);
@override
Widget _buildAdditionalControlsForPage(BuildContext context) {
return CameraControlsWidget(
url: 'https://citadel.vynn.co:8123/api/camera_proxy_stream/camera.demo_camera?token=${this.attributes['access_token']}',
);
}
}

View File

@ -94,4 +94,5 @@ class CardType {
static const entityButton = "entity-button"; static const entityButton = "entity-button";
static const conditional = "conditional"; static const conditional = "conditional";
static const alarmPanel = "alarm-panel"; static const alarmPanel = "alarm-panel";
static const markdown = "markdown";
} }

View File

@ -34,7 +34,7 @@ class Entity {
"moisture.on": "Wet", "moisture.on": "Wet",
"moisture.off": "Dry", "moisture.off": "Dry",
"motion.on": "Detected", "motion.on": "Detected",
"motion.off": "Cler", "motion.off": "Clear",
"moving.on": "Moving", "moving.on": "Moving",
"moving.off": "Stopped", "moving.off": "Stopped",
"occupancy.on": "Occupied", "occupancy.on": "Occupied",

View File

@ -33,6 +33,7 @@ class LightEntity extends Entity {
LightEntity.SUPPORT_WHITE_VALUE); LightEntity.SUPPORT_WHITE_VALUE);
int get brightness => _getIntAttributeValue("brightness"); int get brightness => _getIntAttributeValue("brightness");
String get effect => attributes["effect"];
int get colorTemp => _getIntAttributeValue("color_temp"); int get colorTemp => _getIntAttributeValue("color_temp");
double get maxMireds => _getDoubleAttributeValue("max_mireds"); double get maxMireds => _getDoubleAttributeValue("max_mireds");
double get minMireds => _getDoubleAttributeValue("min_mireds"); double get minMireds => _getDoubleAttributeValue("min_mireds");

View File

@ -47,7 +47,10 @@ class EntityCollection {
case 'lock': { case 'lock': {
return LockEntity(rawEntityData); return LockEntity(rawEntityData);
} }
case "automation": case "automation": {
return AutomationEntity(rawEntityData);
}
case "input_boolean": case "input_boolean":
case "switch": { case "switch": {
return SwitchEntity(rawEntityData); return SwitchEntity(rawEntityData);
@ -83,6 +86,12 @@ class EntityCollection {
case "fan": { case "fan": {
return FanEntity(rawEntityData); return FanEntity(rawEntityData);
} }
/*case "camera": {
return CameraEntity(rawEntityData);
}*/
case "alarm_control_panel": {
return AlarmControlPanelEntity(rawEntityData);
}
default: { default: {
return Entity(rawEntityData); return Entity(rawEntityData);
} }

View File

@ -0,0 +1,40 @@
part of '../../main.dart';
class FlatServiceButton extends StatelessWidget {
final String serviceDomain;
final String serviceName;
final String text;
final double fontSize;
FlatServiceButton({
Key key,
this.serviceDomain,
this.serviceName: "turn_on",
@required this.text,
this.fontSize: Sizes.stateFontSize
}) : super(key: key);
void _setNewState(Entity entity) {
eventBus.fire(new ServiceCallEvent(serviceDomain ?? entity.domain, serviceName, entity.entityId, null));
}
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
return SizedBox(
height: fontSize*2.5,
child: FlatButton(
onPressed: (() {
_setNewState(entityModel.entityWrapper.entity);
}),
child: Text(
text,
textAlign: TextAlign.right,
style:
new TextStyle(fontSize: fontSize, color: Colors.blue),
),
)
);
}
}

View File

@ -0,0 +1,244 @@
part of '../../main.dart';
class AlarmControlPanelControlsWidget extends StatefulWidget {
final bool extended;
final List states;
const AlarmControlPanelControlsWidget({Key key, @required this.extended, this.states: const ["arm_home", "arm_away"]}) : super(key: key);
@override
_AlarmControlPanelControlsWidgetWidgetState createState() => _AlarmControlPanelControlsWidgetWidgetState();
}
class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPanelControlsWidget> {
String code = "";
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 (widget.states.contains("arm_home")) {
buttons.add(
RaisedButton(
onPressed: () => _callService(entity, "alarm_arm_home"),
child: Text("ARM HOME"),
)
);
}
if (widget.states.contains("arm_away")) {
buttons.add(
RaisedButton(
onPressed: () => _callService(entity, "alarm_arm_away"),
child: Text("ARM AWAY"),
)
);
}
if (widget.extended) {
if (widget.states.contains("arm_night")) {
buttons.add(
RaisedButton(
onPressed: () => _callService(entity, "alarm_arm_night"),
child: Text("ARM NIGHT"),
)
);
}
if (widget.states.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 = 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 = 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
]
);
}
}

View File

@ -0,0 +1,134 @@
part of '../../main.dart';
class CameraControlsWidget extends StatefulWidget {
final String url;
CameraControlsWidget({Key key, @required this.url}) : super(key: key);
@override
_CameraControlsWidgetState createState() => _CameraControlsWidgetState();
}
class _CameraControlsWidgetState extends State<CameraControlsWidget> {
@override
void initState() {
super.initState();
Logger.d("Camera source: ${widget.url}");
_getData();
}
http.Client client;
http.StreamedResponse response;
List<int> binaryImage = [];
void _getData() async {
client = new http.Client(); // create a client to make api calls
http.Request request = new http.Request("GET", Uri.parse(widget.url)); // create get request
//Log.d
Logger.d("==Sending");
response = await client.send(request); // sends request and waits for response stream
Logger.d("==Reading");
int byteCount = 0;
Logger.d("==${response.headers}");
List<int> primaryBuffer=[];
List<int> secondaryBuffer=[];
int imageStart = 0;
int imageEnd = 0;
response.stream.transform(
StreamTransformer.fromHandlers(
handleData: (data, sink) {
primaryBuffer.addAll(data);
Logger.d("== data recived: ${data.length}");
Logger.d("== primary buffer size: ${primaryBuffer.length}");
//Logger.d("${data.toString()}");
for (int i = 0; i < primaryBuffer.length - 15; i++) {
String startBoundary = utf8.decode(primaryBuffer.sublist(i, i+15),allowMalformed: true);
if (startBoundary == "--frameboundary") {
Logger.d("== START found at $i");
imageStart = i;
//secondaryBuffer.addAll(primaryBuffer.sublist(i));
//Logger.d("== secondary.length=${secondaryBuffer.length}. clearinig primary");
//primaryBuffer.clear();
break;
}
/*String startBoundary = utf8.decode(primaryBuffer.sublist(i, i+4),allowMalformed: true);
if (startBoundary == "\r\n\r\n") {
Logger.d("==Binary image start found ($i). primary.length=${primaryBuffer.length}");
secondaryBuffer.addAll(primaryBuffer.sublist(i+5));
Logger.d("==secondary.length=${secondaryBuffer.length}. clearinig primary");
primaryBuffer.clear();
Logger.d("==secondary.length=${secondaryBuffer.length}");
for (int j = 0; j < secondaryBuffer.length - 15; j++) {
String endBoundary = utf8.decode(secondaryBuffer.sublist(j, j+15),allowMalformed: true);
if (endBoundary == "--frameboundary") {
Logger.d("==Binary image end found");
sink.add(secondaryBuffer.sublist(0, j-1));
primaryBuffer.addAll(secondaryBuffer.sublist(j));
secondaryBuffer.clear();
break;
}
}
break;
}*/
}
for (int i = imageStart+15; i < primaryBuffer.length - 15; i++) {
String endBoundary = utf8.decode(primaryBuffer.sublist(i, i+15),allowMalformed: true);
if (endBoundary == "--frameboundary") {
Logger.d("==END found");
imageEnd = i;
sink.add(primaryBuffer.sublist(imageStart, imageEnd - 1));
primaryBuffer = primaryBuffer.sublist(imageEnd);
break;
}
}
//byteCount += data.length;
//Logger.d("$byteCount");
},
handleError: (error, stack, sink) {},
handleDone: (sink) {
sink.close();
},
)
).listen((d) {
setState(() {
binaryImage = d;
});
//Logger.d("==binary imagesize=${d.length}");
});
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Image.memory(Uint8List.fromList(binaryImage)),
FlatButton(
child: Text("VIEW"),
onPressed: () {
setState(() {
});
},
)
],
);
return Image.network("${widget.url}");
return FlatButton(
child: Text("VIEW"),
onPressed: () {
HAUtils.launchURL(widget.url);
},
);
}
@override
void dispose() {
client.close();
super.dispose();
}
}

View File

@ -13,6 +13,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
bool _showPending = false; bool _showPending = false;
bool _changedHere = false; bool _changedHere = false;
Timer _resetTimer; Timer _resetTimer;
Timer _tempThrottleTimer;
Timer _targetTempThrottleTimer;
double _tmpTemperature = 0.0; double _tmpTemperature = 0.0;
double _tmpTargetLow = 0.0; double _tmpTargetLow = 0.0;
double _tmpTargetHigh = 0.0; double _tmpTargetHigh = 0.0;
@ -71,21 +73,37 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
} }
void _setTemperature(ClimateEntity entity) { void _setTemperature(ClimateEntity entity) {
if (_tempThrottleTimer!=null) {
_tempThrottleTimer.cancel();
}
setState(() { setState(() {
_tmpTemperature = double.parse(_tmpTemperature.toStringAsFixed(1));
_changedHere = true; _changedHere = true;
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"temperature": "${_tmpTemperature.toStringAsFixed(1)}"})); _tmpTemperature = double.parse(_tmpTemperature.toStringAsFixed(1));
_resetStateTimer(entity); });
_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) { void _setTargetTemp(ClimateEntity entity) {
if (_targetTempThrottleTimer!=null) {
_targetTempThrottleTimer.cancel();
}
setState(() { setState(() {
_changedHere = true;
_tmpTargetLow = double.parse(_tmpTargetLow.toStringAsFixed(1)); _tmpTargetLow = double.parse(_tmpTargetLow.toStringAsFixed(1));
_tmpTargetHigh = double.parse(_tmpTargetHigh.toStringAsFixed(1)); _tmpTargetHigh = double.parse(_tmpTargetHigh.toStringAsFixed(1));
_changedHere = true; });
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"target_temp_high": "${_tmpTargetHigh.toStringAsFixed(1)}", "target_temp_low": "${_tmpTargetLow.toStringAsFixed(1)}"})); _targetTempThrottleTimer = Timer(Duration(seconds: 2), () {
_resetStateTimer(entity); 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);
});
}); });
} }
@ -167,7 +185,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
final entityModel = EntityModel.of(context); final entityModel = EntityModel.of(context);
final ClimateEntity entity = entityModel.entityWrapper.entity; final ClimateEntity entity = entityModel.entityWrapper.entity;
if (_changedHere) { if (_changedHere) {
_showPending = (_tmpTemperature != entity.temperature); _showPending = (_tmpTemperature != entity.temperature || _tmpTargetHigh != entity.targetHigh || _tmpTargetLow != entity.targetLow);
_changedHere = false; _changedHere = false;
} else { } else {
_resetTimer?.cancel(); _resetTimer?.cancel();
@ -278,10 +296,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
TemperatureControlWidget( TemperatureControlWidget(
value: _tmpTemperature, value: _tmpTemperature,
fontColor: _showPending ? Colors.red : Colors.black, fontColor: _showPending ? Colors.red : Colors.black,
onLargeDec: () => _temperatureDown(entity, 0.5), onDec: () => _temperatureDown(entity, 0.5),
onLargeInc: () => _temperatureUp(entity, 0.5), onInc: () => _temperatureUp(entity, 0.5),
onSmallDec: () => _temperatureDown(entity, 0.1),
onSmallInc: () => _temperatureUp(entity, 0.1),
) )
], ],
); );
@ -297,10 +313,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
TemperatureControlWidget( TemperatureControlWidget(
value: _tmpTargetLow, value: _tmpTargetLow,
fontColor: _showPending ? Colors.red : Colors.black, fontColor: _showPending ? Colors.red : Colors.black,
onLargeDec: () => _targetLowDown(entity, 0.5), onDec: () => _targetLowDown(entity, 0.5),
onLargeInc: () => _targetLowUp(entity, 0.5), onInc: () => _targetLowUp(entity, 0.5),
onSmallDec: () => _targetLowDown(entity, 0.1),
onSmallInc: () => _targetLowUp(entity, 0.1),
), ),
Expanded( Expanded(
child: Container(height: 10.0), child: Container(height: 10.0),
@ -312,10 +326,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
TemperatureControlWidget( TemperatureControlWidget(
value: _tmpTargetHigh, value: _tmpTargetHigh,
fontColor: _showPending ? Colors.red : Colors.black, fontColor: _showPending ? Colors.red : Colors.black,
onLargeDec: () => _targetHighDown(entity, 0.5), onDec: () => _targetHighDown(entity, 0.5),
onLargeInc: () => _targetHighUp(entity, 0.5), onInc: () => _targetHighUp(entity, 0.5),
onSmallDec: () => _targetHighDown(entity, 0.1),
onSmallInc: () => _targetHighUp(entity, 0.1),
) )
); );
} }
@ -401,18 +413,14 @@ class TemperatureControlWidget extends StatelessWidget {
final double value; final double value;
final double fontSize; final double fontSize;
final Color fontColor; final Color fontColor;
final onSmallInc; final onInc;
final onLargeInc; final onDec;
final onSmallDec;
final onLargeDec;
TemperatureControlWidget( TemperatureControlWidget(
{Key key, {Key key,
@required this.value, @required this.value,
@required this.onSmallInc, @required this.onInc,
@required this.onSmallDec, @required this.onDec,
@required this.onLargeInc,
@required this.onLargeDec,
this.fontSize, this.fontSize,
this.fontColor}) this.fontColor})
: super(key: key); : super(key: key);
@ -435,29 +443,13 @@ class TemperatureControlWidget extends StatelessWidget {
icon: Icon(MaterialDesignIcons.createIconDataFromIconName( icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
'mdi:chevron-up')), 'mdi:chevron-up')),
iconSize: 30.0, iconSize: 30.0,
onPressed: () => onSmallInc(), onPressed: () => onInc(),
), ),
IconButton( IconButton(
icon: Icon(MaterialDesignIcons.createIconDataFromIconName( icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
'mdi:chevron-down')), 'mdi:chevron-down')),
iconSize: 30.0, iconSize: 30.0,
onPressed: () => onSmallDec(), onPressed: () => onDec(),
)
],
),
Column(
children: <Widget>[
IconButton(
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
'mdi:chevron-double-up')),
iconSize: 30.0,
onPressed: () => onLargeInc(),
),
IconButton(
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
'mdi:chevron-double-down')),
iconSize: 30.0,
onPressed: () => onLargeDec(),
) )
], ],
) )

View File

@ -10,16 +10,16 @@ class LightControlsWidget extends StatefulWidget {
class _LightControlsWidgetState extends State<LightControlsWidget> { class _LightControlsWidgetState extends State<LightControlsWidget> {
int _tmpBrightness; int _tmpBrightness;
int _tmpColorTemp; int _tmpColorTemp = 0;
Color _tmpColor; Color _tmpColor = Colors.white;
bool _changedHere = false; bool _changedHere = false;
String _tmpEffect; String _tmpEffect;
void _resetState(LightEntity entity) { void _resetState(LightEntity entity) {
_tmpBrightness = entity.brightness ?? 0; _tmpBrightness = entity.brightness ?? 0;
_tmpColorTemp = entity.colorTemp; _tmpColorTemp = entity.colorTemp ?? entity.minMireds?.toInt();
_tmpColor = entity.color; _tmpColor = entity.color ?? _tmpColor;
_tmpEffect = null; _tmpEffect = entity.effect;
} }
void _setBrightness(LightEntity entity, double value) { void _setBrightness(LightEntity entity, double value) {
@ -119,7 +119,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
} }
Widget _buildColorTempControl(LightEntity entity) { Widget _buildColorTempControl(LightEntity entity) {
if ((entity.supportColorTemp) && (_tmpColorTemp != null)) { if (entity.supportColorTemp) {
return UniversalSlider( return UniversalSlider(
title: "Color temperature", title: "Color temperature",
leading: Text("Cold", style: TextStyle(color: Colors.lightBlue),), leading: Text("Cold", style: TextStyle(color: Colors.lightBlue),),
@ -141,7 +141,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
} }
Widget _buildColorControl(LightEntity entity) { Widget _buildColorControl(LightEntity entity) {
if ((entity.supportColor) && (entity.color != null)) { if (entity.supportColor) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[

View File

@ -23,6 +23,15 @@ class EntityColor {
"cool": Colors.lightBlue, "cool": Colors.lightBlue,
EntityState.unavailable: Colors.black26, EntityState.unavailable: Colors.black26,
EntityState.unknown: Colors.black26, EntityState.unknown: Colors.black26,
EntityState.alarm_disarmed: Colors.green,
EntityState.alarm_armed_away: Colors.redAccent,
EntityState.alarm_armed_custom_bypass: Colors.redAccent,
EntityState.alarm_armed_home: Colors.redAccent,
EntityState.alarm_armed_night: Colors.redAccent,
EntityState.alarm_triggered: Colors.redAccent,
EntityState.alarm_arming: Colors.amber,
EntityState.alarm_disarming: Colors.amber,
EntityState.alarm_pending: Colors.amber,
}; };
static Color stateColor(String state) { static Color stateColor(String state) {

View File

@ -1,27 +0,0 @@
part of '../../main.dart';
class ButtonStateWidget extends StatelessWidget {
void _setNewState(Entity entity) {
eventBus.fire(new ServiceCallEvent(entity.domain, "turn_on", entity.entityId, null));
}
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
return SizedBox(
height: 34.0,
child: FlatButton(
onPressed: (() {
_setNewState(entityModel.entityWrapper.entity);
}),
child: Text(
"EXECUTE",
textAlign: TextAlign.right,
style:
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
),
)
);
}
}

View File

@ -3,7 +3,6 @@ part of 'main.dart';
class HomeAssistant { class HomeAssistant {
String _webSocketAPIEndpoint; String _webSocketAPIEndpoint;
String _password; String _password;
String _authType;
bool _useLovelace = false; bool _useLovelace = false;
IOWebSocketChannel _hassioChannel; IOWebSocketChannel _hassioChannel;
@ -56,10 +55,9 @@ class HomeAssistant {
_messageQueue = SendMessageQueue(messageExpirationTime); _messageQueue = SendMessageQueue(messageExpirationTime);
} }
void updateSettings(String url, String password, String authType, bool useLovelace) { void updateSettings(String url, String password, bool useLovelace) {
_webSocketAPIEndpoint = url; _webSocketAPIEndpoint = url;
_password = password; _password = password;
_authType = authType;
_useLovelace = useLovelace; _useLovelace = useLovelace;
Logger.d( "Use lovelace is $_useLovelace"); Logger.d( "Use lovelace is $_useLovelace");
} }
@ -213,7 +211,7 @@ class HomeAssistant {
_handleMessage(String message) { _handleMessage(String message) {
var data = json.decode(message); var data = json.decode(message);
if (data["type"] == "auth_required") { if (data["type"] == "auth_required") {
_sendAuthMessageRaw('{"type": "auth","$_authType": "$_password"}'); _sendAuthMessageRaw('{"type": "auth","access_token": "$_password"}');
} else if (data["type"] == "auth_ok") { } else if (data["type"] == "auth_ok") {
_completeConnecting(null); _completeConnecting(null);
_sendSubscribe(); _sendSubscribe();
@ -419,6 +417,16 @@ class HomeAssistant {
name: rawView['title'], name: rawView['title'],
iconName: rawView['icon'] iconName: rawView['icon']
); );
if (rawView['badges'] != null && rawView['badges'] is List) {
rawView['badges'].forEach((entity) {
if (entities.isExist(entity)) {
Entity e = entities.get(entity);
view.badges.add(e);
}
});
}
view.cards.addAll(_createLovelaceCards(rawView["cards"] ?? [])); view.cards.addAll(_createLovelaceCards(rawView["cards"] ?? []));
ui.views.add( ui.views.add(
view view
@ -430,63 +438,78 @@ class HomeAssistant {
List<HACard> _createLovelaceCards(List rawCards) { List<HACard> _createLovelaceCards(List rawCards) {
List<HACard> result = []; List<HACard> result = [];
rawCards.forEach((rawCard){ rawCards.forEach((rawCard){
bool isThereCardOptionsInside = rawCard["card"] != null; try {
HACard card = HACard( bool isThereCardOptionsInside = rawCard["card"] != null;
id: "card", HACard card = HACard(
name: isThereCardOptionsInside ? rawCard["card"]["title"] ?? rawCard["card"]["name"] : rawCard["title"] ?? rawCard["name"], id: "card",
type: isThereCardOptionsInside ? rawCard["card"]['type'] : rawCard['type'], name: isThereCardOptionsInside ? rawCard["card"]["title"] ??
columnsCount: isThereCardOptionsInside ? rawCard["card"]['columns'] ?? 4 : rawCard['columns'] ?? 4, rawCard["card"]["name"] : rawCard["title"] ?? rawCard["name"],
showName: isThereCardOptionsInside ? rawCard["card"]['show_name'] ?? true : rawCard['show_name'] ?? true, type: isThereCardOptionsInside
showState: isThereCardOptionsInside ? rawCard["card"]['show_state'] ?? true : rawCard['show_state'] ?? true, ? rawCard["card"]['type']
showEmpty: rawCard['show_empty'] ?? true, : rawCard['type'],
stateFilter: rawCard['state_filter'] ?? [] columnsCount: isThereCardOptionsInside
); ? rawCard["card"]['columns'] ?? 4
if (rawCard["cards"] != null) { : rawCard['columns'] ?? 4,
card.childCards = _createLovelaceCards(rawCard["cards"]); showName: isThereCardOptionsInside ? rawCard["card"]['show_name'] ??
} true : rawCard['show_name'] ?? true,
rawCard["entities"]?.forEach((rawEntity) { showState: isThereCardOptionsInside
if (rawEntity is String) { ? rawCard["card"]['show_state'] ?? true
if (entities.isExist(rawEntity)) { : rawCard['show_state'] ?? true,
card.entities.add(EntityWrapper(entity: entities.get(rawEntity))); showEmpty: rawCard['show_empty'] ?? true,
stateFilter: rawCard['state_filter'] ?? [],
states: rawCard['states'],
content: rawCard['content']
);
if (rawCard["cards"] != null) {
card.childCards = _createLovelaceCards(rawCard["cards"]);
}
rawCard["entities"]?.forEach((rawEntity) {
if (rawEntity is String) {
if (entities.isExist(rawEntity)) {
card.entities.add(EntityWrapper(entity: entities.get(rawEntity)));
}
} else {
if (entities.isExist(rawEntity["entity"])) {
Entity e = entities.get(rawEntity["entity"]);
card.entities.add(
EntityWrapper(
entity: e,
displayName: rawEntity["name"],
icon: rawEntity["icon"],
uiAction: EntityUIAction(rawEntityData: rawEntity)
)
);
}
} }
} else { });
if (entities.isExist(rawEntity["entity"])) { if (rawCard["entity"] != null) {
Entity e = entities.get(rawEntity["entity"]); var en = rawCard["entity"];
card.entities.add( if (en is String) {
EntityWrapper( if (entities.isExist(en)) {
entity: e, Entity e = entities.get(en);
displayName: rawEntity["name"], card.linkedEntityWrapper = EntityWrapper(
icon: rawEntity["icon"], entity: e,
uiAction: EntityUIAction(rawEntityData: rawEntity) icon: rawCard["icon"],
) displayName: rawCard["name"],
); uiAction: EntityUIAction(rawEntityData: rawCard)
);
}
} else {
if (entities.isExist(en["entity"])) {
Entity e = entities.get(en["entity"]);
card.linkedEntityWrapper = EntityWrapper(
entity: e,
icon: en["icon"],
displayName: en["name"],
uiAction: EntityUIAction(rawEntityData: rawCard)
);
}
} }
} }
}); result.add(card);
if (rawCard["entity"] != null) { } catch (e) {
var en = rawCard["entity"]; Logger.e("There was an error parsing card: ${e.toString()}");
if (en is String) {
if (entities.isExist(en)) {
Entity e = entities.get(en);
card.linkedEntityWrapper = EntityWrapper(
entity: e,
uiAction: EntityUIAction(rawEntityData: rawCard)
);
}
} else {
if (entities.isExist(en["entity"])) {
Entity e = entities.get(en["entity"]);
card.linkedEntityWrapper = EntityWrapper(
entity: e,
icon: en["icon"],
displayName: en["name"],
uiAction: EntityUIAction(rawEntityData: rawCard)
);
}
}
} }
result.add(card);
}); });
return result; return result;
} }
@ -547,17 +570,10 @@ class HomeAssistant {
String url = "$homeAssistantWebHost/api/history/period/$startTime?&filter_entity_id=$entityId"; String url = "$homeAssistantWebHost/api/history/period/$startTime?&filter_entity_id=$entityId";
Logger.d("[Sending] ==> $url"); Logger.d("[Sending] ==> $url");
http.Response historyResponse; http.Response historyResponse;
if (_authType == "access_token") { historyResponse = await http.get(url, headers: {
historyResponse = await http.get(url, headers: {
"authorization": "Bearer $_password", "authorization": "Bearer $_password",
"Content-Type": "application/json" "Content-Type": "application/json"
}); });
} else {
historyResponse = await http.get(url, headers: {
"X-HA-Access": "$_password",
"Content-Type": "application/json"
});
}
var history = json.decode(historyResponse.body); var history = json.decode(historyResponse.body);
if (history is List) { if (history is List) {
Logger.d( "[Received] <== ${history.first.length} history recors"); Logger.d( "[Received] <== ${history.first.length} history recors");

View File

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -14,6 +15,7 @@ import 'package:http/http.dart' as http;
import 'package:flutter_colorpicker/material_picker.dart'; import 'package:flutter_colorpicker/material_picker.dart';
import 'package:charts_flutter/flutter.dart' as charts; import 'package:charts_flutter/flutter.dart' as charts;
import 'package:progress_indicators/progress_indicators.dart'; import 'package:progress_indicators/progress_indicators.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
part 'entity_class/const.dart'; part 'entity_class/const.dart';
part 'entity_class/entity.class.dart'; part 'entity_class/entity.class.dart';
@ -32,6 +34,9 @@ part 'entity_class/media_player_entity.class.dart';
part 'entity_class/lock_entity.class.dart'; part 'entity_class/lock_entity.class.dart';
part 'entity_class/group_entity.class.dart'; part 'entity_class/group_entity.class.dart';
part 'entity_class/fan_entity.class.dart'; part 'entity_class/fan_entity.class.dart';
part 'entity_class/automation_entity.dart';
part 'entity_class/camera_entity.class.dart';
part 'entity_class/alarm_control_panel.class.dart';
part 'entity_widgets/common/badge.dart'; part 'entity_widgets/common/badge.dart';
part 'entity_widgets/model_widgets.dart'; part 'entity_widgets/model_widgets.dart';
part 'entity_widgets/default_entity_container.dart'; part 'entity_widgets/default_entity_container.dart';
@ -44,6 +49,7 @@ part 'entity_widgets/common/last_updated.dart';
part 'entity_widgets/common/mode_swicth.dart'; part 'entity_widgets/common/mode_swicth.dart';
part 'entity_widgets/common/mode_selector.dart'; part 'entity_widgets/common/mode_selector.dart';
part 'entity_widgets/common/universal_slider.dart'; part 'entity_widgets/common/universal_slider.dart';
part 'entity_widgets/common/flat_service_button.dart';
part 'entity_widgets/entity_colors.class.dart'; part 'entity_widgets/entity_colors.class.dart';
part 'entity_widgets/entity_page_container.dart'; part 'entity_widgets/entity_page_container.dart';
part 'entity_widgets/history_chart/entity_history.dart'; part 'entity_widgets/history_chart/entity_history.dart';
@ -60,13 +66,14 @@ part 'entity_widgets/state/simple_state.dart';
part 'entity_widgets/state/climate_state.dart'; part 'entity_widgets/state/climate_state.dart';
part 'entity_widgets/state/cover_state.dart'; part 'entity_widgets/state/cover_state.dart';
part 'entity_widgets/state/date_time_state.dart'; part 'entity_widgets/state/date_time_state.dart';
part 'entity_widgets/state/button_state.dart';
part 'entity_widgets/state/lock_state.dart'; part 'entity_widgets/state/lock_state.dart';
part 'entity_widgets/controls/climate_controls.dart'; part 'entity_widgets/controls/climate_controls.dart';
part 'entity_widgets/controls/cover_controls.dart'; part 'entity_widgets/controls/cover_controls.dart';
part 'entity_widgets/controls/light_controls.dart'; part 'entity_widgets/controls/light_controls.dart';
part 'entity_widgets/controls/media_player_widgets.dart'; part 'entity_widgets/controls/media_player_widgets.dart';
part 'entity_widgets/controls/fan_controls.dart'; part 'entity_widgets/controls/fan_controls.dart';
part 'entity_widgets/controls/alarm_control_panel_controls.dart';
part 'entity_widgets/controls/camera_controls.dart';
part 'settings.page.dart'; part 'settings.page.dart';
part 'home_assistant.class.dart'; part 'home_assistant.class.dart';
part 'log.page.dart'; part 'log.page.dart';
@ -85,7 +92,7 @@ part 'ui_widgets/card_header_widget.dart';
EventBus eventBus = new EventBus(); EventBus eventBus = new EventBus();
const String appName = "HA Client"; const String appName = "HA Client";
const appVersion = "0.3.13"; const appVersion = "0.3.14";
String homeAssistantWebHost; String homeAssistantWebHost;
@ -141,7 +148,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
//Map _instanceConfig; //Map _instanceConfig;
String _webSocketApiEndpoint; String _webSocketApiEndpoint;
String _password; String _password;
String _authType;
//int _uiViewsCount = 0; //int _uiViewsCount = 0;
String _instanceHost; String _instanceHost;
StreamSubscription _stateSubscription; StreamSubscription _stateSubscription;
@ -199,8 +205,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_webSocketApiEndpoint = "${prefs.getString('hassio-protocol')}://$domain:$port/api/websocket"; _webSocketApiEndpoint = "${prefs.getString('hassio-protocol')}://$domain:$port/api/websocket";
homeAssistantWebHost = "${prefs.getString('hassio-res-protocol')}://$domain:$port"; homeAssistantWebHost = "${prefs.getString('hassio-res-protocol')}://$domain:$port";
_password = prefs.getString('hassio-password'); _password = prefs.getString('hassio-password');
_authType = prefs.getString('hassio-auth-type'); _useLovelaceUI = prefs.getBool('use-lovelace') ?? true;
_useLovelaceUI = prefs.getBool('use-lovelace') ?? false;
if ((domain == null) || (port == null) || (_password == null) || if ((domain == null) || (port == null) || (_password == null) ||
(domain.length == 0) || (port.length == 0) || (_password.length == 0)) { (domain.length == 0) || (port.length == 0) || (_password.length == 0)) {
throw("Check connection settings"); throw("Check connection settings");
@ -249,7 +254,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
} }
_refreshData() async { _refreshData() async {
_homeAssistant.updateSettings(_webSocketApiEndpoint, _password, _authType, _useLovelaceUI); _homeAssistant.updateSettings(_webSocketApiEndpoint, _password, _useLovelaceUI);
_hideBottomBar(); _hideBottomBar();
_showInfoBottomBar(progress: true,); _showInfoBottomBar(progress: true,);
await _homeAssistant.fetch().then((result) { await _homeAssistant.fetch().then((result) {
@ -362,10 +367,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
GestureDetector( GestureDetector(
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
HAUtils.launchURL("http://www.keyboardcrumbs.io/"); HAUtils.launchURL("http://www.vynn.co/");
}, },
child: Text( child: Text(
"www.keyboardcrumbs.io", "www.vynn.co",
style: TextStyle( style: TextStyle(
color: Colors.blue, color: Colors.blue,
decoration: TextDecoration.underline decoration: TextDecoration.underline

View File

@ -24,7 +24,14 @@ class MaterialDesignIcons {
"cover.opening": "mdi:window-open", "cover.opening": "mdi:window-open",
"lock.locked": "mdi:lock", "lock.locked": "mdi:lock",
"lock.unlocked": "mdi:lock-open", "lock.unlocked": "mdi:lock-open",
"fan": "mdi:fan" "fan": "mdi:fan",
"alarm_control_panel.disarmed" : "mdi:bell-outline",
"alarm_control_panel.armed_home" : "mdi:bell-plus",
"alarm_control_panel.armed_away" : "mdi:bell",
"alarm_control_panel.armed_night" : "mdi:bell-sleep",
"alarm_control_panel.armed_custom_bypass" : "mdi:bell",
"alarm_control_panel.triggered" : "mdi:bell-ring",
"alarm_control_panel" : "mdi:bell"
}; };
static Map _defaultIconsByDeviceClass = { static Map _defaultIconsByDeviceClass = {

View File

@ -18,10 +18,8 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
String _newHassioPassword = ""; String _newHassioPassword = "";
String _socketProtocol = "wss"; String _socketProtocol = "wss";
String _newSocketProtocol = "wss"; String _newSocketProtocol = "wss";
String _authType = "access_token"; bool _useLovelace = true;
String _newAuthType = "access_token"; bool _newUseLovelace = true;
bool _useLovelace = false;
bool _newUseLovelace = false;
@override @override
void initState() { void initState() {
@ -37,11 +35,10 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
_hassioPort = _newHassioPort = prefs.getString("hassio-port") ?? ""; _hassioPort = _newHassioPort = prefs.getString("hassio-port") ?? "";
_hassioPassword = _newHassioPassword = prefs.getString("hassio-password") ?? ""; _hassioPassword = _newHassioPassword = prefs.getString("hassio-password") ?? "";
_socketProtocol = _newSocketProtocol = prefs.getString("hassio-protocol") ?? 'wss'; _socketProtocol = _newSocketProtocol = prefs.getString("hassio-protocol") ?? 'wss';
_authType = _newAuthType = prefs.getString("hassio-auth-type") ?? 'access_token';
try { try {
_useLovelace = _newUseLovelace = prefs.getBool("use-lovelace") ?? false; _useLovelace = _newUseLovelace = prefs.getBool("use-lovelace") ?? true;
} catch (e) { } catch (e) {
_useLovelace = _newUseLovelace = false; _useLovelace = _newUseLovelace = true;
} }
}); });
} }
@ -51,7 +48,6 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
(_newHassioPort != _hassioPort) || (_newHassioPort != _hassioPort) ||
(_newHassioDomain != _hassioDomain) || (_newHassioDomain != _hassioDomain) ||
(_newSocketProtocol != _socketProtocol) || (_newSocketProtocol != _socketProtocol) ||
(_newAuthType != _authType) ||
(_newUseLovelace != _useLovelace)); (_newUseLovelace != _useLovelace));
} }
@ -66,7 +62,6 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
prefs.setString("hassio-password", _newHassioPassword); prefs.setString("hassio-password", _newHassioPassword);
prefs.setString("hassio-protocol", _newSocketProtocol); prefs.setString("hassio-protocol", _newSocketProtocol);
prefs.setString("hassio-res-protocol", _newSocketProtocol == "wss" ? "https" : "http"); prefs.setString("hassio-res-protocol", _newSocketProtocol == "wss" ? "https" : "http");
prefs.setString("hassio-auth-type", _newAuthType);
prefs.setBool("use-lovelace", _newUseLovelace); prefs.setBool("use-lovelace", _newUseLovelace);
} }
@ -154,33 +149,9 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
"Try ports 80 and 443 if default is not working and you don't know why.", "Try ports 80 and 443 if default is not working and you don't know why.",
style: TextStyle(color: Colors.grey), style: TextStyle(color: Colors.grey),
), ),
new Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Text(
"Login with access token (HA >= 0.78.0)",
softWrap: true,
maxLines: 2,
),
),
Switch(
value: (_newAuthType == "access_token"),
onChanged: (value) {
setState(() {
_newAuthType = value ? "access_token" : "api_password";
});
},
)
],
),
new Text(
"You should use access token for HA >= 0.84.1. Legacy password will not work there.",
style: TextStyle(color: Colors.grey),
),
new TextField( new TextField(
decoration: InputDecoration( decoration: InputDecoration(
labelText: _newAuthType == "access_token" ? "Access token" : "API password" labelText: "Access token"
), ),
controller: new TextEditingController.fromValue( controller: new TextEditingController.fromValue(
new TextEditingValue( new TextEditingValue(

View File

@ -12,6 +12,8 @@ class HACard {
bool showEmpty; bool showEmpty;
int columnsCount; int columnsCount;
List stateFilter; List stateFilter;
List states;
String content;
HACard({ HACard({
this.name, this.name,
@ -22,6 +24,8 @@ class HACard {
this.showState: true, this.showState: true,
this.stateFilter: const [], this.stateFilter: const [],
this.showEmpty: true, this.showEmpty: true,
this.content,
this.states,
@required this.type @required this.type
}); });

View File

@ -11,6 +11,7 @@ class Sizes {
static const nameFontSize = 15.0; static const nameFontSize = 15.0;
static const smallFontSize = 14.0; static const smallFontSize = 14.0;
static const largeFontSize = 24.0; static const largeFontSize = 24.0;
static const mediumFontSize = 21.0;
static const inputWidth = 160.0; static const inputWidth = 160.0;
static const rowPadding = 10.0; static const rowPadding = 10.0;
} }

View File

@ -3,18 +3,22 @@ part of '../main.dart';
class CardHeaderWidget extends StatelessWidget { class CardHeaderWidget extends StatelessWidget {
final String name; final String name;
final Widget trailing;
final Widget subtitle;
const CardHeaderWidget({Key key, this.name}) : super(key: key); const CardHeaderWidget({Key key, this.name, this.trailing, this.subtitle}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var result; var result;
if ((name != null) && (name.trim().length > 0)) { if ((name != null) && (name.trim().length > 0)) {
result = new ListTile( result = new ListTile(
trailing: trailing,
subtitle: subtitle,
title: Text("$name", title: Text("$name",
textAlign: TextAlign.left, textAlign: TextAlign.left,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: Sizes.largeFontSize)), style: new TextStyle(fontSize: Sizes.mediumFontSize)),
); );
} else { } else {
result = new Container(width: 0.0, height: 0.0); result = new Container(width: 0.0, height: 0.0);

View File

@ -33,6 +33,14 @@ class CardWidget extends StatelessWidget {
return _buildEntityButtonCard(context); return _buildEntityButtonCard(context);
} }
case CardType.markdown: {
return _buildMarkdownCard(context);
}
case CardType.alarmPanel: {
return _buildAlarmPanelCard(context);
}
case CardType.horizontalStack: { case CardType.horizontalStack: {
if (card.childCards.isNotEmpty) { if (card.childCards.isNotEmpty) {
List<Widget> children = []; List<Widget> children = [];
@ -109,6 +117,73 @@ class CardWidget extends StatelessWidget {
); );
} }
Widget _buildMarkdownCard(BuildContext context) {
if (card.content == null) {
return Container(height: 0.0, width: 0.0,);
}
List<Widget> body = [];
body.add(CardHeaderWidget(name: card.name));
body.add(MarkdownBody(data: card.content));
return Card(
child: Padding(
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
child: new Column(mainAxisSize: MainAxisSize.min, children: body),
)
);
}
Widget _buildAlarmPanelCard(BuildContext context) {
if (card.linkedEntityWrapper == null || card.linkedEntityWrapper.entity == null) {
return Container(width: 0, height: 0,);
} else {
List<Widget> body = [];
body.add(CardHeaderWidget(
name: card.name ?? "",
subtitle: Text("${card.linkedEntityWrapper.entity.displayState}",
style: TextStyle(
color: Colors.grey
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
EntityIcon(
iconSize: 50.0,
),
Container(
width: 26.0,
child: IconButton(
padding: EdgeInsets.all(0.0),
alignment: Alignment.centerRight,
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
"mdi:dots-vertical")),
onPressed: () => eventBus.fire(new ShowEntityPageEvent(card.linkedEntityWrapper.entity))
)
)
]
),
));
body.add(
AlarmControlPanelControlsWidget(
extended: true,
states: card.states,
)
);
return Card(
child: EntityModel(
entityWrapper: card.linkedEntityWrapper,
handleTap: null,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: body
)
)
);
}
}
Widget _buildGlanceCard(BuildContext context) { Widget _buildGlanceCard(BuildContext context) {
List<EntityWrapper> entitiesToShow = card.getEntitiesToShow(); List<EntityWrapper> entitiesToShow = card.getEntitiesToShow();
if (entitiesToShow.isEmpty && !card.showEmpty) { if (entitiesToShow.isEmpty && !card.showEmpty) {

View File

@ -7,7 +7,7 @@ packages:
name: archive name: archive
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.4" version: "2.0.8"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -35,7 +35,7 @@ packages:
name: cached_network_image name: cached_network_image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.1" version: "0.6.0-alpha.2"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
@ -70,7 +70,7 @@ packages:
name: convert name: convert
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.2" version: "2.1.1"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@ -83,7 +83,7 @@ packages:
description: description:
path: "." path: "."
ref: HEAD ref: HEAD
resolved-ref: e26916e095244a7e5ea61315b030d298d127ed26 resolved-ref: a7ed88a4793e094a4d5d5c2d88a89e55510accde
url: "https://github.com/MarkOSullivan94/dart_config.git" url: "https://github.com/MarkOSullivan94/dart_config.git"
source: git source: git
version: "0.5.0" version: "0.5.0"
@ -93,7 +93,7 @@ packages:
name: date_format name: date_format
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.5" version: "1.0.6"
event_bus: event_bus:
dependency: "direct main" dependency: "direct main"
description: description:
@ -112,14 +112,14 @@ packages:
name: flutter_cache_manager name: flutter_cache_manager
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0+1" version: "0.3.0-alpha.2"
flutter_colorpicker: flutter_colorpicker:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_colorpicker name: flutter_colorpicker
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.0" version: "0.2.1"
flutter_launcher_icons: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -127,6 +127,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.7.0" version: "0.7.0"
flutter_markdown:
dependency: "direct main"
description:
name: flutter_markdown
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -138,7 +145,7 @@ packages:
name: http name: http
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.0" version: "0.12.0+1"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
@ -152,7 +159,7 @@ packages:
name: image name: image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.5" version: "2.0.6"
intl: intl:
dependency: transitive dependency: transitive
description: description:
@ -167,6 +174,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.11.3+2" version: "0.11.3+2"
markdown:
dependency: transitive
description:
name: markdown
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -201,7 +215,7 @@ packages:
name: petitparser name: petitparser
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.2" version: "2.1.1"
progress_indicators: progress_indicators:
dependency: "direct main" dependency: "direct main"
description: description:
@ -222,7 +236,7 @@ packages:
name: shared_preferences name: shared_preferences
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.3" version: "0.5.0"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -235,6 +249,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.4.1" version: "1.4.1"
sqflite:
dependency: transitive
description:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.2+1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -262,7 +283,7 @@ packages:
name: synchronized name: synchronized
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.5.3" version: "1.5.3+2"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -290,7 +311,7 @@ packages:
name: url_launcher name: url_launcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.2" version: "5.0.0"
uuid: uuid:
dependency: transitive dependency: transitive
description: description:
@ -318,7 +339,7 @@ packages:
name: xml name: xml
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.2.3" version: "3.3.1"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:

View File

@ -1,7 +1,7 @@
name: hass_client name: hass_client
description: Home Assistant Android Client description: Home Assistant Android Client
version: 0.3.13+80 version: 0.3.14+85
environment: environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0" sdk: ">=2.0.0-dev.68.0 <3.0.0"
@ -18,10 +18,7 @@ dependencies:
date_format: any date_format: any
flutter_colorpicker: any flutter_colorpicker: any
charts_flutter: any charts_flutter: any
flutter_markdown: any
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
#cupertino_icons: ^0.1.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: