diff --git a/lib/entity_class/button_entity.class.dart b/lib/entity_class/button_entity.class.dart index fd256c3..02327dc 100644 --- a/lib/entity_class/button_entity.class.dart +++ b/lib/entity_class/button_entity.class.dart @@ -17,7 +17,7 @@ class _ButtonEntityWidgetState extends _EntityWidgetState { "EXECUTE", textAlign: TextAlign.right, style: - new TextStyle(fontSize: Entity.STATE_FONT_SIZE, color: Colors.blue), + new TextStyle(fontSize: stateFontSize, color: Colors.blue), ), ); } diff --git a/lib/entity_class/climate_entity.class.dart b/lib/entity_class/climate_entity.class.dart new file mode 100644 index 0000000..796c590 --- /dev/null +++ b/lib/entity_class/climate_entity.class.dart @@ -0,0 +1,226 @@ +part of '../main.dart'; + +class _ClimateEntityWidgetState extends _EntityWidgetState { + + List _operationList = []; + double _temperature; + String _operationMode; + bool _awayMode; + bool _showPending; + bool _changedHere; + double _temperatureStep = 0.2; + Timer _resetTimer; + + @override + double widgetHeight = 38.0; + + @override + void initState() { + _operationList.clear(); + if (widget.entity.attributes["operation_list"] != null) { + widget.entity.attributes["operation_list"].forEach((value){ + _operationList.add(value.toString()); + }); + } + _resetVars(); + super.initState(); + } + + void _resetVars() { + _temperature = widget.entity.attributes['temperature']; + _operationMode = widget.entity.attributes['operation_mode']; + _awayMode = widget.entity.attributes['away_mode'] == "on"; + _showPending = false; + _changedHere = false; + } + + @override + Widget _buildSecondRowWidget() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + super._buildSecondRowWidget(), + _buildAdditionalControls() + ], + ); + } + + void _temperatureUp() { + _temperature += _temperatureStep; + _setTemperature(); + } + + void _temperatureDown() { + _temperature -= _temperatureStep; + _setTemperature(); + } + + void _setTemperature() { + setState(() { + _temperature = double.parse(_temperature.toStringAsFixed(1)); + _changedHere = true; + eventBus.fire(new ServiceCallEvent(widget.entity.domain, "set_temperature", widget.entity.entityId,{"temperature": "${_temperature.toStringAsFixed(1)}"})); + _resetStateTimer(); + }); + } + + void _setOperationMode(value) { + setState(() { + _operationMode = value; + _changedHere = true; + eventBus.fire(new ServiceCallEvent(widget.entity.domain, "set_operation_mode", widget.entity.entityId,{"operation_mode": "$_operationMode"})); + _resetStateTimer(); + }); + } + + void _setAwayMode(value) { + setState(() { + _awayMode = value; + _changedHere = true; + eventBus.fire(new ServiceCallEvent(widget.entity.domain, "set_away_mode", widget.entity.entityId,{"away_mode": "${_awayMode ? 'on' : 'off'}"})); + _resetStateTimer(); + }); + } + + void _resetStateTimer() { + if (_resetTimer!=null) { + _resetTimer.cancel(); + } + _resetTimer = Timer(Duration(seconds: 3), () { + setState(() {}); + _resetVars(); + }); + } + + _buildAdditionalControls() { + if (_changedHere) { + _showPending = (_temperature != widget.entity.attributes['temperature']); + _changedHere = false; + } else { + _resetTimer?.cancel(); + _resetVars(); + } + return Padding( + padding: EdgeInsets.fromLTRB(leftWidgetPadding, rowPadding, rightWidgetPadding, 0.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Target temperature for ${_operationMode != 'off' ? _operationMode : ''}", style: TextStyle( + fontSize: stateFontSize + )), + Row( + children: [ + Expanded( + child: Text( + "$_temperature", + style: TextStyle( + fontSize: largeFontSize, + color: _showPending ? Colors.red : Colors.black + ), + ), + ), + Column( + children: [ + IconButton( + icon: Icon(Icons.keyboard_arrow_up), + iconSize: 30.0, + onPressed: () => _temperatureUp(), + ), + IconButton( + icon: Icon(Icons.keyboard_arrow_down), + iconSize: 30.0, + onPressed: () => _temperatureDown(), + ) + ], + ) + ], + ), + Text("Operation", style: TextStyle( + fontSize: stateFontSize + )), + DropdownButton( + value: "$_operationMode", + iconSize: 30.0, + style: TextStyle( + fontSize: largeFontSize, + color: Colors.black, + ), + items: this._operationList.map((String value) { + return new DropdownMenuItem( + value: value, + child: new Text(value), + ); + }).toList(), + onChanged: (_) => _setOperationMode(_), + ), + Padding( + padding: EdgeInsets.only(top: rowPadding), + child: Row( + children: [ + Expanded( + child: Text( + "Away mode", + style: TextStyle( + fontSize: stateFontSize + ), + ), + ), + Switch( + onChanged: (value) => _setAwayMode(value), + value: _awayMode, + ) + ], + ), + ) + ], + ), + ); + } + + @override + Widget _buildActionWidget(BuildContext context) { + return Padding( + padding: + EdgeInsets.fromLTRB(0.0, 0.0, rightWidgetPadding, 0.0), + child: GestureDetector( + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Row( + children: [ + Text( + "${widget.entity.state}", + textAlign: TextAlign.right, + style: new TextStyle( + fontWeight: FontWeight.bold, + fontSize: stateFontSize, + )), + Text( + " ${widget.entity.attributes["temperature"]}", + textAlign: TextAlign.right, + style: new TextStyle( + fontSize: stateFontSize, + )) + ], + ), + Text( + "Currently: ${widget.entity.attributes["current_temperature"]}", + textAlign: TextAlign.right, + style: new TextStyle( + fontSize: stateFontSize, + color: Colors.black45 + )) + ], + ), + onTap: openEntityPage, + ) + ); + } + + @override + void dispose() { + _resetTimer?.cancel(); + super.dispose(); + } + +} \ No newline at end of file diff --git a/lib/entity_class/datetime_entity.class.dart b/lib/entity_class/datetime_entity.class.dart index 030c9d2..4263cfa 100644 --- a/lib/entity_class/datetime_entity.class.dart +++ b/lib/entity_class/datetime_entity.class.dart @@ -37,13 +37,13 @@ class _DateTimeEntityWidgetState extends _EntityWidgetState { Widget _buildActionWidget(BuildContext context) { return Padding( padding: - EdgeInsets.fromLTRB(0.0, 0.0, Entity.RIGHT_WIDGET_PADDING, 0.0), + EdgeInsets.fromLTRB(0.0, 0.0, rightWidgetPadding, 0.0), child: GestureDetector( child: Text( "$formattedState", textAlign: TextAlign.right, style: new TextStyle( - fontSize: Entity.STATE_FONT_SIZE, + fontSize: stateFontSize, )), onTap: () => _handleStateTap(context), ) diff --git a/lib/entity_class/entity.class.dart b/lib/entity_class/entity.class.dart index ecd7239..79e1e26 100644 --- a/lib/entity_class/entity.class.dart +++ b/lib/entity_class/entity.class.dart @@ -14,21 +14,11 @@ class Entity { "binary_sensor": Color.fromRGBO(3, 155, 229, 1.0) }; static List badgeDomains = ["alarm_control_panel", "binary_sensor", "device_tracker", "updater", "sun", "timer", "sensor"]; - static const RIGHT_WIDGET_PADDING = 14.0; - static const LEFT_WIDGET_PADDING = 8.0; - static const EXTENDED_WIDGET_HEIGHT = 50.0; - static const WIDGET_HEIGHT = 34.0; - static const ICON_SIZE = 28.0; - static const STATE_FONT_SIZE = 16.0; - static const NAME_FONT_SIZE = 16.0; - static const SMALL_FONT_SIZE = 14.0; - static const INPUT_WIDTH = 160.0; - static const ROW_PADDING = 10.0; Map attributes; - String _domain; - String _entityId; - String _state; + String domain; + String entityId; + String state; String assumedState; DateTime _lastUpdated; @@ -36,17 +26,15 @@ class Entity { String get displayName => attributes["friendly_name"] ?? (attributes["name"] ?? "_"); - String get domain => _domain; - String get entityId => _entityId; - String get state => _state; - set state(value) => _state = value; + + String get deviceClass => attributes["device_class"] ?? null; bool get isView => - (_domain == "group") && + (domain == "group") && (attributes != null ? attributes["view"] ?? false : false); - bool get isGroup => _domain == "group"; - bool get isBadge => Entity.badgeDomains.contains(_domain); + bool get isGroup => domain == "group"; + bool get isBadge => Entity.badgeDomains.contains(domain); String get icon => attributes["icon"] ?? ""; bool get isOn => state == "on"; String get entityPicture => attributes["entity_picture"]; @@ -60,10 +48,10 @@ class Entity { void update(Map rawData) { attributes = rawData["attributes"] ?? {}; - _domain = rawData["entity_id"].split(".")[0]; - _entityId = rawData["entity_id"]; - _state = rawData["state"]; - assumedState = _state; + domain = rawData["entity_id"].split(".")[0]; + entityId = rawData["entity_id"]; + state = rawData["state"]; + assumedState = state; _lastUpdated = DateTime.tryParse(rawData["last_updated"]); } @@ -137,28 +125,25 @@ class EntityWidget extends StatefulWidget { case "light": { return _SwitchEntityWidgetState(); } - case "script": case "scene": { return _ButtonEntityWidgetState(); } - case "input_datetime": { return _DateTimeEntityWidgetState(); } - case "input_select": { return _SelectEntityWidgetState(); } - case "input_number": { return _SliderEntityWidgetState(); } - case "input_text": { return _TextEntityWidgetState(); } - + case "climate": { + return _ClimateEntityWidgetState(); + } default: { return _EntityWidgetState(); } @@ -169,6 +154,17 @@ class EntityWidget extends StatefulWidget { class _EntityWidgetState extends State { List attributesToShow = ["all"]; + double rightWidgetPadding = 14.0; + double leftWidgetPadding = 8.0; + double extendedWidgetHeight = 50.0; + double widgetHeight = 34.0; + double iconSize = 28.0; + double stateFontSize = 16.0; + double nameFontSize = 16.0; + double smallFontSize = 14.0; + double largeFontSize = 24.0; + double inputWidth = 160.0; + double rowPadding = 10.0; @override Widget build(BuildContext context) { @@ -189,6 +185,7 @@ class _EntityWidgetState extends State { children: [ _buildMainWidget(context), _buildSecondRowWidget(), + Divider(), _buildAttributesWidget() ], ); @@ -196,7 +193,7 @@ class _EntityWidgetState extends State { Widget _buildMainWidget(BuildContext context) { return SizedBox( - height: Entity.WIDGET_HEIGHT, + height: widgetHeight, child: Row( children: [ GestureDetector( @@ -245,7 +242,7 @@ class _EntityWidgetState extends State { children: [ Expanded( child: Padding( - padding: EdgeInsets.fromLTRB(Entity.LEFT_WIDGET_PADDING, Entity.ROW_PADDING, 0.0, 0.0), + padding: EdgeInsets.fromLTRB(leftWidgetPadding, rowPadding, 0.0, 0.0), child: Text( "$name", textAlign: TextAlign.left, @@ -254,7 +251,7 @@ class _EntityWidgetState extends State { ), Expanded( child: Padding( - padding: EdgeInsets.fromLTRB(0.0, Entity.ROW_PADDING, Entity.RIGHT_WIDGET_PADDING, 0.0), + padding: EdgeInsets.fromLTRB(0.0, rowPadding, rightWidgetPadding, 0.0), child: Text( "$value", textAlign: TextAlign.right, @@ -279,10 +276,10 @@ class _EntityWidgetState extends State { Widget _buildIconWidget() { return Padding( - padding: EdgeInsets.fromLTRB(Entity.LEFT_WIDGET_PADDING, 0.0, 12.0, 0.0), + padding: EdgeInsets.fromLTRB(leftWidgetPadding, 0.0, 12.0, 0.0), child: MaterialDesignIcons.createIconWidgetFromEntityData( widget.entity, - Entity.ICON_SIZE, + iconSize, Entity.STATE_ICONS_COLORS[widget.entity.state] ?? Entity.STATE_ICONS_COLORS["default"]), ); } @@ -290,12 +287,12 @@ class _EntityWidgetState extends State { Widget _buildSecondRowWidget() { return Padding( padding: EdgeInsets.fromLTRB( - Entity.LEFT_WIDGET_PADDING, Entity.SMALL_FONT_SIZE, 0.0, 0.0), + leftWidgetPadding, smallFontSize, 0.0, 0.0), child: Text( '${widget.entity.lastUpdated}', textAlign: TextAlign.left, style: - TextStyle(fontSize: Entity.SMALL_FONT_SIZE, color: Colors.black26), + TextStyle(fontSize: smallFontSize, color: Colors.black26), ), ); } @@ -307,7 +304,7 @@ class _EntityWidgetState extends State { "${widget.entity.displayName}", overflow: TextOverflow.ellipsis, softWrap: false, - style: TextStyle(fontSize: Entity.NAME_FONT_SIZE), + style: TextStyle(fontSize: nameFontSize), ), ); } @@ -315,13 +312,13 @@ class _EntityWidgetState extends State { Widget _buildActionWidget(BuildContext context) { return Padding( padding: - EdgeInsets.fromLTRB(0.0, 0.0, Entity.RIGHT_WIDGET_PADDING, 0.0), + EdgeInsets.fromLTRB(0.0, 0.0, rightWidgetPadding, 0.0), child: GestureDetector( child: Text( "${widget.entity.state}${widget.entity.unitOfMeasurement}", textAlign: TextAlign.right, style: new TextStyle( - fontSize: Entity.STATE_FONT_SIZE, + fontSize: stateFontSize, )), onTap: openEntityPage, ) @@ -350,7 +347,7 @@ class _EntityWidgetState extends State { onBadgeTextValue = widget.entity.unitOfMeasurement; badgeIcon = Center( child: Text( - "${widget.entity.state == 'unknown' ? '-' : widget.entity.state}", + "${widget.entity.state}", overflow: TextOverflow.fade, softWrap: false, textAlign: TextAlign.center, diff --git a/lib/entity_class/slider_entity.class.dart b/lib/entity_class/slider_entity.class.dart index cf673aa..cf2b61b 100644 --- a/lib/entity_class/slider_entity.class.dart +++ b/lib/entity_class/slider_entity.class.dart @@ -25,12 +25,12 @@ class _SliderEntityWidgetState extends _EntityWidgetState { } @override - Widget _buildExtendedWidget(BuildContext context) { - return ListView( + Widget _buildSecondRowWidget() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildMainWidget(context), - _buildExtendedSlider(), - _buildSecondRowWidget() + super._buildSecondRowWidget(), + _buildExtendedSlider() ], ); } @@ -60,11 +60,11 @@ class _SliderEntityWidgetState extends _EntityWidgetState { @override Widget _buildActionWidget(BuildContext context) { Widget stateWidget = Padding( - padding: EdgeInsets.only(right: Entity.RIGHT_WIDGET_PADDING), + padding: EdgeInsets.only(right: rightWidgetPadding), child: Text("${widget.entity.state}${widget.entity.unitOfMeasurement}", textAlign: TextAlign.right, style: new TextStyle( - fontSize: Entity.STATE_FONT_SIZE, + fontSize: stateFontSize, )), ); if (widget.widgetType == EntityWidgetType.regular) { diff --git a/lib/entity_class/sun_entity.class.dart b/lib/entity_class/sun_entity.class.dart index f056716..fd276ce 100644 --- a/lib/entity_class/sun_entity.class.dart +++ b/lib/entity_class/sun_entity.class.dart @@ -3,6 +3,10 @@ part of '../main.dart'; class _SunEntityWidgetState extends _EntityWidgetState { @override - List attributesToShow = ["all"]; + Map _displayStates = { + "below_horizon": "Below horizon", + "above_horizon": "Above horizon" + }; + } \ No newline at end of file diff --git a/lib/home_assistant.class.dart b/lib/home_assistant.class.dart index 718a230..c44390d 100644 --- a/lib/home_assistant.class.dart +++ b/lib/home_assistant.class.dart @@ -192,7 +192,7 @@ class HomeAssistant { _handleMessage(String message) { var data = json.decode(message); - //TheLogger.log("Debug","[Received] => Message type: ${data['type']}"); + TheLogger.log("Debug","[Received] => $data"); if (data["type"] == "auth_required") { _sendAuthMessageRaw('{"type": "auth","$_authType": "$_password"}'); } else if (data["type"] == "auth_ok") { diff --git a/lib/main.dart b/lib/main.dart index 3f3ea3a..ba98b9f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,6 +21,7 @@ part 'entity_class/slider_entity.class.dart'; part 'entity_class/switch_entity.class.dart'; part 'entity_class/text_entity.class.dart'; part 'entity_class/sun_entity.class.dart'; +part 'entity_class/climate_entity.class.dart'; part 'settings.page.dart'; part 'home_assistant.class.dart'; @@ -35,7 +36,7 @@ part 'card_class.dart'; EventBus eventBus = new EventBus(); const String appName = "HA Client"; -const appVersion = "0.2.5.31"; +const appVersion = "0.2.5.33"; String homeAssistantWebHost; @@ -353,14 +354,6 @@ class _MainPageState extends State with WidgetsBindingObserver { applicationName: appName, applicationVersion: appVersion, applicationLegalese: "Keyboard Crumbs | www.keyboardcrumbs.io", - ), - new ListTile( - leading: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:coffee")), - title: Text("Buy me a coffee"), - onTap: () { - Navigator.of(context).pop(); - HAUtils.launchURL("https://www.buymeacoffee.com/estevez"); - }, ) ]); } diff --git a/pubspec.yaml b/pubspec.yaml index c805c99..057e966 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: hass_client description: Home Assistant Android Client -version: 0.2.5+31 +version: 0.2.5+33 environment: sdk: ">=2.0.0-dev.68.0 <3.0.0"