diff --git a/lib/entity.page.dart b/lib/entity.page.dart index 2bc2372..11b8ac2 100644 --- a/lib/entity.page.dart +++ b/lib/entity.page.dart @@ -3,7 +3,7 @@ part of 'main.dart'; class EntityViewPage extends StatefulWidget { EntityViewPage({Key key, this.entity}) : super(key: key); - Entity entity; + final Entity entity; @override _EntityViewPageState createState() => new _EntityViewPageState(); @@ -44,22 +44,13 @@ class _EntityViewPageState extends State { ), body: Padding( padding: EdgeInsets.all(10.0), - child: ListView( - children: [ - _entity.buildWidget(false, context), - _entity.buildAdditionalWidget() - ], - ), + child: _entity.buildWidget(context, false) ), ); } @override void dispose(){ - if (_entity is TextEntity && (_entity as TextEntity).tmpState != _entity.state) { - eventBus.fire(new ServiceCallEvent(_entity.domain, "set_value", _entity.entityId, {"value": "${(_entity as TextEntity).tmpState}"})); - TheLogger.log("Debug", "Saving changed input value for ${_entity.entityId}"); - } if (_stateSubscription != null) _stateSubscription.cancel(); super.dispose(); } diff --git a/lib/entity_class/button_entity.class.dart b/lib/entity_class/button_entity.class.dart index c5429b1..5727afc 100644 --- a/lib/entity_class/button_entity.class.dart +++ b/lib/entity_class/button_entity.class.dart @@ -1,11 +1,10 @@ part of '../main.dart'; -class ButtonEntity extends Entity { - ButtonEntity(Map rawData) : super(rawData); +class _ButtonEntityWidgetState extends _EntityWidgetState { @override void sendNewState(newValue) { - eventBus.fire(new ServiceCallEvent(_domain, "turn_on", _entityId, null)); + eventBus.fire(new ServiceCallEvent(widget.entity.domain, "turn_on", widget.entity.entityId, null)); } @override diff --git a/lib/entity_class/datetime_entity.class.dart b/lib/entity_class/datetime_entity.class.dart index bf3db9b..74da02e 100644 --- a/lib/entity_class/datetime_entity.class.dart +++ b/lib/entity_class/datetime_entity.class.dart @@ -1,19 +1,17 @@ part of '../main.dart'; -class DateTimeEntity extends Entity { - bool get hasDate => _attributes["has_date"] ?? false; - bool get hasTime => _attributes["has_time"] ?? false; - int get year => _attributes["year"] ?? 1970; - int get month => _attributes["month"] ?? 1; - int get day => _attributes["day"] ?? 1; - int get hour => _attributes["hour"] ?? 0; - int get minute => _attributes["minute"] ?? 0; - int get second => _attributes["second"] ?? 0; +class _DateTimeEntityWidgetState extends _EntityWidgetState { + bool get hasDate => widget.entity._attributes["has_date"] ?? false; + bool get hasTime => widget.entity._attributes["has_time"] ?? false; + int get year => widget.entity._attributes["year"] ?? 1970; + int get month => widget.entity._attributes["month"] ?? 1; + int get day => widget.entity._attributes["day"] ?? 1; + int get hour => widget.entity._attributes["hour"] ?? 0; + int get minute => widget.entity._attributes["minute"] ?? 0; + int get second => widget.entity._attributes["second"] ?? 0; String get formattedState => _getFormattedState(); DateTime get dateTimeState => _getDateTimeState(); - DateTimeEntity(Map rawData) : super(rawData); - DateTime _getDateTimeState() { return DateTime(this.year, this.month, this.day, this.hour, this.minute, this.second); } @@ -31,7 +29,7 @@ class DateTimeEntity extends Entity { @override void sendNewState(newValue) { - eventBus.fire(new ServiceCallEvent(_domain, "set_datetime", _entityId, + eventBus.fire(new ServiceCallEvent(widget.entity.domain, "set_datetime", widget.entity.entityId, newValue)); } @@ -72,7 +70,7 @@ class DateTimeEntity extends Entity { } }); } else { - TheLogger.log("Warning", "$entityId has no date and no time"); + TheLogger.log("Warning", "${widget.entity.entityId} has no date and no time"); } } diff --git a/lib/entity_class/entity.class.dart b/lib/entity_class/entity.class.dart index 1cf05a1..5dd198a 100644 --- a/lib/entity_class/entity.class.dart +++ b/lib/entity_class/entity.class.dart @@ -55,6 +55,13 @@ class Entity { _lastUpdated = DateTime.tryParse(rawData["last_updated"]); } + EntityWidget buildWidget(BuildContext context, bool inCard) { + return EntityWidget( + entity: this, + inCard: inCard, + ); + } + String _getLastUpdatedFormatted() { if (_lastUpdated == null) { return "-"; @@ -84,35 +91,98 @@ class Entity { } } - void openEntityPage() { - eventBus.fire(new ShowEntityPageEvent(this)); + +} + +class EntityWidget extends StatefulWidget { + EntityWidget({Key key, this.entity, this.inCard}) : super(key: key); + + final Entity entity; + final bool inCard; + + @override + _EntityWidgetState createState() { + switch (entity.domain) { + case "automation": + case "input_boolean ": + case "switch": + case "light": { + return _SwitchEntityWidgetState(); + } + + case "script": + case "scene": { + return _ButtonEntityWidgetState(); + } + + case "input_datetime": { + return _DateTimeEntityWidgetState(); + } + + case "input_select": { + return _SelectEntityWidgetState(); + } + + case "input_number": { + return _SliderEntityWidgetState(); + } + + case "input_text": { + return _TextEntityWidgetState(); + } + + default: { + return _EntityWidgetState(); + } + } + } +} + +class _EntityWidgetState extends State { + + @override + Widget build(BuildContext context) { + if (widget.inCard) { + return _buildMainWidget(context); + } else { + return ListView( + children: [ + _buildMainWidget(context), + _buildLastUpdatedWidget() + ], + ); + } } - void sendNewState(newState) { - return; - } - - Widget buildWidget(bool inCard, BuildContext context) { + Widget _buildMainWidget(BuildContext context) { return SizedBox( height: Entity.WIDGET_HEIGHT, child: Row( children: [ GestureDetector( child: _buildIconWidget(), - onTap: inCard ? openEntityPage : null, + onTap: widget.inCard ? openEntityPage : null, ), Expanded( child: GestureDetector( child: _buildNameWidget(), - onTap: inCard ? openEntityPage : null, + onTap: widget.inCard ? openEntityPage : null, ), ), - _buildActionWidget(inCard, context) + _buildActionWidget(widget.inCard, context) ], ), ); } + void openEntityPage() { + eventBus.fire(new ShowEntityPageEvent(widget.entity)); + } + + void sendNewState(newState) { + return; + } + Widget buildAdditionalWidget() { return _buildLastUpdatedWidget(); } @@ -121,9 +191,9 @@ class Entity { return Padding( padding: EdgeInsets.fromLTRB(Entity.LEFT_WIDGET_PADDING, 0.0, 12.0, 0.0), child: MaterialDesignIcons.createIconWidgetFromEntityData( - this, + widget.entity, Entity.ICON_SIZE, - Entity.STATE_ICONS_COLORS[_state] ?? Colors.blueGrey), + Entity.STATE_ICONS_COLORS[widget.entity.state] ?? Colors.blueGrey), ); } @@ -132,10 +202,10 @@ class Entity { padding: EdgeInsets.fromLTRB( Entity.LEFT_WIDGET_PADDING, Entity.SMALL_FONT_SIZE, 0.0, 0.0), child: Text( - '${this.lastUpdated}', + '${widget.entity.lastUpdated}', textAlign: TextAlign.left, style: - TextStyle(fontSize: Entity.SMALL_FONT_SIZE, color: Colors.black26), + TextStyle(fontSize: Entity.SMALL_FONT_SIZE, color: Colors.black26), ), ); } @@ -144,7 +214,7 @@ class Entity { return Padding( padding: EdgeInsets.only(right: 10.0), child: Text( - "${this.displayName}", + "${widget.entity.displayName}", overflow: TextOverflow.ellipsis, softWrap: false, style: TextStyle(fontSize: Entity.NAME_FONT_SIZE), @@ -155,10 +225,10 @@ class Entity { Widget _buildActionWidget(bool inCard, BuildContext context) { return Padding( padding: - EdgeInsets.fromLTRB(0.0, 0.0, Entity.RIGHT_WIDGET_PADDING, 0.0), + EdgeInsets.fromLTRB(0.0, 0.0, Entity.RIGHT_WIDGET_PADDING, 0.0), child: GestureDetector( child: Text( - "$_state${this.unitOfMeasurement}", + "${widget.entity.state}${widget.entity.unitOfMeasurement}", textAlign: TextAlign.right, style: new TextStyle( fontSize: Entity.STATE_FONT_SIZE, diff --git a/lib/entity_class/select_entity.class.dart b/lib/entity_class/select_entity.class.dart index ad7afca..0a91044 100644 --- a/lib/entity_class/select_entity.class.dart +++ b/lib/entity_class/select_entity.class.dart @@ -1,29 +1,26 @@ part of '../main.dart'; -class SelectEntity extends Entity { +class _SelectEntityWidgetState extends _EntityWidgetState { List _listOptions = []; - String get initialValue => _attributes["initial"] ?? null; - - SelectEntity(Map rawData) : super(rawData) { - if (_attributes["options"] != null) { - _attributes["options"].forEach((value){ - _listOptions.add(value.toString()); - }); - } - } @override void sendNewState(newValue) { - eventBus.fire(new ServiceCallEvent(_domain, "select_option", _entityId, + eventBus.fire(new ServiceCallEvent(widget.entity.domain, "select_option", widget.entity.entityId, {"option": "$newValue"})); } @override Widget _buildActionWidget(bool inCard, BuildContext context) { + _listOptions.clear(); + if (widget.entity._attributes["options"] != null) { + widget.entity._attributes["options"].forEach((value){ + _listOptions.add(value.toString()); + }); + } return Container( width: Entity.INPUT_WIDTH, child: DropdownButton( - value: _state, + value: widget.entity.state, items: this._listOptions.map((String value) { return new DropdownMenuItem( value: value, diff --git a/lib/entity_class/slider_entity.class.dart b/lib/entity_class/slider_entity.class.dart index 14954b1..fc18525 100644 --- a/lib/entity_class/slider_entity.class.dart +++ b/lib/entity_class/slider_entity.class.dart @@ -1,29 +1,31 @@ part of '../main.dart'; -class SliderEntity extends Entity { +class _SliderEntityWidgetState extends _EntityWidgetState { int _multiplier = 1; - double get minValue => _attributes["min"] ?? 0.0; - double get maxValue => _attributes["max"] ?? 100.0; - double get valueStep => _attributes["step"] ?? 1.0; - double get doubleState => double.tryParse(_state) ?? 0.0; + double get minValue => widget.entity._attributes["min"] ?? 0.0; + double get maxValue => widget.entity._attributes["max"] ?? 100.0; + double get valueStep => widget.entity._attributes["step"] ?? 1.0; + double get doubleState => double.tryParse(widget.entity.state) ?? 0.0; - SliderEntity(Map rawData) : super(rawData) { - if (valueStep < 1) { - _multiplier = 10; - } else if (valueStep < 0.1) { - _multiplier = 100; - } + @override + void initState() { + super.initState(); } @override void sendNewState(newValue) { - eventBus.fire(new ServiceCallEvent(_domain, "set_value", _entityId, + eventBus.fire(new ServiceCallEvent(widget.entity.domain, "set_value", widget.entity.entityId, {"value": "${newValue.toString()}"})); } @override Widget _buildActionWidget(bool inCard, BuildContext context) { + if (valueStep < 1) { + _multiplier = 10; + } else if (valueStep < 0.1) { + _multiplier = 100; + } return Container( width: 200.0, child: Row( @@ -32,13 +34,16 @@ class SliderEntity extends Entity { child: Slider( min: this.minValue * _multiplier, max: this.maxValue * _multiplier, - value: (this.doubleState <= this.maxValue) && - (this.doubleState >= this.minValue) - ? this.doubleState * _multiplier + value: (doubleState <= this.maxValue) && + (doubleState >= this.minValue) + ? doubleState * _multiplier : this.minValue * _multiplier, onChanged: (value) { - eventBus.fire(new StateChangedEvent(_entityId, - (value.roundToDouble() / _multiplier).toString(), true)); + setState(() { + widget.entity.state = (value.roundToDouble() / _multiplier).toString(); + }); + /*eventBus.fire(new StateChangedEvent(widget.entity.entityId, + (value.roundToDouble() / _multiplier).toString(), true));*/ }, onChangeEnd: (value) { sendNewState(value.roundToDouble() / _multiplier); @@ -47,7 +52,7 @@ class SliderEntity extends Entity { ), Padding( padding: EdgeInsets.only(right: Entity.RIGHT_WIDGET_PADDING), - child: Text("$_state${this.unitOfMeasurement}", + child: Text("${widget.entity.state}${widget.entity.unitOfMeasurement}", textAlign: TextAlign.right, style: new TextStyle( fontSize: Entity.STATE_FONT_SIZE, diff --git a/lib/entity_class/switch_entity.class.dart b/lib/entity_class/switch_entity.class.dart index c9a3c95..df9800b 100644 --- a/lib/entity_class/switch_entity.class.dart +++ b/lib/entity_class/switch_entity.class.dart @@ -1,20 +1,25 @@ part of '../main.dart'; -class SwitchEntity extends Entity { - SwitchEntity(Map rawData) : super(rawData); +class _SwitchEntityWidgetState extends _EntityWidgetState { + + @override + void initState() { + super.initState(); + } @override void sendNewState(newValue) { eventBus.fire(new ServiceCallEvent( - _domain, (newValue as bool) ? "turn_on" : "turn_off", entityId, null)); + widget.entity.domain, (newValue as bool) ? "turn_on" : "turn_off", widget.entity.entityId, null)); } @override Widget _buildActionWidget(bool inCard, BuildContext context) { return Switch( - value: this.isOn, + value: widget.entity.isOn, onChanged: ((switchState) { sendNewState(switchState); + widget.entity.state = switchState ? 'on' : 'off'; }), ); } diff --git a/lib/entity_class/text_entity.class.dart b/lib/entity_class/text_entity.class.dart index 8fc0033..cc739f1 100644 --- a/lib/entity_class/text_entity.class.dart +++ b/lib/entity_class/text_entity.class.dart @@ -1,40 +1,37 @@ part of '../main.dart'; -class TextEntity extends Entity { - String tmpState; - FocusNode _focusNode; +class _TextEntityWidgetState extends _EntityWidgetState { + String _tmpValue; + FocusNode _focusNode = FocusNode(); bool validValue = false; - 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"; + int get valueMinLength => widget.entity._attributes["min"] ?? -1; + int get valueMaxLength => widget.entity._attributes["max"] ?? -1; + String get valuePattern => widget.entity._attributes["pattern"] ?? null; + bool get isTextField => widget.entity._attributes["mode"] == "text"; + bool get isPasswordField => widget.entity._attributes["mode"] == "password"; - TextEntity(Map rawData) : super(rawData) { - _focusNode = FocusNode(); - //TODO possible memory leak generator + @override + void initState() { + super.initState(); _focusNode.addListener(_focusListener); - //tmpState = state; + _tmpValue = widget.entity.state; } @override void sendNewState(newValue) { if (validate(newValue)) { - eventBus.fire(new ServiceCallEvent(_domain, "set_value", _entityId, + eventBus.fire(new ServiceCallEvent(widget.entity.domain, "set_value", widget.entity.entityId, {"value": "$newValue"})); + } else { + setState(() { + _tmpValue = widget.entity.state; + }); } } - @override - void update(Map rawData) { - super.update(rawData); - tmpState = _state; - } - bool validate(newValue) { if (newValue is String) { - //TODO add pattern support validValue = (newValue.length >= this.valueMinLength) && (this.valueMaxLength == -1 || (newValue.length <= this.valueMaxLength)); @@ -45,32 +42,42 @@ class TextEntity extends Entity { } void _focusListener() { - if (!_focusNode.hasFocus && (tmpState != state)) { - sendNewState(tmpState); - tmpState = state; + if (!_focusNode.hasFocus && (_tmpValue != widget.entity.state)) { + sendNewState(_tmpValue); } } @override Widget _buildActionWidget(bool inCard, BuildContext context) { + if (!_focusNode.hasFocus && (_tmpValue != widget.entity.state)) { + _tmpValue = widget.entity.state; + } if (this.isTextField || this.isPasswordField) { return Container( width: Entity.INPUT_WIDTH, child: TextField( - focusNode: inCard ? _focusNode : null, + focusNode: _focusNode, obscureText: this.isPasswordField, controller: new TextEditingController.fromValue( new TextEditingValue( - text: tmpState, + text: _tmpValue, selection: - new TextSelection.collapsed(offset: tmpState.length))), + new TextSelection.collapsed(offset: _tmpValue.length))), onChanged: (value) { - tmpState = value; + _tmpValue = value; }), ); } else { - TheLogger.log("Warning", "Unsupported input mode for $entityId"); + TheLogger.log("Warning", "Unsupported input mode for ${widget.entity.entityId}"); return super._buildActionWidget(inCard, context); } } + + @override + void dispose() { + _focusNode.removeListener(_focusListener); + _focusNode.dispose(); + super.dispose(); + } + } \ No newline at end of file diff --git a/lib/entity_collection.class.dart b/lib/entity_collection.class.dart index 4e06795..2f4fbed 100644 --- a/lib/entity_collection.class.dart +++ b/lib/entity_collection.class.dart @@ -27,39 +27,7 @@ class EntityCollection { } Entity _createEntityInstance(rawEntityData) { - switch (rawEntityData["entity_id"].split(".")[0]) { - case "automation": - case "input_boolean ": - case "switch": - case "light": { - return SwitchEntity(rawEntityData); - } - - case "script": - case "scene": { - return ButtonEntity(rawEntityData); - } - - case "input_datetime": { - return DateTimeEntity(rawEntityData); - } - - case "input_select": { - return SelectEntity(rawEntityData); - } - - case "input_number": { - return SliderEntity(rawEntityData); - } - - case "input_text": { - return TextEntity(rawEntityData); - } - - default: { - return Entity(rawEntityData); - } - } + return Entity(rawEntityData); } void updateState(Map rawStateData) { diff --git a/lib/log.page.dart b/lib/log.page.dart index ca142ce..adf4dbe 100644 --- a/lib/log.page.dart +++ b/lib/log.page.dart @@ -44,7 +44,7 @@ class _LogViewPageState extends State { onPressed: () { String body = "```\n$_logData```"; String encodedBody = "${Uri.encodeFull(body)}"; - haUtils.launchURL("https://github.com/estevez-dev/ha_client_pub/issues/new?body=$encodedBody"); + HAUtils.launchURL("https://github.com/estevez-dev/ha_client_pub/issues/new?body=$encodedBody"); }, ), ], diff --git a/lib/main.dart b/lib/main.dart index 4ec268d..14587d3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -67,7 +67,7 @@ class HAClientApp extends StatelessWidget { ), initialRoute: "/", routes: { - "/": (context) => MainPage(title: 'Hass Client'), + "/": (context) => MainPage(title: 'HA Client'), "/connection-settings": (context) => ConnectionSettingsPage(title: "Connection Settings"), "/log-view": (context) => LogViewPage(title: "Log") }, @@ -416,7 +416,7 @@ class _MainPageState extends State with WidgetsBindingObserver { entities.add( Padding( padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0), - child: entity.buildWidget(true, context), + child: entity.buildWidget(context, true), )); } }); @@ -487,7 +487,7 @@ class _MainPageState extends State with WidgetsBindingObserver { title: Text("Report an issue"), onTap: () { Navigator.of(context).pop(); - haUtils.launchURL("https://github.com/estevez-dev/ha_client_pub/issues/new"); + HAUtils.launchURL("https://github.com/estevez-dev/ha_client_pub/issues/new"); }, ), new AboutListTile( diff --git a/lib/utils.class.dart b/lib/utils.class.dart index a05b660..e602706 100644 --- a/lib/utils.class.dart +++ b/lib/utils.class.dart @@ -32,7 +32,7 @@ class TheLogger { } -class haUtils { +class HAUtils { static void launchURL(String url) async { if (await canLaunch(url)) { await launch(url);