From 9edfec7dffd98b3f948d030171109cae05bee803 Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Sat, 27 Oct 2018 14:27:41 +0300 Subject: [PATCH] Code structure --- lib/entity.class.dart | 562 --------- lib/entity_class/button_entity.class.dart | 10 + lib/entity_class/climate_entity.class.dart | 109 ++ lib/entity_class/cover_entity.class.dart | 62 + lib/entity_class/date_time_entity.class.dart | 42 + lib/entity_class/entity.class.dart | 186 +++ lib/entity_class/light_entity.class.dart | 81 ++ lib/entity_class/select_entity.class.dart | 14 + lib/entity_class/sun_entity.class.dart | 41 + lib/entity_class/switch_entity.class.dart | 10 + lib/entity_class/text_entity.class.dart | 16 + lib/entity_widgets/badge.dart | 125 ++ .../controls/climate_controls.dart | 467 ++++++++ .../controls/cover_controls.dart | 201 ++++ .../controls/light_controls.dart | 240 ++++ .../default_entity_container.dart | 28 + .../entity_attributes_list.dart | 57 + lib/entity_widgets/entity_icon.dart | 22 + lib/entity_widgets/entity_model.dart | 22 + lib/entity_widgets/entity_name.dart | 23 + lib/entity_widgets/entity_page_container.dart | 14 + lib/entity_widgets/last_updated.dart | 18 + lib/entity_widgets/mode_selector.dart | 62 + lib/entity_widgets/mode_swicth.dart | 38 + lib/entity_widgets/state/button_state.dart | 24 + lib/entity_widgets/state/climate_state.dart | 57 + lib/entity_widgets/state/cover_state.dart | 65 ++ lib/entity_widgets/state/date_time_state.dart | 75 ++ lib/entity_widgets/state/select_state.dart | 46 + lib/entity_widgets/state/simple_state.dart | 22 + lib/entity_widgets/state/slider_state.dart | 58 + lib/entity_widgets/state/switch_state.dart | 60 + .../state/text_input_state.dart | 98 ++ lib/main.dart | 39 +- lib/ui/card.class.dart | 74 ++ lib/ui/ui.dart | 26 + lib/{ui.dart => ui/view.class.dart} | 112 +- lib/widgets/stateful_widgets.dart | 1029 ----------------- lib/widgets/stateless_widgets.dart | 774 ------------- 39 files changed, 2535 insertions(+), 2474 deletions(-) delete mode 100644 lib/entity.class.dart create mode 100644 lib/entity_class/button_entity.class.dart create mode 100644 lib/entity_class/climate_entity.class.dart create mode 100644 lib/entity_class/cover_entity.class.dart create mode 100644 lib/entity_class/date_time_entity.class.dart create mode 100644 lib/entity_class/entity.class.dart create mode 100644 lib/entity_class/light_entity.class.dart create mode 100644 lib/entity_class/select_entity.class.dart create mode 100644 lib/entity_class/sun_entity.class.dart create mode 100644 lib/entity_class/switch_entity.class.dart create mode 100644 lib/entity_class/text_entity.class.dart create mode 100644 lib/entity_widgets/badge.dart create mode 100644 lib/entity_widgets/controls/climate_controls.dart create mode 100644 lib/entity_widgets/controls/cover_controls.dart create mode 100644 lib/entity_widgets/controls/light_controls.dart create mode 100644 lib/entity_widgets/default_entity_container.dart create mode 100644 lib/entity_widgets/entity_attributes_list.dart create mode 100644 lib/entity_widgets/entity_icon.dart create mode 100644 lib/entity_widgets/entity_model.dart create mode 100644 lib/entity_widgets/entity_name.dart create mode 100644 lib/entity_widgets/entity_page_container.dart create mode 100644 lib/entity_widgets/last_updated.dart create mode 100644 lib/entity_widgets/mode_selector.dart create mode 100644 lib/entity_widgets/mode_swicth.dart create mode 100644 lib/entity_widgets/state/button_state.dart create mode 100644 lib/entity_widgets/state/climate_state.dart create mode 100644 lib/entity_widgets/state/cover_state.dart create mode 100644 lib/entity_widgets/state/date_time_state.dart create mode 100644 lib/entity_widgets/state/select_state.dart create mode 100644 lib/entity_widgets/state/simple_state.dart create mode 100644 lib/entity_widgets/state/slider_state.dart create mode 100644 lib/entity_widgets/state/switch_state.dart create mode 100644 lib/entity_widgets/state/text_input_state.dart create mode 100644 lib/ui/card.class.dart create mode 100644 lib/ui/ui.dart rename lib/{ui.dart => ui/view.class.dart} (61%) delete mode 100644 lib/widgets/stateful_widgets.dart delete mode 100644 lib/widgets/stateless_widgets.dart diff --git a/lib/entity.class.dart b/lib/entity.class.dart deleted file mode 100644 index 67c263b..0000000 --- a/lib/entity.class.dart +++ /dev/null @@ -1,562 +0,0 @@ -part of 'main.dart'; - -class Entity { - static const STATE_ICONS_COLORS = { - "on": Colors.amber, - "off": Color.fromRGBO(68, 115, 158, 1.0), - "default": Color.fromRGBO(68, 115, 158, 1.0), - "unavailable": Colors.black12, - "unknown": Colors.black12, - "playing": Colors.amber - }; - static const badgeColors = { - "default": Color.fromRGBO(223, 76, 30, 1.0), - "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 rightWidgetPadding = 14.0; - static const leftWidgetPadding = 8.0; - static const extendedWidgetHeight = 50.0; - static const iconSize = 28.0; - static const stateFontSize = 16.0; - static const nameFontSize = 16.0; - static const smallFontSize = 14.0; - static const largeFontSize = 24.0; - static const inputWidth = 160.0; - static const rowPadding = 10.0; - - double widgetHeight = 34.0; - - Map attributes; - String domain; - String entityId; - String state; - String assumedState; - DateTime _lastUpdated; - - List childEntities = []; - - List attributesToShow = ["all"]; - - String get displayName => - attributes["friendly_name"] ?? (attributes["name"] ?? "_"); - - String get deviceClass => attributes["device_class"] ?? null; - 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 == "on"; - String get entityPicture => attributes["entity_picture"]; - String get unitOfMeasurement => attributes["unit_of_measurement"] ?? ""; - List get childEntityIds => attributes["entity_id"] ?? []; - String get lastUpdated => _getLastUpdatedFormatted(); - bool get isHidden => attributes["hidden"] ?? false; - - Entity(Map rawData) { - update(rawData); - } - - void update(Map rawData) { - attributes = rawData["attributes"] ?? {}; - domain = rawData["entity_id"].split(".")[0]; - entityId = rawData["entity_id"]; - state = rawData["state"]; - assumedState = state; - _lastUpdated = DateTime.tryParse(rawData["last_updated"]); - } - - 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"); - } - } - - Widget buildDefaultWidget(BuildContext context) { - return EntityModel( - entity: this, - child: DefaultEntityContainer( - state: _buildStatePart(context), - height: widgetHeight, - ), - handleTap: true, - ); - } - - Widget _buildStatePart(BuildContext context) { - return SimpleEntityState(); - } - - Widget _buildStatePartForPage(BuildContext context) { - return _buildStatePart(context); - } - - Widget _buildAdditionalControlsForPage(BuildContext context) { - return Container( - width: 0.0, - height: 0.0, - ); - } - - Widget buildEntityPageWidget(BuildContext context) { - return EntityModel( - entity: this, - child: EntityPageContainer(children: [ - DefaultEntityContainer(state: _buildStatePartForPage(context), height: widgetHeight), - LastUpdatedWidget(), - Divider(), - _buildAdditionalControlsForPage(context), - Divider(), - EntityAttributesList() - ]), - handleTap: false, - ); - } - - Widget buildBadgeWidget(BuildContext context) { - return EntityModel( - entity: this, - child: Badge(), - 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"; - } - } -} - -class SwitchEntity extends Entity { - SwitchEntity(Map rawData) : super(rawData); - - @override - Widget _buildStatePart(BuildContext context) { - return SwitchStateWidget(); - } -} - -class ButtonEntity extends Entity { - ButtonEntity(Map rawData) : super(rawData); - - @override - Widget _buildStatePart(BuildContext context) { - return ButtonStateWidget(); - } -} - -class TextEntity extends Entity { - TextEntity(Map rawData) : super(rawData); - - 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(); - } -} - -class SunEntity extends Entity { - SunEntity(Map rawData) : super(rawData); -} - -class SliderEntity extends Entity { - SliderEntity(Map rawData) : super(rawData); - - 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; - - @override - Widget _buildStatePart(BuildContext context) { - return Expanded( - //width: 200.0, - child: Row( - children: [ - SliderStateWidget( - expanded: true, - ), - SimpleEntityState(), - ], - ), - ); - } - - @override - Widget _buildStatePartForPage(BuildContext context) { - return SimpleEntityState(); - } - - @override - Widget _buildAdditionalControlsForPage(BuildContext context) { - return SliderStateWidget( - expanded: false, - ); - } -} - -class ClimateEntity extends Entity { - @override - double widgetHeight = 38.0; - - static const SUPPORT_TARGET_TEMPERATURE = 1; - static const SUPPORT_TARGET_TEMPERATURE_HIGH = 2; - static const SUPPORT_TARGET_TEMPERATURE_LOW = 4; - static const SUPPORT_TARGET_HUMIDITY = 8; - static const SUPPORT_TARGET_HUMIDITY_HIGH = 16; - static const SUPPORT_TARGET_HUMIDITY_LOW = 32; - static const SUPPORT_FAN_MODE = 64; - static const SUPPORT_OPERATION_MODE = 128; - static const SUPPORT_HOLD_MODE = 256; - static const SUPPORT_SWING_MODE = 512; - static const SUPPORT_AWAY_MODE = 1024; - static const SUPPORT_AUX_HEAT = 2048; - static const SUPPORT_ON_OFF = 4096; - - bool get supportTargetTemperature => ((attributes["supported_features"] & - ClimateEntity.SUPPORT_TARGET_TEMPERATURE) == - ClimateEntity.SUPPORT_TARGET_TEMPERATURE); - bool get supportTargetTemperatureHigh => ((attributes["supported_features"] & - ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH) == - ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH); - bool get supportTargetTemperatureLow => ((attributes["supported_features"] & - ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW) == - ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW); - bool get supportTargetHumidity => ((attributes["supported_features"] & - ClimateEntity.SUPPORT_TARGET_HUMIDITY) == - ClimateEntity.SUPPORT_TARGET_HUMIDITY); - bool get supportTargetHumidityHigh => ((attributes["supported_features"] & - ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH) == - ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH); - bool get supportTargetHumidityLow => ((attributes["supported_features"] & - ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW) == - ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW); - bool get supportFanMode => - ((attributes["supported_features"] & ClimateEntity.SUPPORT_FAN_MODE) == - ClimateEntity.SUPPORT_FAN_MODE); - bool get supportOperationMode => ((attributes["supported_features"] & - ClimateEntity.SUPPORT_OPERATION_MODE) == - ClimateEntity.SUPPORT_OPERATION_MODE); - bool get supportHoldMode => - ((attributes["supported_features"] & ClimateEntity.SUPPORT_HOLD_MODE) == - ClimateEntity.SUPPORT_HOLD_MODE); - bool get supportSwingMode => - ((attributes["supported_features"] & ClimateEntity.SUPPORT_SWING_MODE) == - ClimateEntity.SUPPORT_SWING_MODE); - bool get supportAwayMode => - ((attributes["supported_features"] & ClimateEntity.SUPPORT_AWAY_MODE) == - ClimateEntity.SUPPORT_AWAY_MODE); - bool get supportAuxHeat => - ((attributes["supported_features"] & ClimateEntity.SUPPORT_AUX_HEAT) == - ClimateEntity.SUPPORT_AUX_HEAT); - bool get supportOnOff => - ((attributes["supported_features"] & ClimateEntity.SUPPORT_ON_OFF) == - ClimateEntity.SUPPORT_ON_OFF); - - List get operationList => attributes["operation_list"] != null - ? (attributes["operation_list"] as List).cast() - : null; - List get fanList => attributes["fan_list"] != null - ? (attributes["fan_list"] as List).cast() - : null; - List get swingList => attributes["swing_list"] != null - ? (attributes["swing_list"] as List).cast() - : null; - double get temperature => _getDoubleAttributeValue('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'); - String get operationMode => attributes['operation_mode']; - String get fanMode => attributes['fan_mode']; - String get swingMode => attributes['swing_mode']; - bool get awayMode => attributes['away_mode'] == "on"; - bool get isOff => state == "off"; - bool get auxHeat => attributes['aux_heat'] == "on"; - - ClimateEntity(Map rawData) : super(rawData); - - @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; - } - } - -} - -class SelectEntity extends Entity { - List get listOptions => attributes["options"] != null - ? (attributes["options"] as List).cast() - : []; - - SelectEntity(Map rawData) : super(rawData); - - @override - Widget _buildStatePart(BuildContext context) { - return SelectControlWidget(); - } -} - -class DateTimeEntity extends Entity { - bool get hasDate => attributes["has_date"] ?? false; - bool get hasTime => attributes["has_time"] ?? false; - int get year => attributes["year"] ?? 1970; - int get month => attributes["month"] ?? 1; - int get day => attributes["day"] ?? 1; - int get hour => attributes["hour"] ?? 0; - int get minute => attributes["minute"] ?? 0; - int get second => attributes["second"] ?? 0; - String get formattedState => _getFormattedState(); - DateTime get dateTimeState => _getDateTimeState(); - - DateTimeEntity(Map rawData) : super(rawData); - - @override - Widget _buildStatePart(BuildContext context) { - return DateTimeStateWidget(); - } - - DateTime _getDateTimeState() { - return DateTime( - this.year, this.month, this.day, this.hour, this.minute, this.second); - } - - String _getFormattedState() { - String formattedState = ""; - if (this.hasDate) { - formattedState += formatDate(dateTimeState, [M, ' ', d, ', ', yyyy]); - } - if (this.hasTime) { - formattedState += " " + formatDate(dateTimeState, [HH, ':', nn]); - } - return formattedState; - } - - void setNewState(newValue) { - eventBus - .fire(new ServiceCallEvent(domain, "set_datetime", entityId, newValue)); - } -} - -class CoverEntity extends Entity { - @override - double widgetHeight = 38.0; - - 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; - - bool get supportOpen => ((attributes["supported_features"] & - CoverEntity.SUPPORT_OPEN) == - CoverEntity.SUPPORT_OPEN); - bool get supportClose => ((attributes["supported_features"] & - CoverEntity.SUPPORT_CLOSE) == - CoverEntity.SUPPORT_CLOSE); - bool get supportSetPosition => ((attributes["supported_features"] & - CoverEntity.SUPPORT_SET_POSITION) == - CoverEntity.SUPPORT_SET_POSITION); - bool get supportStop => ((attributes["supported_features"] & - CoverEntity.SUPPORT_STOP) == - CoverEntity.SUPPORT_STOP); - - bool get supportOpenTilt => ((attributes["supported_features"] & - CoverEntity.SUPPORT_OPEN_TILT) == - CoverEntity.SUPPORT_OPEN_TILT); - bool get supportCloseTilt => ((attributes["supported_features"] & - CoverEntity.SUPPORT_CLOSE_TILT) == - CoverEntity.SUPPORT_CLOSE_TILT); - bool get supportStopTilt => ((attributes["supported_features"] & - CoverEntity.SUPPORT_STOP_TILT) == - CoverEntity.SUPPORT_STOP_TILT); - bool get supportSetTiltPosition => ((attributes["supported_features"] & - 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 != "opening") && (state != "open")); - bool get canBeClosed => ((state != "closing") && (state != "closed")); - bool get canTiltBeOpened => currentPosition < 100; - bool get canTiltBeClosed => currentPosition > 0; - - CoverEntity(Map rawData) : super(rawData); - - @override - Widget _buildStatePart(BuildContext context) { - return CoverEntityControlState(); - } - - @override - Widget _buildAdditionalControlsForPage(BuildContext context) { - return CoverControlWidget(); - } - -} - -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 => ((attributes["supported_features"] & - LightEntity.SUPPORT_BRIGHTNESS) == - LightEntity.SUPPORT_BRIGHTNESS); - bool get supportColorTemp => ((attributes["supported_features"] & - LightEntity.SUPPORT_COLOR_TEMP) == - LightEntity.SUPPORT_COLOR_TEMP); - bool get supportEffect => ((attributes["supported_features"] & - LightEntity.SUPPORT_EFFECT) == - LightEntity.SUPPORT_EFFECT); - bool get supportFlash => ((attributes["supported_features"] & - LightEntity.SUPPORT_FLASH) == - LightEntity.SUPPORT_FLASH); - bool get supportColor => ((attributes["supported_features"] & - LightEntity.SUPPORT_COLOR) == - LightEntity.SUPPORT_COLOR); - bool get supportTransition => ((attributes["supported_features"] & - LightEntity.SUPPORT_TRANSITION) == - LightEntity.SUPPORT_TRANSITION); - bool get supportWhiteValue => ((attributes["supported_features"] & - LightEntity.SUPPORT_WHITE_VALUE) == - LightEntity.SUPPORT_WHITE_VALUE); - - int get brightness => _getIntAttributeValue("brightness"); - int get colorTemp => _getIntAttributeValue("color_temp"); - double get maxMireds => _getDoubleAttributeValue("max_mireds"); - double get minMireds => _getDoubleAttributeValue("min_mireds"); - Color get color => _getColor(); - bool get isAdditionalControls => ((attributes["supported_features"] != null) && (attributes["supported_features"] != 0)); - List get effectList => _getEffectList(); - - LightEntity(Map rawData) : super(rawData); - - Color _getColor() { - List rgb = attributes["rgb_color"]; - try { - if ((rgb != null) && (rgb.length > 0)) { - return Color.fromARGB(255, rgb[0], rgb[1], rgb[2]); - } else { - return null; - } - } catch (e) { - return null; - } - } - - List _getEffectList() { - if (attributes["effect_list"] != null) { - List result = (attributes["effect_list"] as List).cast(); - return result; - } else { - return null; - } - } - - @override - Widget _buildStatePart(BuildContext context) { - return SwitchStateWidget(); - } - - @override - Widget _buildAdditionalControlsForPage(BuildContext context) { - if (!isAdditionalControls) { - return Container(height: 0.0, width: 0.0); - } else { - return LightControlsWidget(); - } - } - -} diff --git a/lib/entity_class/button_entity.class.dart b/lib/entity_class/button_entity.class.dart new file mode 100644 index 0000000..6b10656 --- /dev/null +++ b/lib/entity_class/button_entity.class.dart @@ -0,0 +1,10 @@ +part of '../main.dart'; + +class ButtonEntity extends Entity { + ButtonEntity(Map rawData) : super(rawData); + + @override + Widget _buildStatePart(BuildContext context) { + return ButtonStateWidget(); + } +} \ No newline at end of file diff --git a/lib/entity_class/climate_entity.class.dart b/lib/entity_class/climate_entity.class.dart new file mode 100644 index 0000000..459b010 --- /dev/null +++ b/lib/entity_class/climate_entity.class.dart @@ -0,0 +1,109 @@ +part of '../main.dart'; + +class ClimateEntity extends Entity { + @override + double widgetHeight = 38.0; + + static const SUPPORT_TARGET_TEMPERATURE = 1; + static const SUPPORT_TARGET_TEMPERATURE_HIGH = 2; + static const SUPPORT_TARGET_TEMPERATURE_LOW = 4; + static const SUPPORT_TARGET_HUMIDITY = 8; + static const SUPPORT_TARGET_HUMIDITY_HIGH = 16; + static const SUPPORT_TARGET_HUMIDITY_LOW = 32; + static const SUPPORT_FAN_MODE = 64; + static const SUPPORT_OPERATION_MODE = 128; + static const SUPPORT_HOLD_MODE = 256; + static const SUPPORT_SWING_MODE = 512; + static const SUPPORT_AWAY_MODE = 1024; + static const SUPPORT_AUX_HEAT = 2048; + static const SUPPORT_ON_OFF = 4096; + + bool get supportTargetTemperature => ((attributes["supported_features"] & + ClimateEntity.SUPPORT_TARGET_TEMPERATURE) == + ClimateEntity.SUPPORT_TARGET_TEMPERATURE); + bool get supportTargetTemperatureHigh => ((attributes["supported_features"] & + ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH) == + ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH); + bool get supportTargetTemperatureLow => ((attributes["supported_features"] & + ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW) == + ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW); + bool get supportTargetHumidity => ((attributes["supported_features"] & + ClimateEntity.SUPPORT_TARGET_HUMIDITY) == + ClimateEntity.SUPPORT_TARGET_HUMIDITY); + bool get supportTargetHumidityHigh => ((attributes["supported_features"] & + ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH) == + ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH); + bool get supportTargetHumidityLow => ((attributes["supported_features"] & + ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW) == + ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW); + bool get supportFanMode => + ((attributes["supported_features"] & ClimateEntity.SUPPORT_FAN_MODE) == + ClimateEntity.SUPPORT_FAN_MODE); + bool get supportOperationMode => ((attributes["supported_features"] & + ClimateEntity.SUPPORT_OPERATION_MODE) == + ClimateEntity.SUPPORT_OPERATION_MODE); + bool get supportHoldMode => + ((attributes["supported_features"] & ClimateEntity.SUPPORT_HOLD_MODE) == + ClimateEntity.SUPPORT_HOLD_MODE); + bool get supportSwingMode => + ((attributes["supported_features"] & ClimateEntity.SUPPORT_SWING_MODE) == + ClimateEntity.SUPPORT_SWING_MODE); + bool get supportAwayMode => + ((attributes["supported_features"] & ClimateEntity.SUPPORT_AWAY_MODE) == + ClimateEntity.SUPPORT_AWAY_MODE); + bool get supportAuxHeat => + ((attributes["supported_features"] & ClimateEntity.SUPPORT_AUX_HEAT) == + ClimateEntity.SUPPORT_AUX_HEAT); + bool get supportOnOff => + ((attributes["supported_features"] & ClimateEntity.SUPPORT_ON_OFF) == + ClimateEntity.SUPPORT_ON_OFF); + + List get operationList => attributes["operation_list"] != null + ? (attributes["operation_list"] as List).cast() + : null; + List get fanList => attributes["fan_list"] != null + ? (attributes["fan_list"] as List).cast() + : null; + List get swingList => attributes["swing_list"] != null + ? (attributes["swing_list"] as List).cast() + : null; + double get temperature => _getDoubleAttributeValue('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'); + String get operationMode => attributes['operation_mode']; + String get fanMode => attributes['fan_mode']; + String get swingMode => attributes['swing_mode']; + bool get awayMode => attributes['away_mode'] == "on"; + bool get isOff => state == "off"; + bool get auxHeat => attributes['aux_heat'] == "on"; + + ClimateEntity(Map rawData) : super(rawData); + + @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; + } + } + +} \ No newline at end of file diff --git a/lib/entity_class/cover_entity.class.dart b/lib/entity_class/cover_entity.class.dart new file mode 100644 index 0000000..6e3d2ba --- /dev/null +++ b/lib/entity_class/cover_entity.class.dart @@ -0,0 +1,62 @@ +part of '../main.dart'; + +class CoverEntity extends Entity { + @override + double widgetHeight = 38.0; + + 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; + + bool get supportOpen => ((attributes["supported_features"] & + CoverEntity.SUPPORT_OPEN) == + CoverEntity.SUPPORT_OPEN); + bool get supportClose => ((attributes["supported_features"] & + CoverEntity.SUPPORT_CLOSE) == + CoverEntity.SUPPORT_CLOSE); + bool get supportSetPosition => ((attributes["supported_features"] & + CoverEntity.SUPPORT_SET_POSITION) == + CoverEntity.SUPPORT_SET_POSITION); + bool get supportStop => ((attributes["supported_features"] & + CoverEntity.SUPPORT_STOP) == + CoverEntity.SUPPORT_STOP); + + bool get supportOpenTilt => ((attributes["supported_features"] & + CoverEntity.SUPPORT_OPEN_TILT) == + CoverEntity.SUPPORT_OPEN_TILT); + bool get supportCloseTilt => ((attributes["supported_features"] & + CoverEntity.SUPPORT_CLOSE_TILT) == + CoverEntity.SUPPORT_CLOSE_TILT); + bool get supportStopTilt => ((attributes["supported_features"] & + CoverEntity.SUPPORT_STOP_TILT) == + CoverEntity.SUPPORT_STOP_TILT); + bool get supportSetTiltPosition => ((attributes["supported_features"] & + 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 != "opening") && (state != "open")); + bool get canBeClosed => ((state != "closing") && (state != "closed")); + bool get canTiltBeOpened => currentPosition < 100; + bool get canTiltBeClosed => currentPosition > 0; + + CoverEntity(Map rawData) : super(rawData); + + @override + Widget _buildStatePart(BuildContext context) { + return CoverStateWidget(); + } + + @override + Widget _buildAdditionalControlsForPage(BuildContext context) { + return CoverControlWidget(); + } + +} \ No newline at end of file diff --git a/lib/entity_class/date_time_entity.class.dart b/lib/entity_class/date_time_entity.class.dart new file mode 100644 index 0000000..1223a7e --- /dev/null +++ b/lib/entity_class/date_time_entity.class.dart @@ -0,0 +1,42 @@ +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; + String get formattedState => _getFormattedState(); + DateTime get dateTimeState => _getDateTimeState(); + + DateTimeEntity(Map rawData) : super(rawData); + + @override + Widget _buildStatePart(BuildContext context) { + return DateTimeStateWidget(); + } + + DateTime _getDateTimeState() { + return DateTime( + this.year, this.month, this.day, this.hour, this.minute, this.second); + } + + String _getFormattedState() { + String formattedState = ""; + if (this.hasDate) { + formattedState += formatDate(dateTimeState, [M, ' ', d, ', ', yyyy]); + } + if (this.hasTime) { + formattedState += " " + formatDate(dateTimeState, [HH, ':', nn]); + } + return formattedState; + } + + void setNewState(newValue) { + eventBus + .fire(new ServiceCallEvent(domain, "set_datetime", entityId, newValue)); + } +} \ No newline at end of file diff --git a/lib/entity_class/entity.class.dart b/lib/entity_class/entity.class.dart new file mode 100644 index 0000000..f244b2c --- /dev/null +++ b/lib/entity_class/entity.class.dart @@ -0,0 +1,186 @@ +part of '../main.dart'; + +class Entity { + static const STATE_ICONS_COLORS = { + "on": Colors.amber, + "off": Color.fromRGBO(68, 115, 158, 1.0), + "default": Color.fromRGBO(68, 115, 158, 1.0), + "unavailable": Colors.black12, + "unknown": Colors.black12, + "playing": Colors.amber + }; + static const badgeColors = { + "default": Color.fromRGBO(223, 76, 30, 1.0), + "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 rightWidgetPadding = 14.0; + static const leftWidgetPadding = 8.0; + static const extendedWidgetHeight = 50.0; + static const iconSize = 28.0; + static const stateFontSize = 16.0; + static const nameFontSize = 16.0; + static const smallFontSize = 14.0; + static const largeFontSize = 24.0; + static const inputWidth = 160.0; + static const rowPadding = 10.0; + + double widgetHeight = 34.0; + + Map attributes; + String domain; + String entityId; + String state; + String assumedState; + DateTime _lastUpdated; + + List childEntities = []; + + List attributesToShow = ["all"]; + + String get displayName => + attributes["friendly_name"] ?? (attributes["name"] ?? "_"); + + String get deviceClass => attributes["device_class"] ?? null; + 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 == "on"; + String get entityPicture => attributes["entity_picture"]; + String get unitOfMeasurement => attributes["unit_of_measurement"] ?? ""; + List get childEntityIds => attributes["entity_id"] ?? []; + String get lastUpdated => _getLastUpdatedFormatted(); + bool get isHidden => attributes["hidden"] ?? false; + + Entity(Map rawData) { + update(rawData); + } + + void update(Map rawData) { + attributes = rawData["attributes"] ?? {}; + domain = rawData["entity_id"].split(".")[0]; + entityId = rawData["entity_id"]; + state = rawData["state"]; + assumedState = state; + _lastUpdated = DateTime.tryParse(rawData["last_updated"]); + } + + 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"); + } + } + + Widget buildDefaultWidget(BuildContext context) { + return EntityModel( + entity: this, + child: DefaultEntityContainer( + state: _buildStatePart(context), + height: widgetHeight, + ), + handleTap: true, + ); + } + + Widget _buildStatePart(BuildContext context) { + return SimpleEntityState(); + } + + Widget _buildStatePartForPage(BuildContext context) { + return _buildStatePart(context); + } + + Widget _buildAdditionalControlsForPage(BuildContext context) { + return Container( + width: 0.0, + height: 0.0, + ); + } + + Widget buildEntityPageWidget(BuildContext context) { + return EntityModel( + entity: this, + child: EntityPageContainer(children: [ + DefaultEntityContainer(state: _buildStatePartForPage(context), height: widgetHeight), + LastUpdatedWidget(), + Divider(), + _buildAdditionalControlsForPage(context), + Divider(), + EntityAttributesList() + ]), + handleTap: false, + ); + } + + Widget buildBadgeWidget(BuildContext context) { + return EntityModel( + 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"; + } + } +} diff --git a/lib/entity_class/light_entity.class.dart b/lib/entity_class/light_entity.class.dart new file mode 100644 index 0000000..91b1b98 --- /dev/null +++ b/lib/entity_class/light_entity.class.dart @@ -0,0 +1,81 @@ +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 => ((attributes["supported_features"] & + LightEntity.SUPPORT_BRIGHTNESS) == + LightEntity.SUPPORT_BRIGHTNESS); + bool get supportColorTemp => ((attributes["supported_features"] & + LightEntity.SUPPORT_COLOR_TEMP) == + LightEntity.SUPPORT_COLOR_TEMP); + bool get supportEffect => ((attributes["supported_features"] & + LightEntity.SUPPORT_EFFECT) == + LightEntity.SUPPORT_EFFECT); + bool get supportFlash => ((attributes["supported_features"] & + LightEntity.SUPPORT_FLASH) == + LightEntity.SUPPORT_FLASH); + bool get supportColor => ((attributes["supported_features"] & + LightEntity.SUPPORT_COLOR) == + LightEntity.SUPPORT_COLOR); + bool get supportTransition => ((attributes["supported_features"] & + LightEntity.SUPPORT_TRANSITION) == + LightEntity.SUPPORT_TRANSITION); + bool get supportWhiteValue => ((attributes["supported_features"] & + LightEntity.SUPPORT_WHITE_VALUE) == + LightEntity.SUPPORT_WHITE_VALUE); + + int get brightness => _getIntAttributeValue("brightness"); + int get colorTemp => _getIntAttributeValue("color_temp"); + double get maxMireds => _getDoubleAttributeValue("max_mireds"); + double get minMireds => _getDoubleAttributeValue("min_mireds"); + Color get color => _getColor(); + bool get isAdditionalControls => ((attributes["supported_features"] != null) && (attributes["supported_features"] != 0)); + List get effectList => _getEffectList(); + + LightEntity(Map rawData) : super(rawData); + + Color _getColor() { + List rgb = attributes["rgb_color"]; + try { + if ((rgb != null) && (rgb.length > 0)) { + return Color.fromARGB(255, rgb[0], rgb[1], rgb[2]); + } else { + return null; + } + } catch (e) { + return null; + } + } + + List _getEffectList() { + if (attributes["effect_list"] != null) { + List result = (attributes["effect_list"] as List).cast(); + return result; + } else { + return null; + } + } + + @override + Widget _buildStatePart(BuildContext context) { + return SwitchStateWidget(); + } + + @override + Widget _buildAdditionalControlsForPage(BuildContext context) { + if (!isAdditionalControls) { + return Container(height: 0.0, width: 0.0); + } else { + return LightControlsWidget(); + } + } + +} \ No newline at end of file diff --git a/lib/entity_class/select_entity.class.dart b/lib/entity_class/select_entity.class.dart new file mode 100644 index 0000000..636a74f --- /dev/null +++ b/lib/entity_class/select_entity.class.dart @@ -0,0 +1,14 @@ +part of '../main.dart'; + +class SelectEntity extends Entity { + List get listOptions => attributes["options"] != null + ? (attributes["options"] as List).cast() + : []; + + SelectEntity(Map rawData) : super(rawData); + + @override + Widget _buildStatePart(BuildContext context) { + return SelectStateWidget(); + } +} \ No newline at end of file diff --git a/lib/entity_class/sun_entity.class.dart b/lib/entity_class/sun_entity.class.dart new file mode 100644 index 0000000..b83768b --- /dev/null +++ b/lib/entity_class/sun_entity.class.dart @@ -0,0 +1,41 @@ +part of '../main.dart'; + +class SunEntity extends Entity { + SunEntity(Map rawData) : super(rawData); +} + +class SliderEntity extends Entity { + SliderEntity(Map rawData) : super(rawData); + + 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; + + @override + Widget _buildStatePart(BuildContext context) { + return Expanded( + //width: 200.0, + child: Row( + children: [ + SliderStateWidget( + expanded: true, + ), + SimpleEntityState(), + ], + ), + ); + } + + @override + Widget _buildStatePartForPage(BuildContext context) { + return SimpleEntityState(); + } + + @override + Widget _buildAdditionalControlsForPage(BuildContext context) { + return SliderStateWidget( + expanded: false, + ); + } +} \ No newline at end of file diff --git a/lib/entity_class/switch_entity.class.dart b/lib/entity_class/switch_entity.class.dart new file mode 100644 index 0000000..f6aee40 --- /dev/null +++ b/lib/entity_class/switch_entity.class.dart @@ -0,0 +1,10 @@ +part of '../main.dart'; + +class SwitchEntity extends Entity { + SwitchEntity(Map rawData) : super(rawData); + + @override + Widget _buildStatePart(BuildContext context) { + return SwitchStateWidget(); + } +} \ No newline at end of file diff --git a/lib/entity_class/text_entity.class.dart b/lib/entity_class/text_entity.class.dart new file mode 100644 index 0000000..7230c84 --- /dev/null +++ b/lib/entity_class/text_entity.class.dart @@ -0,0 +1,16 @@ +part of '../main.dart'; + +class TextEntity extends Entity { + TextEntity(Map rawData) : super(rawData); + + 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(); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/badge.dart b/lib/entity_widgets/badge.dart new file mode 100644 index 0000000..3d7dcf2 --- /dev/null +++ b/lib/entity_widgets/badge.dart @@ -0,0 +1,125 @@ +part of '../main.dart'; + +class BadgeWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + final entityModel = EntityModel.of(context); + double iconSize = 26.0; + Widget badgeIcon; + String onBadgeTextValue; + Color iconColor = Entity.badgeColors[entityModel.entity.domain] ?? + Entity.badgeColors["default"]; + switch (entityModel.entity.domain) { + case "sun": + { + badgeIcon = entityModel.entity.state == "below_horizon" + ? Icon( + MaterialDesignIcons.createIconDataFromIconCode(0xf0dc), + size: iconSize, + ) + : Icon( + MaterialDesignIcons.createIconDataFromIconCode(0xf5a8), + size: iconSize, + ); + break; + } + case "sensor": + { + onBadgeTextValue = entityModel.entity.unitOfMeasurement; + badgeIcon = Center( + child: Text( + "${entityModel.entity.state}", + overflow: TextOverflow.fade, + softWrap: false, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 17.0), + ), + ); + break; + } + case "device_tracker": + { + badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData( + entityModel.entity, iconSize, Colors.black); + onBadgeTextValue = entityModel.entity.state; + break; + } + default: + { + badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData( + entityModel.entity, iconSize, Colors.black); + } + } + Widget onBadgeText; + if (onBadgeTextValue == null || onBadgeTextValue.length == 0) { + onBadgeText = Container(width: 0.0, height: 0.0); + } else { + onBadgeText = Container( + padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0), + child: Text("$onBadgeTextValue", + style: TextStyle(fontSize: 12.0, color: Colors.white), + textAlign: TextAlign.center, + softWrap: false, + overflow: TextOverflow.fade), + decoration: new BoxDecoration( + // Circle shape + //shape: BoxShape.circle, + color: iconColor, + borderRadius: BorderRadius.circular(9.0), + )); + } + return GestureDetector( + child: Column( + children: [ + Container( + margin: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0), + width: 50.0, + height: 50.0, + decoration: new BoxDecoration( + // Circle shape + shape: BoxShape.circle, + color: Colors.white, + // The border you want + border: new Border.all( + width: 2.0, + color: iconColor, + ), + ), + child: Stack( + overflow: Overflow.visible, + children: [ + Positioned( + width: 46.0, + height: 46.0, + top: 0.0, + left: 0.0, + child: badgeIcon, + ), + Positioned( + //width: 50.0, + bottom: -9.0, + left: -10.0, + right: -10.0, + child: Center( + child: onBadgeText, + )) + ], + ), + ), + Container( + width: 60.0, + child: Text( + "${entityModel.entity.displayName}", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 12.0), + softWrap: true, + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + onTap: () => + eventBus.fire(new ShowEntityPageEvent(entityModel.entity))); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/controls/climate_controls.dart b/lib/entity_widgets/controls/climate_controls.dart new file mode 100644 index 0000000..909526f --- /dev/null +++ b/lib/entity_widgets/controls/climate_controls.dart @@ -0,0 +1,467 @@ +part of '../../main.dart'; + +class ClimateControlWidget extends StatefulWidget { + + ClimateControlWidget({Key key}) : super(key: key); + + @override + _ClimateControlWidgetState createState() => _ClimateControlWidgetState(); +} + +class _ClimateControlWidgetState extends State { + + bool _showPending = false; + bool _changedHere = false; + Timer _resetTimer; + double _tmpTemperature = 0.0; + double _tmpTargetLow = 0.0; + double _tmpTargetHigh = 0.0; + double _tmpTargetHumidity = 0.0; + String _tmpOperationMode; + String _tmpFanMode; + String _tmpSwingMode; + bool _tmpAwayMode = false; + bool _tmpIsOff = false; + bool _tmpAuxHeat = false; + + void _resetVars(ClimateEntity entity) { + _tmpTemperature = entity.temperature; + _tmpTargetHigh = entity.targetHigh; + _tmpTargetLow = entity.targetLow; + _tmpOperationMode = entity.operationMode; + _tmpFanMode = entity.fanMode; + _tmpSwingMode = entity.swingMode; + _tmpAwayMode = entity.awayMode; + _tmpIsOff = entity.isOff; + _tmpAuxHeat = entity.auxHeat; + _tmpTargetHumidity = entity.targetHumidity; + + _showPending = false; + _changedHere = false; + } + + void _temperatureUp(ClimateEntity entity, double step) { + _tmpTemperature = ((_tmpTemperature + step) <= entity.maxTemp) ? _tmpTemperature + step : entity.maxTemp; + _setTemperature(entity); + } + + void _temperatureDown(ClimateEntity entity, double step) { + _tmpTemperature = ((_tmpTemperature - step) >= entity.minTemp) ? _tmpTemperature - step : entity.minTemp; + _setTemperature(entity); + } + + void _targetLowUp(ClimateEntity entity, double step) { + _tmpTargetLow = ((_tmpTargetLow + step) <= entity.maxTemp) ? _tmpTargetLow + step : entity.maxTemp; + _setTargetTemp(entity); + } + + void _targetLowDown(ClimateEntity entity, double step) { + _tmpTargetLow = ((_tmpTargetLow - step) >= entity.minTemp) ? _tmpTargetLow - step : entity.minTemp; + _setTargetTemp(entity); + } + + void _targetHighUp(ClimateEntity entity, double step) { + _tmpTargetHigh = ((_tmpTargetHigh + step) <= entity.maxTemp) ? _tmpTargetHigh + step : entity.maxTemp; + _setTargetTemp(entity); + } + + void _targetHighDown(ClimateEntity entity, double step) { + _tmpTargetHigh = ((_tmpTargetHigh - step) >= entity.minTemp) ? _tmpTargetHigh - step : entity.minTemp; + _setTargetTemp(entity); + } + + void _setTemperature(ClimateEntity entity) { + setState(() { + _tmpTemperature = double.parse(_tmpTemperature.toStringAsFixed(1)); + _changedHere = true; + eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"temperature": "${_tmpTemperature.toStringAsFixed(1)}"})); + _resetStateTimer(entity); + }); + } + + void _setTargetTemp(ClimateEntity entity) { + setState(() { + _tmpTargetLow = double.parse(_tmpTargetLow.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)}"})); + _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 _setOperationMode(ClimateEntity entity, value) { + setState(() { + _tmpOperationMode = value; + _changedHere = true; + eventBus.fire(new ServiceCallEvent(entity.domain, "set_operation_mode", entity.entityId,{"operation_mode": "$_tmpOperationMode"})); + _resetStateTimer(entity); + }); + } + + void _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 _setAwayMode(ClimateEntity entity, value) { + setState(() { + _tmpAwayMode = value; + _changedHere = true; + eventBus.fire(new ServiceCallEvent(entity.domain, "set_away_mode", entity.entityId,{"away_mode": "${_tmpAwayMode ? 'on' : 'off'}"})); + _resetStateTimer(entity); + }); + } + + void _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.entity; + if (_changedHere) { + _showPending = (_tmpTemperature != entity.temperature); + _changedHere = false; + } else { + _resetTimer?.cancel(); + _resetVars(entity); + } + return Padding( + padding: EdgeInsets.fromLTRB(Entity.leftWidgetPadding, Entity.rowPadding, Entity.rightWidgetPadding, 0.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildOnOffControl(entity), + _buildTemperatureControls(entity), + _buildTargetTemperatureControls(entity), + _buildHumidityControls(entity), + _buildOperationControl(entity), + _buildFanControl(entity), + _buildSwingControl(entity), + _buildAwayModeControl(entity), + _buildAuxHeatControl(entity) + ], + ), + ); + } + + Widget _buildAwayModeControl(ClimateEntity entity) { + if (entity.supportAwayMode) { + return ModeSwitchWidget( + caption: "Away mode", + onChange: (value) => _setAwayMode(entity, value), + value: _tmpAwayMode, + ); + } 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.supportOperationMode) { + return ModeSelectorWidget( + onChange: (mode) => _setOperationMode(entity, mode), + options: entity.operationList, + caption: "Operation", + value: _tmpOperationMode, + ); + } else { + return Container(height: 0.0, width: 0.0); + } + } + + Widget _buildFanControl(ClimateEntity entity) { + if (entity.supportFanMode) { + return ModeSelectorWidget( + options: entity.fanList, + 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.swingList, + 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: [ + Text("Target temperature", style: TextStyle( + fontSize: Entity.stateFontSize + )), + TemperatureControlWidget( + value: _tmpTemperature, + fontColor: _showPending ? Colors.red : Colors.black, + onLargeDec: () => _temperatureDown(entity, 0.5), + onLargeInc: () => _temperatureUp(entity, 0.5), + onSmallDec: () => _temperatureDown(entity, 0.1), + onSmallInc: () => _temperatureUp(entity, 0.1), + ) + ], + ); + } else { + return Container(width: 0.0, height: 0.0,); + } + } + + Widget _buildTargetTemperatureControls(ClimateEntity entity) { + List controls = []; + if ((entity.supportTargetTemperatureLow) && (entity.targetLow != null)) { + controls.addAll([ + TemperatureControlWidget( + value: _tmpTargetLow, + fontColor: _showPending ? Colors.red : Colors.black, + onLargeDec: () => _targetLowDown(entity, 0.5), + onLargeInc: () => _targetLowUp(entity, 0.5), + onSmallDec: () => _targetLowDown(entity, 0.1), + onSmallInc: () => _targetLowUp(entity, 0.1), + ), + Expanded( + child: Container(height: 10.0), + ) + ]); + } + if ((entity.supportTargetTemperatureHigh) && (entity.targetHigh != null)) { + controls.add( + TemperatureControlWidget( + value: _tmpTargetHigh, + fontColor: _showPending ? Colors.red : Colors.black, + onLargeDec: () => _targetHighDown(entity, 0.5), + onLargeInc: () => _targetHighUp(entity, 0.5), + onSmallDec: () => _targetHighDown(entity, 0.1), + onSmallInc: () => _targetHighUp(entity, 0.1), + ) + ); + } + if (controls.isNotEmpty) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Target temperature range", style: TextStyle( + fontSize: Entity.stateFontSize + )), + Row( + children: controls, + ) + ], + ); + } else { + return Container(width: 0.0, height: 0.0); + } + } + + Widget _buildHumidityControls(ClimateEntity entity) { + List result = []; + if (entity.supportTargetHumidity) { + result.addAll([ + Text( + "$_tmpTargetHumidity%", + style: TextStyle(fontSize: Entity.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: [ + Padding( + padding: EdgeInsets.fromLTRB( + 0.0, Entity.rowPadding, 0.0, Entity.rowPadding), + child: Text("Target humidity", style: TextStyle( + fontSize: Entity.stateFontSize + )), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: result, + ), + Container( + height: Entity.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 onSmallInc; + final onLargeInc; + final onSmallDec; + final onLargeDec; + + TemperatureControlWidget( + {Key key, + @required this.value, + @required this.onSmallInc, + @required this.onSmallDec, + @required this.onLargeInc, + @required this.onLargeDec, + this.fontSize, + this.fontColor}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "$value", + style: TextStyle( + fontSize: fontSize ?? 24.0, + color: fontColor ?? Colors.black + ), + ), + Column( + children: [ + IconButton( + icon: Icon(MaterialDesignIcons.createIconDataFromIconName( + 'mdi:chevron-up')), + iconSize: 30.0, + onPressed: () => onSmallInc(), + ), + IconButton( + icon: Icon(MaterialDesignIcons.createIconDataFromIconName( + 'mdi:chevron-down')), + iconSize: 30.0, + onPressed: () => onSmallDec(), + ) + ], + ), + Column( + children: [ + 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(), + ) + ], + ) + ], + ); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/controls/cover_controls.dart b/lib/entity_widgets/controls/cover_controls.dart new file mode 100644 index 0000000..ff21d4b --- /dev/null +++ b/lib/entity_widgets/controls/cover_controls.dart @@ -0,0 +1,201 @@ +part of '../../main.dart'; + +class CoverControlWidget extends StatefulWidget { + + CoverControlWidget({Key key}) : super(key: key); + + @override + _CoverControlWidgetState createState() => _CoverControlWidgetState(); +} + +class _CoverControlWidgetState extends State { + + 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.entity; + TheLogger.debug("${entity.state}"); + if (_changedHere) { + _changedHere = false; + } else { + _resetVars(entity); + } + return Padding( + padding: EdgeInsets.fromLTRB(Entity.leftWidgetPadding, Entity.rowPadding, Entity.rightWidgetPadding, 0.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildPositionControls(entity), + _buildTiltControls(entity) + ], + ), + ); + } + + Widget _buildPositionControls(CoverEntity entity) { + if (entity.supportSetPosition) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB( + 0.0, Entity.rowPadding, 0.0, Entity.rowPadding), + child: Text("Position", style: TextStyle( + fontSize: Entity.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: Entity.rowPadding,) + ], + ); + } else { + return Container(width: 0.0, height: 0.0); + } + } + + Widget _buildTiltControls(CoverEntity entity) { + List controls = []; + if (entity.supportCloseTilt || entity.supportOpenTilt || entity.supportStopTilt) { + controls.add( + CoverTiltControlsWidget() + ); + } + if (entity.supportSetTiltPosition) { + controls.addAll([ + 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: Entity.rowPadding,) + ]); + } + if (controls.isNotEmpty) { + controls.insert(0, Padding( + padding: EdgeInsets.fromLTRB( + 0.0, Entity.rowPadding, 0.0, Entity.rowPadding), + child: Text("Tilt position", style: TextStyle( + fontSize: Entity.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.entity; + List buttons = []; + if (entity.supportOpenTilt) { + buttons.add(IconButton( + icon: Icon( + MaterialDesignIcons.createIconDataFromIconName( + "mdi:arrow-top-right"), + size: Entity.iconSize, + ), + onPressed: entity.canTiltBeOpened ? () => _open(entity) : null)); + } else { + buttons.add(Container( + width: Entity.iconSize + 20.0, + )); + } + if (entity.supportStopTilt) { + buttons.add(IconButton( + icon: Icon( + MaterialDesignIcons.createIconDataFromIconName("mdi:stop"), + size: Entity.iconSize, + ), + onPressed: () => _stop(entity))); + } else { + buttons.add(Container( + width: Entity.iconSize + 20.0, + )); + } + if (entity.supportCloseTilt) { + buttons.add(IconButton( + icon: Icon( + MaterialDesignIcons.createIconDataFromIconName( + "mdi:arrow-bottom-left"), + size: Entity.iconSize, + ), + onPressed: entity.canTiltBeClosed ? () => _close(entity) : null)); + } else { + buttons.add(Container( + width: Entity.iconSize + 20.0, + )); + } + + return Row( + children: buttons, + ); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/controls/light_controls.dart b/lib/entity_widgets/controls/light_controls.dart new file mode 100644 index 0000000..6ce600a --- /dev/null +++ b/lib/entity_widgets/controls/light_controls.dart @@ -0,0 +1,240 @@ +part of '../../main.dart'; + +class LightControlsWidget extends StatefulWidget { + + @override + _LightControlsWidgetState createState() => _LightControlsWidgetState(); + +} + +class _LightControlsWidgetState extends State { + + int _tmpBrightness; + int _tmpColorTemp; + Color _tmpColor; + bool _changedHere = false; + String _tmpEffect; + + void _resetState(LightEntity entity) { + _tmpBrightness = entity.brightness ?? 0; + _tmpColorTemp = entity.colorTemp; + _tmpColor = entity.color; + _tmpEffect = null; + } + + void _setBrightness(LightEntity entity, double value) { + setState(() { + _tmpBrightness = value.round(); + _changedHere = true; + if (_tmpBrightness > 0) { + eventBus.fire(new ServiceCallEvent( + entity.domain, "turn_on", entity.entityId, + {"brightness": _tmpBrightness})); + } else { + eventBus.fire(new ServiceCallEvent( + entity.domain, "turn_off", entity.entityId, + null)); + } + }); + } + + 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, Color color) { + setState(() { + _tmpColor = color; + _changedHere = true; + TheLogger.debug( "Color: [${color.red}, ${color.green}, ${color.blue}]"); + if ((color == Colors.black) || ((color.red == color.green) && (color.green == color.blue))) { + eventBus.fire(new ServiceCallEvent( + entity.domain, "turn_off", entity.entityId, + null)); + } else { + eventBus.fire(new ServiceCallEvent( + entity.domain, "turn_on", entity.entityId, + {"rgb_color": [color.red, color.green, color.blue]})); + } + }); + } + + 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.entity; + if (!_changedHere) { + _resetState(entity); + } else { + _changedHere = false; + } + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildBrightnessControl(entity), + _buildColorTempControl(entity), + _buildColorControl(entity), + _buildEffectControl(entity) + ], + ); + } + + Widget _buildBrightnessControl(LightEntity entity) { + if ((entity.supportBrightness) && (_tmpBrightness != null)) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container(height: Entity.rowPadding,), + Text( + "Brightness", + style: TextStyle(fontSize: Entity.stateFontSize), + ), + Container(height: Entity.rowPadding,), + Row( + children: [ + Icon(Icons.brightness_5), + Expanded( + child: Slider( + value: _tmpBrightness.toDouble(), + min: 0.0, + max: 255.0, + onChanged: (value) { + setState(() { + _changedHere = true; + _tmpBrightness = value.round(); + }); + }, + onChangeEnd: (value) => _setBrightness(entity, value), + ), + ) + ], + ), + Container(height: Entity.rowPadding,) + ], + ); + } else { + return Container(width: 0.0, height: 0.0); + } + } + + Widget _buildColorTempControl(LightEntity entity) { + if ((entity.supportColorTemp) && (_tmpColorTemp != null)) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container(height: Entity.rowPadding,), + Text( + "Color temperature", + style: TextStyle(fontSize: Entity.stateFontSize), + ), + Container(height: Entity.rowPadding,), + Row( + children: [ + Text("Cold", style: TextStyle(color: Colors.lightBlue),), + Expanded( + child: Slider( + value: _tmpColorTemp.toDouble(), + min: entity.minMireds, + max: entity.maxMireds, + onChanged: (value) { + setState(() { + _changedHere = true; + _tmpColorTemp = value.round(); + }); + }, + onChangeEnd: (value) => _setColorTemp(entity, value), + ), + ), + Text("Warm", style: TextStyle(color: Colors.amberAccent),), + ], + ), + Container(height: Entity.rowPadding,) + ], + ); + } else { + return Container(width: 0.0, height: 0.0); + } + } + + Widget _buildColorControl(LightEntity entity) { + if ((entity.supportColor) && (entity.color != null)) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container(height: Entity.rowPadding,), + RaisedButton( + onPressed: () => _showColorPicker(entity), + color: _tmpColor ?? Colors.black45, + child: Text( + "COLOR", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 50.0, + fontWeight: FontWeight.bold, + color: Colors.black12, + ), + ), + ), + Container(height: 2*Entity.rowPadding,), + ], + ); + } else { + return Container(width: 0.0, height: 0.0); + } + } + + void _showColorPicker(LightEntity entity) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + titlePadding: EdgeInsets.all(0.0), + contentPadding: EdgeInsets.all(0.0), + content: SingleChildScrollView( + child: MaterialPicker( + pickerColor: _tmpColor, + onColorChanged: (color) { + _setColor(entity, color); + Navigator.of(context).pop(); + }, + enableLabel: true, + ), + ), + ); + }, + ); + } + + 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); + } + } + + +} \ No newline at end of file diff --git a/lib/entity_widgets/default_entity_container.dart b/lib/entity_widgets/default_entity_container.dart new file mode 100644 index 0000000..5e9a8ab --- /dev/null +++ b/lib/entity_widgets/default_entity_container.dart @@ -0,0 +1,28 @@ +part of '../main.dart'; + +class DefaultEntityContainer extends StatelessWidget { + DefaultEntityContainer({ + Key key, + @required this.state, + @required this.height + }) : super(key: key); + + final Widget state; + final double height; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: height, + child: Row( + children: [ + EntityIcon(), + Expanded( + child: EntityName(), + ), + state + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/entity_attributes_list.dart b/lib/entity_widgets/entity_attributes_list.dart new file mode 100644 index 0000000..e515fb9 --- /dev/null +++ b/lib/entity_widgets/entity_attributes_list.dart @@ -0,0 +1,57 @@ +part of '../main.dart'; + +class EntityAttributesList extends StatelessWidget { + EntityAttributesList({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final entityModel = EntityModel.of(context); + List attrs = []; + if ((entityModel.entity.attributesToShow == null) || + (entityModel.entity.attributesToShow.contains("all"))) { + entityModel.entity.attributes.forEach((name, value) { + attrs.add(_buildSingleAttribute("$name", "$value")); + }); + } else { + entityModel.entity.attributesToShow.forEach((String attr) { + String attrValue = entityModel.entity.getAttribute("$attr"); + if (attrValue != null) { + attrs.add( + _buildSingleAttribute("$attr", "$attrValue")); + } + }); + } + return Column( + children: attrs, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + ); + } + + Widget _buildSingleAttribute(String name, String value) { + return Row( + children: [ + Expanded( + child: Padding( + padding: EdgeInsets.fromLTRB( + Entity.leftWidgetPadding, Entity.rowPadding, 0.0, 0.0), + child: Text( + "$name", + textAlign: TextAlign.left, + ), + ), + ), + Expanded( + child: Padding( + padding: EdgeInsets.fromLTRB( + 0.0, Entity.rowPadding, Entity.rightWidgetPadding, 0.0), + child: Text( + "$value", + textAlign: TextAlign.right, + ), + ), + ) + ], + ); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/entity_icon.dart b/lib/entity_widgets/entity_icon.dart new file mode 100644 index 0000000..761f632 --- /dev/null +++ b/lib/entity_widgets/entity_icon.dart @@ -0,0 +1,22 @@ +part of '../main.dart'; + +class EntityIcon extends StatelessWidget { + @override + Widget build(BuildContext context) { + final entityModel = EntityModel.of(context); + return GestureDetector( + child: Padding( + padding: EdgeInsets.fromLTRB( + Entity.leftWidgetPadding, 0.0, 12.0, 0.0), + child: MaterialDesignIcons.createIconWidgetFromEntityData( + entityModel.entity, + Entity.iconSize, + Entity.STATE_ICONS_COLORS[entityModel.entity.state] ?? + Entity.STATE_ICONS_COLORS["default"]), + ), + onTap: () => entityModel.handleTap + ? eventBus.fire(new ShowEntityPageEvent(entityModel.entity)) + : null, + ); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/entity_model.dart b/lib/entity_widgets/entity_model.dart new file mode 100644 index 0000000..da4119d --- /dev/null +++ b/lib/entity_widgets/entity_model.dart @@ -0,0 +1,22 @@ +part of '../main.dart'; + +class EntityModel extends InheritedWidget { + const EntityModel({ + Key key, + @required this.entity, + @required this.handleTap, + @required Widget child, + }) : super(key: key, child: child); + + final Entity entity; + final bool handleTap; + + static EntityModel of(BuildContext context) { + return context.inheritFromWidgetOfExactType(EntityModel); + } + + @override + bool updateShouldNotify(InheritedWidget oldWidget) { + return true; + } +} \ No newline at end of file diff --git a/lib/entity_widgets/entity_name.dart b/lib/entity_widgets/entity_name.dart new file mode 100644 index 0000000..d759988 --- /dev/null +++ b/lib/entity_widgets/entity_name.dart @@ -0,0 +1,23 @@ +part of '../main.dart'; + +class EntityName extends StatelessWidget { + @override + Widget build(BuildContext context) { + final entityModel = EntityModel.of(context); + return GestureDetector( + child: Padding( + padding: EdgeInsets.only(right: 10.0), + child: Text( + "${entityModel.entity.displayName}", + overflow: TextOverflow.ellipsis, + softWrap: false, + style: TextStyle(fontSize: Entity.nameFontSize), + ), + ), + onTap: () => + entityModel.handleTap + ? eventBus.fire(new ShowEntityPageEvent(entityModel.entity)) + : null, + ); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/entity_page_container.dart b/lib/entity_widgets/entity_page_container.dart new file mode 100644 index 0000000..2b09eff --- /dev/null +++ b/lib/entity_widgets/entity_page_container.dart @@ -0,0 +1,14 @@ +part of '../main.dart'; + +class EntityPageContainer extends StatelessWidget { + EntityPageContainer({Key key, @required this.children}) : super(key: key); + + final List children; + + @override + Widget build(BuildContext context) { + return ListView( + children: children, + ); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/last_updated.dart b/lib/entity_widgets/last_updated.dart new file mode 100644 index 0000000..d777777 --- /dev/null +++ b/lib/entity_widgets/last_updated.dart @@ -0,0 +1,18 @@ +part of '../main.dart'; + +class LastUpdatedWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + final entityModel = EntityModel.of(context); + return Padding( + padding: EdgeInsets.fromLTRB( + Entity.leftWidgetPadding, 0.0, 0.0, 0.0), + child: Text( + '${entityModel.entity.lastUpdated}', + textAlign: TextAlign.left, + style: TextStyle( + fontSize: Entity.smallFontSize, color: Colors.black26), + ), + ); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/mode_selector.dart b/lib/entity_widgets/mode_selector.dart new file mode 100644 index 0000000..49de2c6 --- /dev/null +++ b/lib/entity_widgets/mode_selector.dart @@ -0,0 +1,62 @@ +part of '../main.dart'; + +class ModeSelectorWidget extends StatelessWidget { + + final String caption; + final List options; + final String value; + final double captionFontSize; + final double valueFontSize; + final double bottomPadding; + final onChange; + + ModeSelectorWidget({ + Key key, + this.caption, + @required this.options, + this.value, + @required this.onChange, + this.captionFontSize, + this.valueFontSize, + this.bottomPadding + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("$caption", style: TextStyle( + fontSize: captionFontSize ?? Entity.stateFontSize + )), + Row( + children: [ + Expanded( + child: ButtonTheme( + alignedDropdown: true, + child: DropdownButton( + value: value, + iconSize: 30.0, + isExpanded: true, + style: TextStyle( + fontSize: valueFontSize ?? Entity.largeFontSize, + color: Colors.black, + ), + hint: Text("Select ${caption.toLowerCase()}"), + items: options.map((String value) { + return new DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + onChanged: (mode) => onChange(mode), + ), + ), + ) + ], + ), + Container(height: bottomPadding ?? Entity.rowPadding,) + ], + ); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/mode_swicth.dart b/lib/entity_widgets/mode_swicth.dart new file mode 100644 index 0000000..e2fef6a --- /dev/null +++ b/lib/entity_widgets/mode_swicth.dart @@ -0,0 +1,38 @@ +part of '../main.dart'; + +class ModeSwitchWidget extends StatelessWidget { + + final String caption; + final onChange; + final double captionFontSize; + final bool value; + + ModeSwitchWidget({ + Key key, + @required this.caption, + @required this.onChange, + this.captionFontSize, + this.value + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Text( + "$caption", + style: TextStyle( + fontSize: captionFontSize ?? Entity.stateFontSize + ), + ), + ), + Switch( + onChanged: (value) => onChange(value), + value: value ?? false, + ) + ], + ); + } + +} \ No newline at end of file diff --git a/lib/entity_widgets/state/button_state.dart b/lib/entity_widgets/state/button_state.dart new file mode 100644 index 0000000..4f9acb5 --- /dev/null +++ b/lib/entity_widgets/state/button_state.dart @@ -0,0 +1,24 @@ +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 FlatButton( + onPressed: (() { + _setNewState(entityModel.entity); + }), + child: Text( + "EXECUTE", + textAlign: TextAlign.right, + style: + new TextStyle(fontSize: Entity.stateFontSize, color: Colors.blue), + ), + ); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/state/climate_state.dart b/lib/entity_widgets/state/climate_state.dart new file mode 100644 index 0000000..961ac8e --- /dev/null +++ b/lib/entity_widgets/state/climate_state.dart @@ -0,0 +1,57 @@ +part of '../../main.dart'; + +class ClimateStateWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + final entityModel = EntityModel.of(context); + final ClimateEntity entity = entityModel.entity; + String targetTemp = "-"; + if ((entity.supportTargetTemperature) && (entity.temperature != null)) { + targetTemp = "${entity.temperature}"; + } else if ((entity.supportTargetTemperatureLow) && + (entity.targetLow != null)) { + targetTemp = "${entity.targetLow}"; + if ((entity.supportTargetTemperatureHigh) && + (entity.targetHigh != null)) { + targetTemp += " - ${entity.targetHigh}"; + } + } + return Padding( + padding: EdgeInsets.fromLTRB( + 0.0, 0.0, Entity.rightWidgetPadding, 0.0), + child: GestureDetector( + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Text("${entity.state}", + textAlign: TextAlign.right, + style: new TextStyle( + fontWeight: FontWeight.bold, + fontSize: Entity.stateFontSize, + )), + Text(" $targetTemp", + textAlign: TextAlign.right, + style: new TextStyle( + fontSize: Entity.stateFontSize, + )) + ], + ), + entity.attributes["current_temperature"] != null ? + Text("Currently: ${entity.attributes["current_temperature"]}", + textAlign: TextAlign.right, + style: new TextStyle( + fontSize: Entity.stateFontSize, + color: Colors.black45) + ) : + Container(height: 0.0,) + ], + ), + onTap: () => entityModel.handleTap + ? eventBus.fire(new ShowEntityPageEvent(entity)) + : null, + )); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/state/cover_state.dart b/lib/entity_widgets/state/cover_state.dart new file mode 100644 index 0000000..f565554 --- /dev/null +++ b/lib/entity_widgets/state/cover_state.dart @@ -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.entity; + List buttons = []; + if (entity.supportOpen) { + buttons.add(IconButton( + icon: Icon( + MaterialDesignIcons.createIconDataFromIconName("mdi:arrow-up"), + size: Entity.iconSize, + ), + onPressed: entity.canBeOpened ? () => _open(entity) : null)); + } else { + buttons.add(Container( + width: Entity.iconSize + 20.0, + )); + } + if (entity.supportStop) { + buttons.add(IconButton( + icon: Icon( + MaterialDesignIcons.createIconDataFromIconName("mdi:stop"), + size: Entity.iconSize, + ), + onPressed: () => _stop(entity))); + } else { + buttons.add(Container( + width: Entity.iconSize + 20.0, + )); + } + if (entity.supportClose) { + buttons.add(IconButton( + icon: Icon( + MaterialDesignIcons.createIconDataFromIconName("mdi:arrow-down"), + size: Entity.iconSize, + ), + onPressed: entity.canBeClosed ? () => _close(entity) : null)); + } else { + buttons.add(Container( + width: Entity.iconSize + 20.0, + )); + } + + return Row( + children: buttons, + ); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/state/date_time_state.dart b/lib/entity_widgets/state/date_time_state.dart new file mode 100644 index 0000000..12a49a2 --- /dev/null +++ b/lib/entity_widgets/state/date_time_state.dart @@ -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.entity; + return Padding( + padding: EdgeInsets.fromLTRB(0.0, 0.0, Entity.rightWidgetPadding, 0.0), + child: GestureDetector( + child: Text("${entity.formattedState}", + textAlign: TextAlign.right, + style: new TextStyle( + fontSize: Entity.stateFontSize, + )), + onTap: () => _handleStateTap(context, entity), + )); + } + + void _handleStateTap(BuildContext context, DateTimeEntity entity) { + if (entity.hasDate) { + _showDatePicker(context, entity).then((date) { + if (date != null) { + if (entity.hasTime) { + _showTimePicker(context, entity).then((time) { + entity.setNewState({ + "date": "${formatDate(date, [yyyy, '-', mm, '-', dd])}", + "time": + "${formatDate(DateTime(1970, 1, 1, time.hour, time.minute), [ + HH, + ':', + nn + ])}" + }); + }); + } else { + entity.setNewState({ + "date": "${formatDate(date, [yyyy, '-', mm, '-', dd])}" + }); + } + } + }); + } else if (entity.hasTime) { + _showTimePicker(context, entity).then((time) { + if (time != null) { + entity.setNewState({ + "time": + "${formatDate(DateTime(1970, 1, 1, time.hour, time.minute), [ + HH, + ':', + nn + ])}" + }); + } + }); + } else { + TheLogger.warning( "${entity.entityId} has no date and no time"); + } + } + + Future _showDatePicker(BuildContext context, DateTimeEntity entity) { + return showDatePicker( + context: context, + initialDate: entity.dateTimeState, + firstDate: DateTime(1970), + lastDate: DateTime(2037) //Unix timestamp will finish at Jan 19, 2038 + ); + } + + Future _showTimePicker(BuildContext context, DateTimeEntity entity) { + return showTimePicker( + context: context, + initialTime: TimeOfDay.fromDateTime(entity.dateTimeState)); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/state/select_state.dart b/lib/entity_widgets/state/select_state.dart new file mode 100644 index 0000000..0f15457 --- /dev/null +++ b/lib/entity_widgets/state/select_state.dart @@ -0,0 +1,46 @@ +part of '../../main.dart'; + +class SelectStateWidget extends StatefulWidget { + + SelectStateWidget({Key key}) : super(key: key); + + @override + _SelectStateWidgetState createState() => _SelectStateWidgetState(); +} + +class _SelectStateWidgetState extends State { + + void setNewState(domain, entityId, newValue) { + eventBus.fire(new ServiceCallEvent(domain, "select_option", entityId, + {"option": "$newValue"})); + } + + @override + Widget build(BuildContext context) { + final entityModel = EntityModel.of(context); + final SelectEntity entity = entityModel.entity; + Widget ctrl; + if (entity.listOptions.isNotEmpty) { + ctrl = DropdownButton( + value: entity.state, + items: entity.listOptions.map((String value) { + return new DropdownMenuItem( + value: value, + child: new Text(value), + ); + }).toList(), + onChanged: (_) { + setNewState(entity.domain, entity.entityId,_); + }, + ); + } else { + ctrl = Text('---'); + } + return Expanded( + //width: Entity.INPUT_WIDTH, + child: ctrl, + ); + } + + +} \ No newline at end of file diff --git a/lib/entity_widgets/state/simple_state.dart b/lib/entity_widgets/state/simple_state.dart new file mode 100644 index 0000000..6acbbae --- /dev/null +++ b/lib/entity_widgets/state/simple_state.dart @@ -0,0 +1,22 @@ +part of '../../main.dart'; + +class SimpleEntityState extends StatelessWidget { + @override + Widget build(BuildContext context) { + final entityModel = EntityModel.of(context); + return Padding( + padding: EdgeInsets.fromLTRB( + 0.0, 0.0, Entity.rightWidgetPadding, 0.0), + child: GestureDetector( + child: Text( + "${entityModel.entity.state}${entityModel.entity.unitOfMeasurement}", + textAlign: TextAlign.right, + style: new TextStyle( + fontSize: Entity.stateFontSize, + )), + onTap: () => entityModel.handleTap + ? eventBus.fire(new ShowEntityPageEvent(entityModel.entity)) + : null, + )); + } +} \ No newline at end of file diff --git a/lib/entity_widgets/state/slider_state.dart b/lib/entity_widgets/state/slider_state.dart new file mode 100644 index 0000000..512a1ad --- /dev/null +++ b/lib/entity_widgets/state/slider_state.dart @@ -0,0 +1,58 @@ +part of '../../main.dart'; + +class SliderStateWidget extends StatefulWidget { + + final bool expanded; + + SliderStateWidget({Key key, @required this.expanded}) : super(key: key); + + @override + _SliderStateWidgetState createState() => _SliderStateWidgetState(); +} + +class _SliderStateWidgetState extends State { + int _multiplier = 1; + + void setNewState(newValue, domain, entityId) { + eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId, + {"value": "${newValue.toString()}"})); + } + + @override + Widget build(BuildContext context) { + final entityModel = EntityModel.of(context); + final SliderEntity entity = entityModel.entity; + if (entity.valueStep < 1) { + _multiplier = 10; + } else if (entity.valueStep < 0.1) { + _multiplier = 100; + } + Widget slider = Slider( + min: entity.minValue * _multiplier, + max: entity.maxValue * _multiplier, + value: (entity.doubleState <= entity.maxValue) && + (entity.doubleState >= entity.minValue) + ? entity.doubleState * _multiplier + : entity.minValue * _multiplier, + onChanged: (value) { + setState(() { + entity.state = + (value.roundToDouble() / _multiplier).toString(); + }); + eventBus.fire(new StateChangedEvent(entity.entityId, + (value.roundToDouble() / _multiplier).toString(), true)); + + }, + onChangeEnd: (value) { + setNewState(value.roundToDouble() / _multiplier, entity.domain, entity.entityId); + }, + ); + if (widget.expanded) { + return Expanded( + child: slider, + ); + } else { + return slider; + } + } +} \ No newline at end of file diff --git a/lib/entity_widgets/state/switch_state.dart b/lib/entity_widgets/state/switch_state.dart new file mode 100644 index 0000000..449ecde --- /dev/null +++ b/lib/entity_widgets/state/switch_state.dart @@ -0,0 +1,60 @@ +part of '../../main.dart'; + +class SwitchStateWidget extends StatefulWidget { + @override + _SwitchStateWidgetState createState() => _SwitchStateWidgetState(); +} + +class _SwitchStateWidgetState extends State { + + @override + void initState() { + super.initState(); + } + + void _setNewState(newValue, Entity entity) { + setState(() { + entity.assumedState = newValue ? 'on' : 'off'; + }); + Timer(Duration(seconds: 2), (){ + setState(() { + entity.assumedState = entity.state; + }); + }); + eventBus.fire(new ServiceCallEvent( + entity.domain, (newValue as bool) ? "turn_on" : "turn_off", entity.entityId, null)); + } + + @override + Widget build(BuildContext context) { + final entityModel = EntityModel.of(context); + final entity = entityModel.entity; + if ((entity.attributes["assumed_state"] == null) || (entity.attributes["assumed_state"] == false)) { + return Switch( + value: entity.assumedState == 'on', + onChanged: ((switchState) { + _setNewState(switchState, entity); + }), + ); + } else { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IconButton( + onPressed: () => _setNewState(false, entity), + icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:flash-off")), + color: entity.assumedState == 'on' ? Colors.black : Colors.blue, + iconSize: Entity.iconSize, + ), + IconButton( + onPressed: () => _setNewState(true, entity), + icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:flash")), + color: entity.assumedState == 'on' ? Colors.blue : Colors.black, + iconSize: Entity.iconSize + ) + ], + ); + } + + } +} \ No newline at end of file diff --git a/lib/entity_widgets/state/text_input_state.dart b/lib/entity_widgets/state/text_input_state.dart new file mode 100644 index 0000000..0a06e9c --- /dev/null +++ b/lib/entity_widgets/state/text_input_state.dart @@ -0,0 +1,98 @@ +part of '../../main.dart'; + +class TextInputStateWidget extends StatefulWidget { + + TextInputStateWidget({Key key}) : super(key: key); + + @override + _TextInputStateWidgetState createState() => _TextInputStateWidgetState(); +} + +class _TextInputStateWidgetState extends State { + String _tmpValue; + String _entityState; + String _entityDomain; + String _entityId; + int _minLength; + int _maxLength; + FocusNode _focusNode = FocusNode(); + bool validValue = false; + + @override + void initState() { + super.initState(); + _focusNode.addListener(_focusListener); + } + + void setNewState(newValue, domain, entityId) { + if (validate(newValue, _minLength, _maxLength)) { + eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId, + {"value": "$newValue"})); + } else { + setState(() { + _tmpValue = _entityState; + }); + } + } + + bool validate(newValue, minLength, maxLength) { + if (newValue is String) { + validValue = (newValue.length >= minLength) && + (maxLength == -1 || + (newValue.length <= maxLength)); + } else { + validValue = true; + } + return validValue; + } + + void _focusListener() { + if (!_focusNode.hasFocus && (_tmpValue != _entityState)) { + setNewState(_tmpValue, _entityDomain, _entityId); + } + } + + @override + Widget build(BuildContext context) { + final entityModel = EntityModel.of(context); + final TextEntity entity = entityModel.entity; + _entityState = entity.state; + _entityDomain = entity.domain; + _entityId = entity.entityId; + _minLength = entity.valueMinLength; + _maxLength = entity.valueMaxLength; + + if (!_focusNode.hasFocus && (_tmpValue != entity.state)) { + _tmpValue = entity.state; + } + if (entity.isTextField || entity.isPasswordField) { + return Expanded( + //width: Entity.INPUT_WIDTH, + child: TextField( + focusNode: _focusNode, + obscureText: entity.isPasswordField, + controller: new TextEditingController.fromValue( + new TextEditingValue( + text: _tmpValue, + selection: + new TextSelection.collapsed(offset: _tmpValue.length) + ) + ), + onChanged: (value) { + _tmpValue = value; + }), + ); + } else { + TheLogger.warning( "Unsupported input mode for ${entity.entityId}"); + return SimpleEntityState(); + } + } + + @override + void dispose() { + _focusNode.removeListener(_focusListener); + _focusNode.dispose(); + super.dispose(); + } + +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index ef4c15f..3b51a78 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,9 +14,38 @@ import 'package:date_format/date_format.dart'; import 'package:http/http.dart' as http; import 'package:flutter_colorpicker/material_picker.dart'; -part 'entity.class.dart'; -part 'widgets/stateless_widgets.dart'; -part 'widgets/stateful_widgets.dart'; +part 'entity_class/entity.class.dart'; +part 'entity_class/switch_entity.class.dart'; +part 'entity_class/button_entity.class.dart'; +part 'entity_class/text_entity.class.dart'; +part 'entity_class/climate_entity.class.dart'; +part 'entity_class/cover_entity.class.dart'; +part 'entity_class/date_time_entity.class.dart'; +part 'entity_class/light_entity.class.dart'; +part 'entity_class/select_entity.class.dart'; +part 'entity_class/sun_entity.class.dart'; +part 'entity_widgets/badge.dart'; +part 'entity_widgets/entity_model.dart'; +part 'entity_widgets/default_entity_container.dart'; +part 'entity_widgets/entity_attributes_list.dart'; +part 'entity_widgets/entity_icon.dart'; +part 'entity_widgets/entity_name.dart'; +part 'entity_widgets/last_updated.dart'; +part 'entity_widgets/mode_swicth.dart'; +part 'entity_widgets/mode_selector.dart'; +part 'entity_widgets/entity_page_container.dart'; +part 'entity_widgets/state/switch_state.dart'; +part 'entity_widgets/state/slider_state.dart'; +part 'entity_widgets/state/text_input_state.dart'; +part 'entity_widgets/state/select_state.dart'; +part 'entity_widgets/state/simple_state.dart'; +part 'entity_widgets/state/climate_state.dart'; +part 'entity_widgets/state/cover_state.dart'; +part 'entity_widgets/state/date_time_state.dart'; +part 'entity_widgets/state/button_state.dart'; +part 'entity_widgets/controls/climate_controls.dart'; +part 'entity_widgets/controls/cover_controls.dart'; +part 'entity_widgets/controls/light_controls.dart'; part 'settings.page.dart'; part 'home_assistant.class.dart'; part 'log.page.dart'; @@ -24,7 +53,9 @@ part 'entity.page.dart'; part 'utils.class.dart'; part 'mdi.class.dart'; part 'entity_collection.class.dart'; -part 'ui.dart'; +part 'ui/ui.dart'; +part 'ui/view.class.dart'; +part 'ui/card.class.dart'; EventBus eventBus = new EventBus(); const String appName = "HA Client"; diff --git a/lib/ui/card.class.dart b/lib/ui/card.class.dart new file mode 100644 index 0000000..2b012f9 --- /dev/null +++ b/lib/ui/card.class.dart @@ -0,0 +1,74 @@ +part of '../main.dart'; + +class HACard { + List entities = []; + Entity linkedEntity; + String name; + String id; + + HACard({ + this.name, + this.id, + this.linkedEntity + }); + + Widget build(BuildContext context) { + return HACardWidget( + card: this, + ); + } + +} + +class HACardWidget extends StatelessWidget { + + final HACard card; + + const HACardWidget({ + Key key, + this.card + }) : super(key: key); + + @override + Widget build(BuildContext context) { + if ((card.linkedEntity!= null) && (card.linkedEntity.isHidden)) { + return Container(width: 0.0, height: 0.0,); + } + List body = []; + body.add(_buildCardHeader()); + body.addAll(_buildCardBody(context)); + return Card( + child: new Column(mainAxisSize: MainAxisSize.min, children: body) + ); + } + + Widget _buildCardHeader() { + var result; + if ((card.name != null) && (card.name.trim().length > 0)) { + result = new ListTile( + title: Text("${card.name}", + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis, + style: new TextStyle(fontWeight: FontWeight.bold, fontSize: 25.0)), + ); + } else { + result = new Container(width: 0.0, height: 0.0); + } + return result; + } + + List _buildCardBody(BuildContext context) { + List result = []; + card.entities.forEach((Entity entity) { + if (!entity.isHidden) { + result.add( + Padding( + padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0), + child: entity.buildDefaultWidget(context), + )); + } + }); + return result; + } + +} \ No newline at end of file diff --git a/lib/ui/ui.dart b/lib/ui/ui.dart new file mode 100644 index 0000000..c4f5a54 --- /dev/null +++ b/lib/ui/ui.dart @@ -0,0 +1,26 @@ +part of '../main.dart'; + +class HomeAssistantUI { + List views; + + HomeAssistantUI() { + views = []; + } + + Widget build(BuildContext context) { + return TabBarView( + children: _buildViews(context) + ); + } + + List _buildViews(BuildContext context) { + List result = []; + views.forEach((view) { + result.add( + view.build(context) + ); + }); + return result; + } + +} \ No newline at end of file diff --git a/lib/ui.dart b/lib/ui/view.class.dart similarity index 61% rename from lib/ui.dart rename to lib/ui/view.class.dart index 71933ce..4a31a8b 100644 --- a/lib/ui.dart +++ b/lib/ui/view.class.dart @@ -1,29 +1,4 @@ -part of 'main.dart'; - -class HomeAssistantUI { - List views; - - HomeAssistantUI() { - views = []; - } - - Widget build(BuildContext context) { - return TabBarView( - children: _buildViews(context) - ); - } - - List _buildViews(BuildContext context) { - List result = []; - views.forEach((view) { - result.add( - view.build(context) - ); - }); - return result; - } - -} +part of '../main.dart'; class HAView { List cards = []; @@ -80,28 +55,28 @@ class HAView { } Widget build(BuildContext context) { - return NewViewWidget( + return HAViewWidget( view: this, ); } } -class NewViewWidget extends StatefulWidget { +class HAViewWidget extends StatefulWidget { final HAView view; - const NewViewWidget({ + const HAViewWidget({ Key key, this.view }) : super(key: key); @override State createState() { - return NewViewWidgetState(); + return HAViewWidgetState(); } } -class NewViewWidgetState extends State { +class HAViewWidgetState extends State { StreamSubscription _refreshDataSubscription; Completer _refreshCompleter; @@ -145,7 +120,7 @@ class NewViewWidgetState extends State { widget.view.cards.forEach((HACard card){ result.add( card.build(context) - ); + ); }); return result; @@ -178,77 +153,4 @@ class NewViewWidgetState extends State { } -} - -class HACard { - List entities = []; - Entity linkedEntity; - String name; - String id; - - HACard({ - this.name, - this.id, - this.linkedEntity - }); - - Widget build(BuildContext context) { - return NewCardWidget( - card: this, - ); - } - -} - -class NewCardWidget extends StatelessWidget { - - final HACard card; - - const NewCardWidget({ - Key key, - this.card - }) : super(key: key); - - @override - Widget build(BuildContext context) { - if ((card.linkedEntity!= null) && (card.linkedEntity.isHidden)) { - return Container(width: 0.0, height: 0.0,); - } - List body = []; - body.add(_buildCardHeader()); - body.addAll(_buildCardBody(context)); - return Card( - child: new Column(mainAxisSize: MainAxisSize.min, children: body) - ); - } - - Widget _buildCardHeader() { - var result; - if ((card.name != null) && (card.name.trim().length > 0)) { - result = new ListTile( - title: Text("${card.name}", - textAlign: TextAlign.left, - overflow: TextOverflow.ellipsis, - style: new TextStyle(fontWeight: FontWeight.bold, fontSize: 25.0)), - ); - } else { - result = new Container(width: 0.0, height: 0.0); - } - return result; - } - - List _buildCardBody(BuildContext context) { - List result = []; - card.entities.forEach((Entity entity) { - if (!entity.isHidden) { - result.add( - Padding( - padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0), - child: entity.buildDefaultWidget(context), - )); - } - }); - return result; - } - } \ No newline at end of file diff --git a/lib/widgets/stateful_widgets.dart b/lib/widgets/stateful_widgets.dart deleted file mode 100644 index 4fb87dd..0000000 --- a/lib/widgets/stateful_widgets.dart +++ /dev/null @@ -1,1029 +0,0 @@ -part of '../main.dart'; - -class SwitchStateWidget extends StatefulWidget { - @override - _SwitchStateWidgetState createState() => _SwitchStateWidgetState(); -} - -class _SwitchStateWidgetState extends State { - - @override - void initState() { - super.initState(); - } - - void _setNewState(newValue, Entity entity) { - setState(() { - entity.assumedState = newValue ? 'on' : 'off'; - }); - Timer(Duration(seconds: 2), (){ - setState(() { - entity.assumedState = entity.state; - }); - }); - eventBus.fire(new ServiceCallEvent( - entity.domain, (newValue as bool) ? "turn_on" : "turn_off", entity.entityId, null)); - } - - @override - Widget build(BuildContext context) { - final entityModel = EntityModel.of(context); - final entity = entityModel.entity; - if ((entity.attributes["assumed_state"] == null) || (entity.attributes["assumed_state"] == false)) { - return Switch( - value: entity.assumedState == 'on', - onChanged: ((switchState) { - _setNewState(switchState, entity); - }), - ); - } else { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - IconButton( - onPressed: () => _setNewState(false, entity), - icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:flash-off")), - color: entity.assumedState == 'on' ? Colors.black : Colors.blue, - iconSize: Entity.iconSize, - ), - IconButton( - onPressed: () => _setNewState(true, entity), - icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:flash")), - color: entity.assumedState == 'on' ? Colors.blue : Colors.black, - iconSize: Entity.iconSize - ) - ], - ); - } - - } -} - -class TextInputStateWidget extends StatefulWidget { - - TextInputStateWidget({Key key}) : super(key: key); - - @override - _TextInputStateWidgetState createState() => _TextInputStateWidgetState(); -} - -class _TextInputStateWidgetState extends State { - String _tmpValue; - String _entityState; - String _entityDomain; - String _entityId; - int _minLength; - int _maxLength; - FocusNode _focusNode = FocusNode(); - bool validValue = false; - - @override - void initState() { - super.initState(); - _focusNode.addListener(_focusListener); - } - - void setNewState(newValue, domain, entityId) { - if (validate(newValue, _minLength, _maxLength)) { - eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId, - {"value": "$newValue"})); - } else { - setState(() { - _tmpValue = _entityState; - }); - } - } - - bool validate(newValue, minLength, maxLength) { - if (newValue is String) { - validValue = (newValue.length >= minLength) && - (maxLength == -1 || - (newValue.length <= maxLength)); - } else { - validValue = true; - } - return validValue; - } - - void _focusListener() { - if (!_focusNode.hasFocus && (_tmpValue != _entityState)) { - setNewState(_tmpValue, _entityDomain, _entityId); - } - } - - @override - Widget build(BuildContext context) { - final entityModel = EntityModel.of(context); - final TextEntity entity = entityModel.entity; - _entityState = entity.state; - _entityDomain = entity.domain; - _entityId = entity.entityId; - _minLength = entity.valueMinLength; - _maxLength = entity.valueMaxLength; - - if (!_focusNode.hasFocus && (_tmpValue != entity.state)) { - _tmpValue = entity.state; - } - if (entity.isTextField || entity.isPasswordField) { - return Expanded( - //width: Entity.INPUT_WIDTH, - child: TextField( - focusNode: _focusNode, - obscureText: entity.isPasswordField, - controller: new TextEditingController.fromValue( - new TextEditingValue( - text: _tmpValue, - selection: - new TextSelection.collapsed(offset: _tmpValue.length) - ) - ), - onChanged: (value) { - _tmpValue = value; - }), - ); - } else { - TheLogger.warning( "Unsupported input mode for ${entity.entityId}"); - return SimpleEntityState(); - } - } - - @override - void dispose() { - _focusNode.removeListener(_focusListener); - _focusNode.dispose(); - super.dispose(); - } - -} - -class SliderStateWidget extends StatefulWidget { - - final bool expanded; - - SliderStateWidget({Key key, @required this.expanded}) : super(key: key); - - @override - _SliderStateWidgetState createState() => _SliderStateWidgetState(); -} - -class _SliderStateWidgetState extends State { - int _multiplier = 1; - - void setNewState(newValue, domain, entityId) { - eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId, - {"value": "${newValue.toString()}"})); - } - - @override - Widget build(BuildContext context) { - final entityModel = EntityModel.of(context); - final SliderEntity entity = entityModel.entity; - if (entity.valueStep < 1) { - _multiplier = 10; - } else if (entity.valueStep < 0.1) { - _multiplier = 100; - } - Widget slider = Slider( - min: entity.minValue * _multiplier, - max: entity.maxValue * _multiplier, - value: (entity.doubleState <= entity.maxValue) && - (entity.doubleState >= entity.minValue) - ? entity.doubleState * _multiplier - : entity.minValue * _multiplier, - onChanged: (value) { - setState(() { - entity.state = - (value.roundToDouble() / _multiplier).toString(); - }); - eventBus.fire(new StateChangedEvent(entity.entityId, - (value.roundToDouble() / _multiplier).toString(), true)); - - }, - onChangeEnd: (value) { - setNewState(value.roundToDouble() / _multiplier, entity.domain, entity.entityId); - }, - ); - if (widget.expanded) { - return Expanded( - child: slider, - ); - } else { - return slider; - } - } -} - -class ClimateControlWidget extends StatefulWidget { - - ClimateControlWidget({Key key}) : super(key: key); - - @override - _ClimateControlWidgetState createState() => _ClimateControlWidgetState(); -} - -class _ClimateControlWidgetState extends State { - - bool _showPending = false; - bool _changedHere = false; - Timer _resetTimer; - double _tmpTemperature = 0.0; - double _tmpTargetLow = 0.0; - double _tmpTargetHigh = 0.0; - double _tmpTargetHumidity = 0.0; - String _tmpOperationMode; - String _tmpFanMode; - String _tmpSwingMode; - bool _tmpAwayMode = false; - bool _tmpIsOff = false; - bool _tmpAuxHeat = false; - - void _resetVars(ClimateEntity entity) { - _tmpTemperature = entity.temperature; - _tmpTargetHigh = entity.targetHigh; - _tmpTargetLow = entity.targetLow; - _tmpOperationMode = entity.operationMode; - _tmpFanMode = entity.fanMode; - _tmpSwingMode = entity.swingMode; - _tmpAwayMode = entity.awayMode; - _tmpIsOff = entity.isOff; - _tmpAuxHeat = entity.auxHeat; - _tmpTargetHumidity = entity.targetHumidity; - - _showPending = false; - _changedHere = false; - } - - void _temperatureUp(ClimateEntity entity, double step) { - _tmpTemperature = ((_tmpTemperature + step) <= entity.maxTemp) ? _tmpTemperature + step : entity.maxTemp; - _setTemperature(entity); - } - - void _temperatureDown(ClimateEntity entity, double step) { - _tmpTemperature = ((_tmpTemperature - step) >= entity.minTemp) ? _tmpTemperature - step : entity.minTemp; - _setTemperature(entity); - } - - void _targetLowUp(ClimateEntity entity, double step) { - _tmpTargetLow = ((_tmpTargetLow + step) <= entity.maxTemp) ? _tmpTargetLow + step : entity.maxTemp; - _setTargetTemp(entity); - } - - void _targetLowDown(ClimateEntity entity, double step) { - _tmpTargetLow = ((_tmpTargetLow - step) >= entity.minTemp) ? _tmpTargetLow - step : entity.minTemp; - _setTargetTemp(entity); - } - - void _targetHighUp(ClimateEntity entity, double step) { - _tmpTargetHigh = ((_tmpTargetHigh + step) <= entity.maxTemp) ? _tmpTargetHigh + step : entity.maxTemp; - _setTargetTemp(entity); - } - - void _targetHighDown(ClimateEntity entity, double step) { - _tmpTargetHigh = ((_tmpTargetHigh - step) >= entity.minTemp) ? _tmpTargetHigh - step : entity.minTemp; - _setTargetTemp(entity); - } - - void _setTemperature(ClimateEntity entity) { - setState(() { - _tmpTemperature = double.parse(_tmpTemperature.toStringAsFixed(1)); - _changedHere = true; - eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"temperature": "${_tmpTemperature.toStringAsFixed(1)}"})); - _resetStateTimer(entity); - }); - } - - void _setTargetTemp(ClimateEntity entity) { - setState(() { - _tmpTargetLow = double.parse(_tmpTargetLow.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)}"})); - _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 _setOperationMode(ClimateEntity entity, value) { - setState(() { - _tmpOperationMode = value; - _changedHere = true; - eventBus.fire(new ServiceCallEvent(entity.domain, "set_operation_mode", entity.entityId,{"operation_mode": "$_tmpOperationMode"})); - _resetStateTimer(entity); - }); - } - - void _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 _setAwayMode(ClimateEntity entity, value) { - setState(() { - _tmpAwayMode = value; - _changedHere = true; - eventBus.fire(new ServiceCallEvent(entity.domain, "set_away_mode", entity.entityId,{"away_mode": "${_tmpAwayMode ? 'on' : 'off'}"})); - _resetStateTimer(entity); - }); - } - - void _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.entity; - if (_changedHere) { - _showPending = (_tmpTemperature != entity.temperature); - _changedHere = false; - } else { - _resetTimer?.cancel(); - _resetVars(entity); - } - return Padding( - padding: EdgeInsets.fromLTRB(Entity.leftWidgetPadding, Entity.rowPadding, Entity.rightWidgetPadding, 0.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildOnOffControl(entity), - _buildTemperatureControls(entity), - _buildTargetTemperatureControls(entity), - _buildHumidityControls(entity), - _buildOperationControl(entity), - _buildFanControl(entity), - _buildSwingControl(entity), - _buildAwayModeControl(entity), - _buildAuxHeatControl(entity) - ], - ), - ); - } - - Widget _buildAwayModeControl(ClimateEntity entity) { - if (entity.supportAwayMode) { - return ModeSwitchWidget( - caption: "Away mode", - onChange: (value) => _setAwayMode(entity, value), - value: _tmpAwayMode, - ); - } 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.supportOperationMode) { - return ModeSelectorWidget( - onChange: (mode) => _setOperationMode(entity, mode), - options: entity.operationList, - caption: "Operation", - value: _tmpOperationMode, - ); - } else { - return Container(height: 0.0, width: 0.0); - } - } - - Widget _buildFanControl(ClimateEntity entity) { - if (entity.supportFanMode) { - return ModeSelectorWidget( - options: entity.fanList, - 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.swingList, - 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: [ - Text("Target temperature", style: TextStyle( - fontSize: Entity.stateFontSize - )), - TemperatureControlWidget( - value: _tmpTemperature, - fontColor: _showPending ? Colors.red : Colors.black, - onLargeDec: () => _temperatureDown(entity, 0.5), - onLargeInc: () => _temperatureUp(entity, 0.5), - onSmallDec: () => _temperatureDown(entity, 0.1), - onSmallInc: () => _temperatureUp(entity, 0.1), - ) - ], - ); - } else { - return Container(width: 0.0, height: 0.0,); - } - } - - Widget _buildTargetTemperatureControls(ClimateEntity entity) { - List controls = []; - if ((entity.supportTargetTemperatureLow) && (entity.targetLow != null)) { - controls.addAll([ - TemperatureControlWidget( - value: _tmpTargetLow, - fontColor: _showPending ? Colors.red : Colors.black, - onLargeDec: () => _targetLowDown(entity, 0.5), - onLargeInc: () => _targetLowUp(entity, 0.5), - onSmallDec: () => _targetLowDown(entity, 0.1), - onSmallInc: () => _targetLowUp(entity, 0.1), - ), - Expanded( - child: Container(height: 10.0), - ) - ]); - } - if ((entity.supportTargetTemperatureHigh) && (entity.targetHigh != null)) { - controls.add( - TemperatureControlWidget( - value: _tmpTargetHigh, - fontColor: _showPending ? Colors.red : Colors.black, - onLargeDec: () => _targetHighDown(entity, 0.5), - onLargeInc: () => _targetHighUp(entity, 0.5), - onSmallDec: () => _targetHighDown(entity, 0.1), - onSmallInc: () => _targetHighUp(entity, 0.1), - ) - ); - } - if (controls.isNotEmpty) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("Target temperature range", style: TextStyle( - fontSize: Entity.stateFontSize - )), - Row( - children: controls, - ) - ], - ); - } else { - return Container(width: 0.0, height: 0.0); - } - } - - Widget _buildHumidityControls(ClimateEntity entity) { - List result = []; - if (entity.supportTargetHumidity) { - result.addAll([ - Text( - "$_tmpTargetHumidity%", - style: TextStyle(fontSize: Entity.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: [ - Padding( - padding: EdgeInsets.fromLTRB( - 0.0, Entity.rowPadding, 0.0, Entity.rowPadding), - child: Text("Target humidity", style: TextStyle( - fontSize: Entity.stateFontSize - )), - ), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: result, - ), - Container( - height: Entity.rowPadding, - ) - ], - ); - } else { - return Container( - width: 0.0, - height: 0.0, - ); - } - } - - - @override - void dispose() { - _resetTimer?.cancel(); - super.dispose(); - } - -} - -class SelectControlWidget extends StatefulWidget { - - SelectControlWidget({Key key}) : super(key: key); - - @override - _SelectControlWidgetState createState() => _SelectControlWidgetState(); -} - -class _SelectControlWidgetState extends State { - - void setNewState(domain, entityId, newValue) { - eventBus.fire(new ServiceCallEvent(domain, "select_option", entityId, - {"option": "$newValue"})); - } - - @override - Widget build(BuildContext context) { - final entityModel = EntityModel.of(context); - final SelectEntity entity = entityModel.entity; - Widget ctrl; - if (entity.listOptions.isNotEmpty) { - ctrl = DropdownButton( - value: entity.state, - items: entity.listOptions.map((String value) { - return new DropdownMenuItem( - value: value, - child: new Text(value), - ); - }).toList(), - onChanged: (_) { - setNewState(entity.domain, entity.entityId,_); - }, - ); - } else { - ctrl = Text('---'); - } - return Expanded( - //width: Entity.INPUT_WIDTH, - child: ctrl, - ); - } - - -} - -class CoverControlWidget extends StatefulWidget { - - CoverControlWidget({Key key}) : super(key: key); - - @override - _CoverControlWidgetState createState() => _CoverControlWidgetState(); -} - -class _CoverControlWidgetState extends State { - - 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.entity; - TheLogger.debug("${entity.state}"); - if (_changedHere) { - _changedHere = false; - } else { - _resetVars(entity); - } - return Padding( - padding: EdgeInsets.fromLTRB(Entity.leftWidgetPadding, Entity.rowPadding, Entity.rightWidgetPadding, 0.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildPositionControls(entity), - _buildTiltControls(entity) - ], - ), - ); - } - - Widget _buildPositionControls(CoverEntity entity) { - if (entity.supportSetPosition) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.fromLTRB( - 0.0, Entity.rowPadding, 0.0, Entity.rowPadding), - child: Text("Position", style: TextStyle( - fontSize: Entity.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: Entity.rowPadding,) - ], - ); - } else { - return Container(width: 0.0, height: 0.0); - } - } - - Widget _buildTiltControls(CoverEntity entity) { - List controls = []; - if (entity.supportCloseTilt || entity.supportOpenTilt || entity.supportStopTilt) { - controls.add( - CoverEntityTiltControlButtons() - ); - } - if (entity.supportSetTiltPosition) { - controls.addAll([ - 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: Entity.rowPadding,) - ]); - } - if (controls.isNotEmpty) { - controls.insert(0, Padding( - padding: EdgeInsets.fromLTRB( - 0.0, Entity.rowPadding, 0.0, Entity.rowPadding), - child: Text("Tilt position", style: TextStyle( - fontSize: Entity.stateFontSize - )), - )); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: controls, - ); - } else { - return Container(width: 0.0, height: 0.0); - } - } - -} - -class LightControlsWidget extends StatefulWidget { - - @override - _LightControlsWidgetState createState() => _LightControlsWidgetState(); - -} - -class _LightControlsWidgetState extends State { - - int _tmpBrightness; - int _tmpColorTemp; - Color _tmpColor; - bool _changedHere = false; - String _tmpEffect; - - void _resetState(LightEntity entity) { - _tmpBrightness = entity.brightness ?? 0; - _tmpColorTemp = entity.colorTemp; - _tmpColor = entity.color; - _tmpEffect = null; - } - - void _setBrightness(LightEntity entity, double value) { - setState(() { - _tmpBrightness = value.round(); - _changedHere = true; - if (_tmpBrightness > 0) { - eventBus.fire(new ServiceCallEvent( - entity.domain, "turn_on", entity.entityId, - {"brightness": _tmpBrightness})); - } else { - eventBus.fire(new ServiceCallEvent( - entity.domain, "turn_off", entity.entityId, - null)); - } - }); - } - - 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, Color color) { - setState(() { - _tmpColor = color; - _changedHere = true; - TheLogger.debug( "Color: [${color.red}, ${color.green}, ${color.blue}]"); - if ((color == Colors.black) || ((color.red == color.green) && (color.green == color.blue))) { - eventBus.fire(new ServiceCallEvent( - entity.domain, "turn_off", entity.entityId, - null)); - } else { - eventBus.fire(new ServiceCallEvent( - entity.domain, "turn_on", entity.entityId, - {"rgb_color": [color.red, color.green, color.blue]})); - } - }); - } - - 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.entity; - if (!_changedHere) { - _resetState(entity); - } else { - _changedHere = false; - } - return Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - _buildBrightnessControl(entity), - _buildColorTempControl(entity), - _buildColorControl(entity), - _buildEffectControl(entity) - ], - ); - } - - Widget _buildBrightnessControl(LightEntity entity) { - if ((entity.supportBrightness) && (_tmpBrightness != null)) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container(height: Entity.rowPadding,), - Text( - "Brightness", - style: TextStyle(fontSize: Entity.stateFontSize), - ), - Container(height: Entity.rowPadding,), - Row( - children: [ - Icon(Icons.brightness_5), - Expanded( - child: Slider( - value: _tmpBrightness.toDouble(), - min: 0.0, - max: 255.0, - onChanged: (value) { - setState(() { - _changedHere = true; - _tmpBrightness = value.round(); - }); - }, - onChangeEnd: (value) => _setBrightness(entity, value), - ), - ) - ], - ), - Container(height: Entity.rowPadding,) - ], - ); - } else { - return Container(width: 0.0, height: 0.0); - } - } - - Widget _buildColorTempControl(LightEntity entity) { - if ((entity.supportColorTemp) && (_tmpColorTemp != null)) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container(height: Entity.rowPadding,), - Text( - "Color temperature", - style: TextStyle(fontSize: Entity.stateFontSize), - ), - Container(height: Entity.rowPadding,), - Row( - children: [ - Text("Cold", style: TextStyle(color: Colors.lightBlue),), - Expanded( - child: Slider( - value: _tmpColorTemp.toDouble(), - min: entity.minMireds, - max: entity.maxMireds, - onChanged: (value) { - setState(() { - _changedHere = true; - _tmpColorTemp = value.round(); - }); - }, - onChangeEnd: (value) => _setColorTemp(entity, value), - ), - ), - Text("Warm", style: TextStyle(color: Colors.amberAccent),), - ], - ), - Container(height: Entity.rowPadding,) - ], - ); - } else { - return Container(width: 0.0, height: 0.0); - } - } - - Widget _buildColorControl(LightEntity entity) { - if ((entity.supportColor) && (entity.color != null)) { - return Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container(height: Entity.rowPadding,), - RaisedButton( - onPressed: () => _showColorPicker(entity), - color: _tmpColor ?? Colors.black45, - child: Text( - "COLOR", - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 50.0, - fontWeight: FontWeight.bold, - color: Colors.black12, - ), - ), - ), - Container(height: 2*Entity.rowPadding,), - ], - ); - } else { - return Container(width: 0.0, height: 0.0); - } - } - - void _showColorPicker(LightEntity entity) { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - titlePadding: EdgeInsets.all(0.0), - contentPadding: EdgeInsets.all(0.0), - content: SingleChildScrollView( - child: MaterialPicker( - pickerColor: _tmpColor, - onColorChanged: (color) { - _setColor(entity, color); - Navigator.of(context).pop(); - }, - enableLabel: true, - ), - ), - ); - }, - ); - } - - 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); - } - } - - -} \ No newline at end of file diff --git a/lib/widgets/stateless_widgets.dart b/lib/widgets/stateless_widgets.dart deleted file mode 100644 index ed7238e..0000000 --- a/lib/widgets/stateless_widgets.dart +++ /dev/null @@ -1,774 +0,0 @@ -part of '../main.dart'; - -class EntityWidgetsSizes {} - -class EntityModel extends InheritedWidget { - const EntityModel({ - Key key, - @required this.entity, - @required this.handleTap, - @required Widget child, - }) : super(key: key, child: child); - - final Entity entity; - final bool handleTap; - - static EntityModel of(BuildContext context) { - return context.inheritFromWidgetOfExactType(EntityModel); - } - - @override - bool updateShouldNotify(InheritedWidget oldWidget) { - return true; - } -} - -class DefaultEntityContainer extends StatelessWidget { - DefaultEntityContainer({ - Key key, - @required this.state, - @required this.height - }) : super(key: key); - - final Widget state; - final double height; - - @override - Widget build(BuildContext context) { - return SizedBox( - height: height, - child: Row( - children: [ - EntityIcon(), - Expanded( - child: EntityName(), - ), - state - ], - ), - ); - } -} - -class EntityPageContainer extends StatelessWidget { - EntityPageContainer({Key key, @required this.children}) : super(key: key); - - final List children; - - @override - Widget build(BuildContext context) { - return ListView( - children: children, - ); - } -} - -class SimpleEntityState extends StatelessWidget { - @override - Widget build(BuildContext context) { - final entityModel = EntityModel.of(context); - return Padding( - padding: EdgeInsets.fromLTRB( - 0.0, 0.0, Entity.rightWidgetPadding, 0.0), - child: GestureDetector( - child: Text( - "${entityModel.entity.state}${entityModel.entity.unitOfMeasurement}", - textAlign: TextAlign.right, - style: new TextStyle( - fontSize: Entity.stateFontSize, - )), - onTap: () => entityModel.handleTap - ? eventBus.fire(new ShowEntityPageEvent(entityModel.entity)) - : null, - )); - } -} - -class EntityName extends StatelessWidget { - @override - Widget build(BuildContext context) { - final entityModel = EntityModel.of(context); - return GestureDetector( - child: Padding( - padding: EdgeInsets.only(right: 10.0), - child: Text( - "${entityModel.entity.displayName}", - overflow: TextOverflow.ellipsis, - softWrap: false, - style: TextStyle(fontSize: Entity.nameFontSize), - ), - ), - onTap: () => entityModel.handleTap - ? eventBus.fire(new ShowEntityPageEvent(entityModel.entity)) - : null, - ); - } -} - -class EntityIcon extends StatelessWidget { - @override - Widget build(BuildContext context) { - final entityModel = EntityModel.of(context); - return GestureDetector( - child: Padding( - padding: EdgeInsets.fromLTRB( - Entity.leftWidgetPadding, 0.0, 12.0, 0.0), - child: MaterialDesignIcons.createIconWidgetFromEntityData( - entityModel.entity, - Entity.iconSize, - Entity.STATE_ICONS_COLORS[entityModel.entity.state] ?? - Entity.STATE_ICONS_COLORS["default"]), - ), - onTap: () => entityModel.handleTap - ? eventBus.fire(new ShowEntityPageEvent(entityModel.entity)) - : null, - ); - } -} - -class LastUpdatedWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - final entityModel = EntityModel.of(context); - return Padding( - padding: EdgeInsets.fromLTRB( - Entity.leftWidgetPadding, 0.0, 0.0, 0.0), - child: Text( - '${entityModel.entity.lastUpdated}', - textAlign: TextAlign.left, - style: TextStyle( - fontSize: Entity.smallFontSize, color: Colors.black26), - ), - ); - } -} - -class EntityAttributesList extends StatelessWidget { - EntityAttributesList({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final entityModel = EntityModel.of(context); - List attrs = []; - if ((entityModel.entity.attributesToShow == null) || - (entityModel.entity.attributesToShow.contains("all"))) { - entityModel.entity.attributes.forEach((name, value) { - attrs.add(_buildSingleAttribute("$name", "$value")); - }); - } else { - entityModel.entity.attributesToShow.forEach((String attr) { - String attrValue = entityModel.entity.getAttribute("$attr"); - if (attrValue != null) { - attrs.add( - _buildSingleAttribute("$attr", "$attrValue")); - } - }); - } - return Column( - children: attrs, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - ); - } - - Widget _buildSingleAttribute(String name, String value) { - return Row( - children: [ - Expanded( - child: Padding( - padding: EdgeInsets.fromLTRB( - Entity.leftWidgetPadding, Entity.rowPadding, 0.0, 0.0), - child: Text( - "$name", - textAlign: TextAlign.left, - ), - ), - ), - Expanded( - child: Padding( - padding: EdgeInsets.fromLTRB( - 0.0, Entity.rowPadding, Entity.rightWidgetPadding, 0.0), - child: Text( - "$value", - textAlign: TextAlign.right, - ), - ), - ) - ], - ); - } -} - -class Badge extends StatelessWidget { - @override - Widget build(BuildContext context) { - final entityModel = EntityModel.of(context); - double iconSize = 26.0; - Widget badgeIcon; - String onBadgeTextValue; - Color iconColor = Entity.badgeColors[entityModel.entity.domain] ?? - Entity.badgeColors["default"]; - switch (entityModel.entity.domain) { - case "sun": - { - badgeIcon = entityModel.entity.state == "below_horizon" - ? Icon( - MaterialDesignIcons.createIconDataFromIconCode(0xf0dc), - size: iconSize, - ) - : Icon( - MaterialDesignIcons.createIconDataFromIconCode(0xf5a8), - size: iconSize, - ); - break; - } - case "sensor": - { - onBadgeTextValue = entityModel.entity.unitOfMeasurement; - badgeIcon = Center( - child: Text( - "${entityModel.entity.state}", - overflow: TextOverflow.fade, - softWrap: false, - textAlign: TextAlign.center, - style: TextStyle(fontSize: 17.0), - ), - ); - break; - } - case "device_tracker": - { - badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData( - entityModel.entity, iconSize, Colors.black); - onBadgeTextValue = entityModel.entity.state; - break; - } - default: - { - badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData( - entityModel.entity, iconSize, Colors.black); - } - } - Widget onBadgeText; - if (onBadgeTextValue == null || onBadgeTextValue.length == 0) { - onBadgeText = Container(width: 0.0, height: 0.0); - } else { - onBadgeText = Container( - padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0), - child: Text("$onBadgeTextValue", - style: TextStyle(fontSize: 12.0, color: Colors.white), - textAlign: TextAlign.center, - softWrap: false, - overflow: TextOverflow.fade), - decoration: new BoxDecoration( - // Circle shape - //shape: BoxShape.circle, - color: iconColor, - borderRadius: BorderRadius.circular(9.0), - )); - } - return GestureDetector( - child: Column( - children: [ - Container( - margin: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0), - width: 50.0, - height: 50.0, - decoration: new BoxDecoration( - // Circle shape - shape: BoxShape.circle, - color: Colors.white, - // The border you want - border: new Border.all( - width: 2.0, - color: iconColor, - ), - ), - child: Stack( - overflow: Overflow.visible, - children: [ - Positioned( - width: 46.0, - height: 46.0, - top: 0.0, - left: 0.0, - child: badgeIcon, - ), - Positioned( - //width: 50.0, - bottom: -9.0, - left: -10.0, - right: -10.0, - child: Center( - child: onBadgeText, - )) - ], - ), - ), - Container( - width: 60.0, - child: Text( - "${entityModel.entity.displayName}", - textAlign: TextAlign.center, - style: TextStyle(fontSize: 12.0), - softWrap: true, - maxLines: 3, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - onTap: () => - eventBus.fire(new ShowEntityPageEvent(entityModel.entity))); - } -} - -class ClimateStateWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - final entityModel = EntityModel.of(context); - final ClimateEntity entity = entityModel.entity; - String targetTemp = "-"; - if ((entity.supportTargetTemperature) && (entity.temperature != null)) { - targetTemp = "${entity.temperature}"; - } else if ((entity.supportTargetTemperatureLow) && - (entity.targetLow != null)) { - targetTemp = "${entity.targetLow}"; - if ((entity.supportTargetTemperatureHigh) && - (entity.targetHigh != null)) { - targetTemp += " - ${entity.targetHigh}"; - } - } - return Padding( - padding: EdgeInsets.fromLTRB( - 0.0, 0.0, Entity.rightWidgetPadding, 0.0), - child: GestureDetector( - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - children: [ - Text("${entity.state}", - textAlign: TextAlign.right, - style: new TextStyle( - fontWeight: FontWeight.bold, - fontSize: Entity.stateFontSize, - )), - Text(" $targetTemp", - textAlign: TextAlign.right, - style: new TextStyle( - fontSize: Entity.stateFontSize, - )) - ], - ), - entity.attributes["current_temperature"] != null ? - Text("Currently: ${entity.attributes["current_temperature"]}", - textAlign: TextAlign.right, - style: new TextStyle( - fontSize: Entity.stateFontSize, - color: Colors.black45) - ) : - Container(height: 0.0,) - ], - ), - onTap: () => entityModel.handleTap - ? eventBus.fire(new ShowEntityPageEvent(entity)) - : null, - )); - } -} - -class TemperatureControlWidget extends StatelessWidget { - final double value; - final double fontSize; - final Color fontColor; - final onSmallInc; - final onLargeInc; - final onSmallDec; - final onLargeDec; - - TemperatureControlWidget( - {Key key, - @required this.value, - @required this.onSmallInc, - @required this.onSmallDec, - @required this.onLargeInc, - @required this.onLargeDec, - this.fontSize, - this.fontColor}) - : super(key: key); - - @override - Widget build(BuildContext context) { - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - "$value", - style: TextStyle( - fontSize: fontSize ?? 24.0, - color: fontColor ?? Colors.black - ), - ), - Column( - children: [ - IconButton( - icon: Icon(MaterialDesignIcons.createIconDataFromIconName( - 'mdi:chevron-up')), - iconSize: 30.0, - onPressed: () => onSmallInc(), - ), - IconButton( - icon: Icon(MaterialDesignIcons.createIconDataFromIconName( - 'mdi:chevron-down')), - iconSize: 30.0, - onPressed: () => onSmallDec(), - ) - ], - ), - Column( - children: [ - 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(), - ) - ], - ) - ], - ); - } -} - -class DateTimeStateWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - final entityModel = EntityModel.of(context); - final DateTimeEntity entity = entityModel.entity; - return Padding( - padding: EdgeInsets.fromLTRB(0.0, 0.0, Entity.rightWidgetPadding, 0.0), - child: GestureDetector( - child: Text("${entity.formattedState}", - textAlign: TextAlign.right, - style: new TextStyle( - fontSize: Entity.stateFontSize, - )), - onTap: () => _handleStateTap(context, entity), - )); - } - - void _handleStateTap(BuildContext context, DateTimeEntity entity) { - if (entity.hasDate) { - _showDatePicker(context, entity).then((date) { - if (date != null) { - if (entity.hasTime) { - _showTimePicker(context, entity).then((time) { - entity.setNewState({ - "date": "${formatDate(date, [yyyy, '-', mm, '-', dd])}", - "time": - "${formatDate(DateTime(1970, 1, 1, time.hour, time.minute), [ - HH, - ':', - nn - ])}" - }); - }); - } else { - entity.setNewState({ - "date": "${formatDate(date, [yyyy, '-', mm, '-', dd])}" - }); - } - } - }); - } else if (entity.hasTime) { - _showTimePicker(context, entity).then((time) { - if (time != null) { - entity.setNewState({ - "time": - "${formatDate(DateTime(1970, 1, 1, time.hour, time.minute), [ - HH, - ':', - nn - ])}" - }); - } - }); - } else { - TheLogger.warning( "${entity.entityId} has no date and no time"); - } - } - - Future _showDatePicker(BuildContext context, DateTimeEntity entity) { - return showDatePicker( - context: context, - initialDate: entity.dateTimeState, - firstDate: DateTime(1970), - lastDate: DateTime(2037) //Unix timestamp will finish at Jan 19, 2038 - ); - } - - Future _showTimePicker(BuildContext context, DateTimeEntity entity) { - return showTimePicker( - context: context, - initialTime: TimeOfDay.fromDateTime(entity.dateTimeState)); - } -} - -class CoverEntityControlState 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.entity; - List buttons = []; - if (entity.supportOpen) { - buttons.add(IconButton( - icon: Icon( - MaterialDesignIcons.createIconDataFromIconName("mdi:arrow-up"), - size: Entity.iconSize, - ), - onPressed: entity.canBeOpened ? () => _open(entity) : null)); - } else { - buttons.add(Container( - width: Entity.iconSize + 20.0, - )); - } - if (entity.supportStop) { - buttons.add(IconButton( - icon: Icon( - MaterialDesignIcons.createIconDataFromIconName("mdi:stop"), - size: Entity.iconSize, - ), - onPressed: () => _stop(entity))); - } else { - buttons.add(Container( - width: Entity.iconSize + 20.0, - )); - } - if (entity.supportClose) { - buttons.add(IconButton( - icon: Icon( - MaterialDesignIcons.createIconDataFromIconName("mdi:arrow-down"), - size: Entity.iconSize, - ), - onPressed: entity.canBeClosed ? () => _close(entity) : null)); - } else { - buttons.add(Container( - width: Entity.iconSize + 20.0, - )); - } - - return Row( - children: buttons, - ); - } -} - -class CoverEntityTiltControlButtons 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.entity; - List buttons = []; - if (entity.supportOpenTilt) { - buttons.add(IconButton( - icon: Icon( - MaterialDesignIcons.createIconDataFromIconName( - "mdi:arrow-top-right"), - size: Entity.iconSize, - ), - onPressed: entity.canTiltBeOpened ? () => _open(entity) : null)); - } else { - buttons.add(Container( - width: Entity.iconSize + 20.0, - )); - } - if (entity.supportStopTilt) { - buttons.add(IconButton( - icon: Icon( - MaterialDesignIcons.createIconDataFromIconName("mdi:stop"), - size: Entity.iconSize, - ), - onPressed: () => _stop(entity))); - } else { - buttons.add(Container( - width: Entity.iconSize + 20.0, - )); - } - if (entity.supportCloseTilt) { - buttons.add(IconButton( - icon: Icon( - MaterialDesignIcons.createIconDataFromIconName( - "mdi:arrow-bottom-left"), - size: Entity.iconSize, - ), - onPressed: entity.canTiltBeClosed ? () => _close(entity) : null)); - } else { - buttons.add(Container( - width: Entity.iconSize + 20.0, - )); - } - - return Row( - children: buttons, - ); - } -} - -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 FlatButton( - onPressed: (() { - _setNewState(entityModel.entity); - }), - child: Text( - "EXECUTE", - textAlign: TextAlign.right, - style: - new TextStyle(fontSize: Entity.stateFontSize, color: Colors.blue), - ), - ); - } -} - -class ModeSelectorWidget extends StatelessWidget { - - final String caption; - final List options; - final String value; - final double captionFontSize; - final double valueFontSize; - final double bottomPadding; - final onChange; - - ModeSelectorWidget({ - Key key, - this.caption, - @required this.options, - this.value, - @required this.onChange, - this.captionFontSize, - this.valueFontSize, - this.bottomPadding - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("$caption", style: TextStyle( - fontSize: captionFontSize ?? Entity.stateFontSize - )), - Row( - children: [ - Expanded( - child: ButtonTheme( - alignedDropdown: true, - child: DropdownButton( - value: value, - iconSize: 30.0, - isExpanded: true, - style: TextStyle( - fontSize: valueFontSize ?? Entity.largeFontSize, - color: Colors.black, - ), - hint: Text("Select ${caption.toLowerCase()}"), - items: options.map((String value) { - return new DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - onChanged: (mode) => onChange(mode), - ), - ), - ) - ], - ), - Container(height: bottomPadding ?? Entity.rowPadding,) - ], - ); - } -} - -class ModeSwitchWidget extends StatelessWidget { - - final String caption; - final onChange; - final double captionFontSize; - final bool value; - - ModeSwitchWidget({ - Key key, - @required this.caption, - @required this.onChange, - this.captionFontSize, - this.value - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Row( - children: [ - Expanded( - child: Text( - "$caption", - style: TextStyle( - fontSize: captionFontSize ?? Entity.stateFontSize - ), - ), - ), - Switch( - onChanged: (value) => onChange(value), - value: value ?? false, - ) - ], - ); - } - -} \ No newline at end of file