Compare commits

..

36 Commits
0.3.3 ... 0.3.5

Author SHA1 Message Date
71c4ac7fed v.0.3.5 2018-11-04 18:28:02 +02:00
3f7e21e97e Fix Lovelace views int id issue 2018-11-04 18:26:31 +02:00
e24c47b041 Error handling improvements 2018-11-04 18:20:06 +02:00
73b32b30a8 Build number inc 2018-11-04 12:01:41 +02:00
5b6155057c Fix views count issue on app loading 2018-11-04 11:23:21 +02:00
ff4185effe internal build version 2018-11-03 23:33:12 +02:00
b2da9fc04d Fix target temp history 2018-11-03 23:10:25 +02:00
f281fab744 Version 0.3.4 2018-11-03 22:54:36 +02:00
3b99f4feeb Resolves #120 2018-11-03 22:50:21 +02:00
efab8b60b1 WIP #120 null values handling 2018-11-03 21:56:06 +02:00
0e96406573 WIP #120 show all states for climate 2018-11-03 19:54:26 +02:00
ed8757c08d Version code 2018-10-31 01:39:53 +02:00
813770329c WIP #120 render only needed states 2018-10-31 01:37:36 +02:00
1853bd466e WIP #120 combined history state 2018-10-31 01:02:53 +02:00
07258477b3 Unsupported cards improvements 2018-10-30 22:53:49 +02:00
a3adb72cf8 Unsupported lovelace cards showing entities 2018-10-30 22:51:45 +02:00
e25162f7b5 Version code 2018-10-29 23:54:52 +02:00
d30c9d574b WIP #120 Remove custom renderers for dots 2018-10-29 23:30:11 +02:00
efa5a1958c WIP #120 Simple state chart improvements 2018-10-29 23:06:36 +02:00
37f20fae5a Version code change 2018-10-29 00:59:44 +02:00
91db34badb WIP #120 History chart based on attributes 2018-10-29 00:58:52 +02:00
c20200b609 WIP #120 Random color for states 2018-10-28 21:02:38 +02:00
fcd4ac7292 WIP #120 Numeric state charts 2018-10-28 20:01:01 +02:00
e16338c3f2 WIP #120 History widget improvements 2018-10-28 18:07:52 +02:00
6e038b0685 WIP #120 Convert history time to local 2018-10-28 15:25:12 +02:00
052cd3894e WIP #120 Simplest on/off state history chart 2018-10-28 14:56:23 +02:00
809c7d6355 Card separation by type 2018-10-27 17:28:47 +03:00
9edfec7dff Code structure 2018-10-27 14:27:41 +03:00
df56f6ceda version code change 2018-10-27 01:27:12 +03:00
5e834b0645 Logger improvements 2018-10-27 01:24:23 +03:00
8fb0d61a84 Resolves #122 2018-10-27 00:54:05 +03:00
54979b583b version change for internal testing 2018-10-25 00:58:03 +03:00
4e955e98d8 Still #154 default view 2018-10-25 00:54:20 +03:00
88cfcb4382 Resolves #153 hidden entities 2018-10-25 00:13:50 +03:00
5338e45ddc Resolves #154 UI building refactoring 2018-10-25 00:08:26 +03:00
24d071e2f8 WIP #154 UI building refactoring 2018-10-24 23:53:10 +03:00
63 changed files with 3959 additions and 2639 deletions

View File

@ -1,61 +0,0 @@
part of 'main.dart';
class CardWidget extends StatelessWidget {
final List<Entity> entities;
final String friendlyName;
const CardWidget({
Key key,
this.entities,
this.friendlyName
}) : super(key: key);
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
if (entityModel != null) {
final groupEntity = entityModel.entity;
if ((groupEntity!= null) && (groupEntity.isHidden)) {
return Container(width: 0.0, height: 0.0,);
}
}
List<Widget> body = [];
body.add(_buildCardHeader());
body.addAll(_buildCardBody(context));
return Card(
child: new Column(mainAxisSize: MainAxisSize.min, children: body)
);
}
Widget _buildCardHeader() {
var result;
if ((friendlyName != null) && (friendlyName.trim().length > 0)) {
result = new ListTile(
//leading: const Icon(Icons.device_hub),
//subtitle: Text(".."),
//trailing: Text("${data["state"]}"),
title: Text("$friendlyName",
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<Widget> _buildCardBody(BuildContext context) {
List<Widget> result = [];
entities.forEach((Entity entity) {
result.add(
Padding(
padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0),
child: entity.buildDefaultWidget(context),
));
});
return result;
}
}

View File

@ -52,7 +52,10 @@ class _EntityViewPageState extends State<EntityViewPage> {
),
body: Padding(
padding: EdgeInsets.all(10.0),
child: HomeAssistantModel(
homeAssistant: widget.homeAssistant,
child: widget.entity.buildEntityPageWidget(context)
)
),
);
}

View File

@ -0,0 +1,10 @@
part of '../main.dart';
class ButtonEntity extends Entity {
ButtonEntity(Map rawData) : super(rawData);
@override
Widget _buildStatePart(BuildContext context) {
return ButtonStateWidget();
}
}

View File

@ -0,0 +1,130 @@
part of '../main.dart';
class ClimateEntity extends Entity {
@override
double widgetHeight = 38.0;
@override
EntityHistoryConfig historyConfig = EntityHistoryConfig(
chartType: EntityHistoryWidgetType.numericAttributes,
numericState: false,
numericAttributesToShow: ["current_temperature"]
);
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<String> get operationList => attributes["operation_list"] != null
? (attributes["operation_list"] as List).cast<String>()
: null;
List<String> get fanList => attributes["fan_list"] != null
? (attributes["fan_list"] as List).cast<String>()
: null;
List<String> get swingList => attributes["swing_list"] != null
? (attributes["swing_list"] as List).cast<String>()
: 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
void update(Map rawData) {
super.update(rawData);
if (supportTargetTemperature) {
historyConfig.numericAttributesToShow.add("temperature");
}
if (supportTargetTemperatureHigh) {
historyConfig.numericAttributesToShow.add("target_temp_high");
}
if (supportTargetTemperatureLow) {
historyConfig.numericAttributesToShow.add("target_temp_low");
}
}
@override
Widget _buildStatePart(BuildContext context) {
return ClimateStateWidget();
}
@override
Widget _buildAdditionalControlsForPage(BuildContext context) {
return ClimateControlWidget();
}
@override
double _getDoubleAttributeValue(String attributeName) {
var temp1 = attributes["$attributeName"];
if (temp1 is int) {
return temp1.toDouble();
} else if (temp1 is double) {
return temp1;
} else {
return null;
}
}
}

View File

@ -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();
}
}

View File

@ -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));
}
}

View File

@ -1,14 +1,7 @@
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)
@ -44,8 +37,10 @@ class Entity {
DateTime _lastUpdated;
List<Entity> childEntities = [];
List<String> attributesToShow = ["all"];
EntityHistoryConfig historyConfig = EntityHistoryConfig(
chartType: EntityHistoryWidgetType.simple
);
String get displayName =>
attributes["friendly_name"] ?? (attributes["name"] ?? "_");
@ -63,6 +58,7 @@ class Entity {
List get childEntityIds => attributes["entity_id"] ?? [];
String get lastUpdated => _getLastUpdatedFormatted();
bool get isHidden => attributes["hidden"] ?? false;
double get doubleState => double.tryParse(state) ?? 0.0;
Entity(Map rawData) {
update(rawData);
@ -132,18 +128,24 @@ class Entity {
DefaultEntityContainer(state: _buildStatePartForPage(context), height: widgetHeight),
LastUpdatedWidget(),
Divider(),
buildHistoryWidget(),
_buildAdditionalControlsForPage(context),
Divider(),
EntityAttributesList()
]),
handleTap: false,
);
}
Widget buildHistoryWidget() {
return EntityHistoryWidget(
config: historyConfig,
);
}
Widget buildBadgeWidget(BuildContext context) {
return EntityModel(
entity: this,
child: Badge(),
child: BadgeWidget(),
handleTap: true,
);
}
@ -184,379 +186,3 @@ class Entity {
}
}
}
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: <Widget>[
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<String> get operationList => attributes["operation_list"] != null
? (attributes["operation_list"] as List).cast<String>()
: null;
List<String> get fanList => attributes["fan_list"] != null
? (attributes["fan_list"] as List).cast<String>()
: null;
List<String> get swingList => attributes["swing_list"] != null
? (attributes["swing_list"] as List).cast<String>()
: 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<String> get listOptions => attributes["options"] != null
? (attributes["options"] as List).cast<String>()
: [];
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<String> 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<String> _getEffectList() {
if (attributes["effect_list"] != null) {
List<String> result = (attributes["effect_list"] as List).cast<String>();
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();
}
}
}

View File

@ -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<String> 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<String> _getEffectList() {
if (attributes["effect_list"] != null) {
List<String> result = (attributes["effect_list"] as List).cast<String>();
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();
}
}
}

View File

@ -0,0 +1,51 @@
part of '../main.dart';
class MediaPlayerEntity extends Entity {
static const SUPPORT_PAUSE = 1;
static const SUPPORT_SEEK = 2;
static const SUPPORT_VOLUME_SET = 4;
static const SUPPORT_VOLUME_MUTE = 8;
static const SUPPORT_PREVIOUS_TRACK = 16;
static const SUPPORT_NEXT_TRACK = 32;
static const SUPPORT_TURN_ON = 128;
static const SUPPORT_TURN_OFF = 256;
static const SUPPORT_PLAY_MEDIA = 512;
static const SUPPORT_VOLUME_STEP = 1024;
static const SUPPORT_SELECT_SOURCE = 2048;
static const SUPPORT_STOP = 4096;
static const SUPPORT_CLEAR_PLAYLIST = 8192;
static const SUPPORT_PLAY = 16384;
static const SUPPORT_SHUFFLE_SET = 32768;
static const SUPPORT_SELECT_SOUND_MODE = 65536;
MediaPlayerEntity(Map rawData) : super(rawData);
bool get supportPause => ((attributes["supported_features"] &
MediaPlayerEntity.SUPPORT_PAUSE) ==
MediaPlayerEntity.SUPPORT_PAUSE);
bool get supportSeek => ((attributes["supported_features"] &
MediaPlayerEntity.SUPPORT_SEEK) ==
MediaPlayerEntity.SUPPORT_SEEK);
bool get supportVolumeSet => ((attributes["supported_features"] &
MediaPlayerEntity.SUPPORT_VOLUME_SET) ==
MediaPlayerEntity.SUPPORT_VOLUME_SET);
bool get supportVolumeMute => ((attributes["supported_features"] &
MediaPlayerEntity.SUPPORT_VOLUME_MUTE) ==
MediaPlayerEntity.SUPPORT_VOLUME_MUTE);
bool get supportPreviousTrack => ((attributes["supported_features"] &
MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK) ==
MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK);
bool get supportNextTrack => ((attributes["supported_features"] &
MediaPlayerEntity.SUPPORT_NEXT_TRACK) ==
MediaPlayerEntity.SUPPORT_NEXT_TRACK);
bool get supportTurnOn => ((attributes["supported_features"] &
MediaPlayerEntity.SUPPORT_TURN_ON) ==
MediaPlayerEntity.SUPPORT_TURN_ON);
bool get supportTurnOff => ((attributes["supported_features"] &
MediaPlayerEntity.SUPPORT_TURN_OFF) ==
MediaPlayerEntity.SUPPORT_TURN_OFF);
}

View File

@ -0,0 +1,17 @@
part of '../main.dart';
class SunEntity extends Entity {
SunEntity(Map rawData) : super(rawData);
}
class SensorEntity extends Entity {
@override
EntityHistoryConfig historyConfig = EntityHistoryConfig(
chartType: EntityHistoryWidgetType.numericState,
numericState: true
);
SensorEntity(Map rawData) : super(rawData);
}

View File

@ -0,0 +1,14 @@
part of '../main.dart';
class SelectEntity extends Entity {
List<String> get listOptions => attributes["options"] != null
? (attributes["options"] as List).cast<String>()
: [];
SelectEntity(Map rawData) : super(rawData);
@override
Widget _buildStatePart(BuildContext context) {
return SelectStateWidget();
}
}

View File

@ -0,0 +1,36 @@
part of '../main.dart';
class SliderEntity extends Entity {
SliderEntity(Map rawData) : super(rawData);
double get minValue => _getDoubleAttributeValue("min") ?? 0.0;
double get maxValue =>_getDoubleAttributeValue("max") ?? 100.0;
double get valueStep => _getDoubleAttributeValue("step") ?? 1.0;
@override
Widget _buildStatePart(BuildContext context) {
return Expanded(
//width: 200.0,
child: Row(
children: <Widget>[
SliderStateWidget(
expanded: true,
),
SimpleEntityState(),
],
),
);
}
@override
Widget _buildStatePartForPage(BuildContext context) {
return SimpleEntityState();
}
@override
Widget _buildAdditionalControlsForPage(BuildContext context) {
return SliderStateWidget(
expanded: false,
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,775 +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: <Widget>[
EntityIcon(),
Expanded(
child: EntityName(),
),
state
],
),
);
}
}
class EntityPageContainer extends StatelessWidget {
EntityPageContainer({Key key, @required this.children}) : super(key: key);
final List<Widget> 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),
//TODO: move createIconWidgetFromEntityData into this widget
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<Widget> 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: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
Row(
children: <Widget>[
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: <Widget>[
Text(
"$value",
style: TextStyle(
fontSize: fontSize ?? 24.0,
color: fontColor ?? Colors.black
),
),
Column(
children: <Widget>[
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: <Widget>[
IconButton(
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
'mdi:chevron-double-up')),
iconSize: 30.0,
onPressed: () => onLargeInc(),
),
IconButton(
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
'mdi:chevron-double-down')),
iconSize: 30.0,
onPressed: () => onLargeDec(),
)
],
)
],
);
}
}
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.log("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<Widget> 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<Widget> 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<String> 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: <Widget>[
Text("$caption", style: TextStyle(
fontSize: captionFontSize ?? Entity.stateFontSize
)),
Row(
children: <Widget>[
Expanded(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton<String>(
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<String>(
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: <Widget>[
Expanded(
child: Text(
"$caption",
style: TextStyle(
fontSize: captionFontSize ?? Entity.stateFontSize
),
),
),
Switch(
onChanged: (value) => onChange(value),
value: value ?? false,
)
],
);
}
}

View File

@ -0,0 +1,10 @@
part of '../main.dart';
class SwitchEntity extends Entity {
SwitchEntity(Map rawData) : super(rawData);
@override
Widget _buildStatePart(BuildContext context) {
return SwitchStateWidget();
}
}

View File

@ -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();
}
}

View File

@ -3,22 +3,23 @@ part of 'main.dart';
class EntityCollection {
Map<String, Entity> _allEntities;
Map<String, Entity> views;
//Map<String, Entity> views;
bool get isEmpty => _allEntities.isEmpty;
List<Entity> get viewEntities => _allEntities.values.where((entity) => entity.isView).toList();
EntityCollection() {
_allEntities = {};
views = {};
//views = {};
}
bool get hasDefaultView => _allEntities["group.default_view"] != null;
bool get hasDefaultView => _allEntities.keys.contains("group.default_view");
void parse(List rawData) {
_allEntities.clear();
views.clear();
//views.clear();
TheLogger.log("Debug","Parsing ${rawData.length} Home Assistant entities");
TheLogger.debug("Parsing ${rawData.length} Home Assistant entities");
rawData.forEach((rawEntityData) {
addFromRaw(rawEntityData);
});
@ -26,9 +27,9 @@ class EntityCollection {
if ((entity.isGroup) && (entity.childEntityIds != null)) {
entity.childEntities = getAll(entity.childEntityIds);
}
if (entity.isView) {
/*if (entity.isView) {
views[entityId] = entity;
}
}*/
});
}
@ -37,6 +38,9 @@ class EntityCollection {
case 'sun': {
return SunEntity(rawEntityData);
}
case 'sensor': {
return SensorEntity(rawEntityData);
}
case "automation":
case "input_boolean":
case "switch": {
@ -114,31 +118,32 @@ class EntityCollection {
return _allEntities[entityId] != null;
}
Map<String,List<String>> getDefaultViewTopLevelEntities() {
Map<String,List<String>> result = {"userGroups": [], "notGroupedEntities": []};
List<String> entities = [];
List<Entity> filterEntitiesForDefaultView() {
List<Entity> result = [];
List<Entity> groups = [];
List<Entity> nonGroupEntities = [];
_allEntities.forEach((id, entity){
if ((id.indexOf("group.") == 0) && (id.indexOf(".all_") == -1) && (!entity.isView)) {
result["userGroups"].add(id);
groups.add(entity);
}
if (!entity.isGroup) {
entities.add(id);
nonGroupEntities.add(entity);
}
});
entities.forEach((entiyId) {
nonGroupEntities.forEach((entity) {
bool foundInGroup = false;
result["userGroups"].forEach((userGroupId) {
if (_allEntities[userGroupId].childEntityIds.contains(entiyId)) {
groups.forEach((groupEntity) {
if (groupEntity.childEntityIds.contains(entity.entityId)) {
foundInGroup = true;
}
});
if (!foundInGroup) {
result["notGroupedEntities"].add(entiyId);
result.add(entity);
}
});
result.insertAll(0, groups);
return result;
}
}

View File

@ -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: <Widget>[
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: <Widget>[
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)));
}
}

View File

@ -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<ClimateControlWidget> {
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: <Widget>[
_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: <Widget>[
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<Widget> controls = [];
if ((entity.supportTargetTemperatureLow) && (entity.targetLow != null)) {
controls.addAll(<Widget>[
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: <Widget>[
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<Widget> result = [];
if (entity.supportTargetHumidity) {
result.addAll(<Widget>[
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: <Widget>[
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: <Widget>[
Text(
"$value",
style: TextStyle(
fontSize: fontSize ?? 24.0,
color: fontColor ?? Colors.black
),
),
Column(
children: <Widget>[
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: <Widget>[
IconButton(
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
'mdi:chevron-double-up')),
iconSize: 30.0,
onPressed: () => onLargeInc(),
),
IconButton(
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
'mdi:chevron-double-down')),
iconSize: 30.0,
onPressed: () => onLargeDec(),
)
],
)
],
);
}
}

View File

@ -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<CoverControlWidget> {
double _tmpPosition = 0.0;
double _tmpTiltPosition = 0.0;
bool _changedHere = false;
void _setNewPosition(CoverEntity entity, double position) {
setState(() {
_tmpPosition = position.roundToDouble();
_changedHere = true;
eventBus.fire(new ServiceCallEvent(entity.domain, "set_cover_position", entity.entityId,{"position": _tmpPosition.round()}));
});
}
void _setNewTiltPosition(CoverEntity entity, double position) {
setState(() {
_tmpTiltPosition = position.roundToDouble();
_changedHere = true;
eventBus.fire(new ServiceCallEvent(entity.domain, "set_cover_tilt_position", entity.entityId,{"tilt_position": _tmpTiltPosition.round()}));
});
}
void _resetVars(CoverEntity entity) {
_tmpPosition = entity.currentPosition;
_tmpTiltPosition = entity.currentTiltPosition;
}
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
final CoverEntity entity = entityModel.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: <Widget>[
_buildPositionControls(entity),
_buildTiltControls(entity)
],
),
);
}
Widget _buildPositionControls(CoverEntity entity) {
if (entity.supportSetPosition) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(
0.0, 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<Widget> controls = [];
if (entity.supportCloseTilt || entity.supportOpenTilt || entity.supportStopTilt) {
controls.add(
CoverTiltControlsWidget()
);
}
if (entity.supportSetTiltPosition) {
controls.addAll(<Widget>[
Slider(
value: _tmpTiltPosition,
min: 0.0,
max: 100.0,
divisions: 10,
onChanged: (double value) {
setState(() {
_tmpTiltPosition = value.roundToDouble();
_changedHere = true;
});
},
onChangeEnd: (double value) => _setNewTiltPosition(entity, value),
),
Container(height: 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<Widget> 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,
);
}
}

View File

@ -0,0 +1,240 @@
part of '../../main.dart';
class LightControlsWidget extends StatefulWidget {
@override
_LightControlsWidgetState createState() => _LightControlsWidgetState();
}
class _LightControlsWidgetState extends State<LightControlsWidget> {
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: <Widget>[
_buildBrightnessControl(entity),
_buildColorTempControl(entity),
_buildColorControl(entity),
_buildEffectControl(entity)
],
);
}
Widget _buildBrightnessControl(LightEntity entity) {
if ((entity.supportBrightness) && (_tmpBrightness != null)) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(height: Entity.rowPadding,),
Text(
"Brightness",
style: TextStyle(fontSize: Entity.stateFontSize),
),
Container(height: Entity.rowPadding,),
Row(
children: <Widget>[
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: <Widget>[
Container(height: Entity.rowPadding,),
Text(
"Color temperature",
style: TextStyle(fontSize: Entity.stateFontSize),
),
Container(height: Entity.rowPadding,),
Row(
children: <Widget>[
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: <Widget>[
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);
}
}
}

View File

@ -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: <Widget>[
EntityIcon(),
Expanded(
child: EntityName(),
),
state
],
),
);
}
}

View File

@ -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<Widget> 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: <Widget>[
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,
),
),
)
],
);
}
}

View File

@ -0,0 +1,54 @@
part of '../main.dart';
class EntityColors {
static const _stateColors = {
"on": Colors.amber,
"auto": Colors.amber,
"idle": Colors.amber,
"playing": Colors.amber,
"above_horizon": Colors.amber,
"home": Colors.amber,
"open": Colors.amber,
"off": Color.fromRGBO(68, 115, 158, 1.0),
"closed": Color.fromRGBO(68, 115, 158, 1.0),
"below_horizon": Color.fromRGBO(68, 115, 158, 1.0),
"default": Color.fromRGBO(68, 115, 158, 1.0),
"heat": Colors.redAccent,
"cool": Colors.lightBlue,
"unavailable": Colors.black26,
"unknown": Colors.black26,
};
static Color stateColor(String state) {
return _stateColors[state] ?? _stateColors["default"];
}
static charts.Color chartHistoryStateColor(String state, int id) {
Color c = _stateColors[state];
if (c != null) {
return charts.Color(
r: c.red,
g: c.green,
b: c.blue,
a: c.alpha
);
} else {
return charts.MaterialPalette.getOrderedPalettes(id+1)[id].shadeDefault;
}
}
static Color historyStateColor(String state, int id) {
Color c = _stateColors[state];
if (c != null) {
return c;
} else {
if (id > -1) {
charts.Color c1 = charts.MaterialPalette.getOrderedPalettes(id + 1)[id].shadeDefault;
return Color.fromARGB(c1.a, c1.r, c1.g, c1.b);
} else {
return _stateColors["on"];
}
}
}
}

View File

@ -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,
EntityColors.stateColor(entityModel.entity.state)
),
),
onTap: () => entityModel.handleTap
? eventBus.fire(new ShowEntityPageEvent(entityModel.entity))
: null,
);
}
}

View File

@ -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,
);
}
}

View File

@ -0,0 +1,14 @@
part of '../main.dart';
class EntityPageContainer extends StatelessWidget {
EntityPageContainer({Key key, @required this.children}) : super(key: key);
final List<Widget> children;
@override
Widget build(BuildContext context) {
return ListView(
children: children,
);
}
}

View File

@ -0,0 +1,230 @@
part of '../../main.dart';
class CombinedHistoryChartWidget extends StatefulWidget {
final rawHistory;
final EntityHistoryConfig config;
const CombinedHistoryChartWidget({Key key, @required this.rawHistory, @required this.config}) : super(key: key);
@override
State<StatefulWidget> createState() {
return new _CombinedHistoryChartWidgetState();
}
}
class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget> {
int _selectedId = -1;
List<charts.Series<EntityHistoryMoment, DateTime>> _parsedHistory;
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
_parsedHistory = _parseHistory();
DateTime selectedTime;
List<String> selectedStates = [];
List<int> colorIndexes = [];
if ((_selectedId > -1) && (_parsedHistory != null) && (_parsedHistory.first.data.length >= (_selectedId + 1))) {
selectedTime = _parsedHistory.first.data[_selectedId].startTime;
_parsedHistory.where((item) { return item.id == "state"; }).forEach((item) {
selectedStates.add(item.data[_selectedId].state);
colorIndexes.add(item.data[_selectedId].colorId);
});
_parsedHistory.where((item) { return item.id == "value"; }).forEach((item) {
selectedStates.add("${item.data[_selectedId].value ?? '-'}");
colorIndexes.add(item.data[_selectedId].colorId);
});
}
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
HistoryControlWidget(
selectedTimeStart: selectedTime,
selectedStates: selectedStates,
onPrevTap: () => _selectPrev(),
onNextTap: () => _selectNext(),
colorIndexes: colorIndexes,
),
SizedBox(
height: 150.0,
child: charts.TimeSeriesChart(
_parsedHistory,
animate: false,
primaryMeasureAxis: new charts.NumericAxisSpec(
tickProviderSpec:
new charts.BasicNumericTickProviderSpec(zeroBound: false)),
dateTimeFactory: const charts.LocalDateTimeFactory(),
defaultRenderer: charts.LineRendererConfig(
includeArea: false,
includePoints: true
),
selectionModels: [
new charts.SelectionModelConfig(
type: charts.SelectionModelType.info,
listener: (model) => _onSelectionChanged(model),
)
],
customSeriesRenderers: [
new charts.SymbolAnnotationRendererConfig(
customRendererId: "stateBars"
)
],
),
)
],
);
}
double _parseToDouble(temp1) {
if (temp1 is int) {
return temp1.toDouble();
} else if (temp1 is double) {
return temp1;
} else {
return double.tryParse("$temp1");
}
}
List<charts.Series<EntityHistoryMoment, DateTime>> _parseHistory() {
TheLogger.debug(" parsing history...");
Map<String, List<EntityHistoryMoment>> numericDataLists = {};
int colorIdCounter = 0;
widget.config.numericAttributesToShow.forEach((String attrName) {
TheLogger.debug(" parsing attribute $attrName");
List<EntityHistoryMoment> data = [];
DateTime now = DateTime.now();
for (var i = 0; i < widget.rawHistory.length; i++) {
var stateData = widget.rawHistory[i];
DateTime startTime = DateTime.tryParse(stateData["last_updated"])?.toLocal();
DateTime endTime;
bool hiddenLine;
double value;
double previousValue = 0.0;
value = _parseToDouble(stateData["attributes"]["$attrName"]);
bool hiddenDot = (value == null);
if (hiddenDot && i > 0) {
previousValue = data[i-1].value ?? data[i-1].previousValue;
}
if (i < (widget.rawHistory.length - 1)) {
endTime = DateTime.tryParse(widget.rawHistory[i+1]["last_updated"])?.toLocal();
double nextValue = _parseToDouble(widget.rawHistory[i+1]["attributes"]["$attrName"]);
hiddenLine = (nextValue == null || hiddenDot);
} else {
hiddenLine = hiddenDot;
endTime = now;
}
data.add(EntityHistoryMoment(
value: value,
previousValue: previousValue,
hiddenDot: hiddenDot,
hiddenLine: hiddenLine,
state: stateData["state"],
startTime: startTime,
endTime: endTime,
id: i,
colorId: colorIdCounter
));
}
data.add(EntityHistoryMoment(
value: data.last.value,
previousValue: data.last.previousValue,
hiddenDot: data.last.hiddenDot,
hiddenLine: data.last.hiddenLine,
state: data.last.state,
startTime: now,
id: widget.rawHistory.length,
colorId: colorIdCounter
));
numericDataLists.addAll({attrName: data});
colorIdCounter += 1;
});
if ((_selectedId == -1) && (numericDataLists.isNotEmpty)) {
_selectedId = 0;
}
List<charts.Series<EntityHistoryMoment, DateTime>> result = [];
numericDataLists.forEach((attrName, dataList) {
TheLogger.debug(" adding ${dataList.length} data values");
result.add(
new charts.Series<EntityHistoryMoment, DateTime>(
id: "value",
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor("_", historyMoment.colorId),
radiusPxFn: (EntityHistoryMoment historyMoment, __) {
if (historyMoment.hiddenDot) {
return 0.0;
} else if (historyMoment.id == _selectedId) {
return 5.0;
} else {
return 1.0;
}
},
strokeWidthPxFn: (EntityHistoryMoment historyMoment, __) => historyMoment.hiddenLine ? 0.0 : 2.0,
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
measureFn: (EntityHistoryMoment historyMoment, _) => historyMoment.value ?? historyMoment.previousValue,
data: dataList,
/*domainLowerBoundFn: (CombinedEntityStateHistoryMoment historyMoment, _) => historyMoment.time.subtract(Duration(hours: 1)),
domainUpperBoundFn: (CombinedEntityStateHistoryMoment historyMoment, _) => historyMoment.time.add(Duration(hours: 1)),*/
)
);
});
result.add(
new charts.Series<EntityHistoryMoment, DateTime>(
id: 'state',
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 4.0,
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
domainLowerBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
domainUpperBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.endTime ?? DateTime.now(),
// No measure values are needed for symbol annotations.
measureFn: (_, __) => null,
data: numericDataLists[numericDataLists.keys.first],
)
// Configure our custom symbol annotation renderer for this series.
..setAttribute(charts.rendererIdKey, 'stateBars')
// Optional radius for the annotation shape. If not specified, this will
// default to the same radius as the points.
//..setAttribute(charts.boundsLineRadiusPxKey, 3.5)
);
return result;
}
void _selectPrev() {
if (_selectedId > 0) {
setState(() {
_selectedId -= 1;
});
}
}
void _selectNext() {
if (_selectedId < (_parsedHistory.first.data.length - 1)) {
setState(() {
_selectedId += 1;
});
}
}
void _onSelectionChanged(charts.SelectionModel model) {
final selectedDatum = model.selectedDatum;
int selectedId;
if (selectedDatum.isNotEmpty) {
selectedId = selectedDatum.first.datum.id;
setState(() {
_selectedId = selectedId;
});
} else {
setState(() {
});
}
}
}

View File

@ -0,0 +1,130 @@
part of '../../main.dart';
class EntityHistoryWidgetType {
static const int simple = 0;
static const int numericState = 1;
static const int numericAttributes = 2;
}
class EntityHistoryConfig {
final int chartType;
final List<String> numericAttributesToShow;
final bool numericState;
EntityHistoryConfig({this.chartType, this.numericAttributesToShow, this.numericState: true});
}
class EntityHistoryWidget extends StatefulWidget {
final EntityHistoryConfig config;
const EntityHistoryWidget({Key key, @required this.config}) : super(key: key);
@override
_EntityHistoryWidgetState createState() {
return new _EntityHistoryWidgetState();
}
}
class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
List _history;
bool _needToUpdateHistory;
@override
void initState() {
super.initState();
_needToUpdateHistory = true;
}
void _loadHistory(HomeAssistant ha, String entityId) {
ha.getHistory(entityId).then((history){
setState(() {
_history = history.isNotEmpty ? history[0] : [];
_needToUpdateHistory = false;
});
}).catchError((e) {
TheLogger.error("Error loading $entityId history: $e");
setState(() {
_history = [];
_needToUpdateHistory = false;
});
});
}
@override
Widget build(BuildContext context) {
final HomeAssistantModel homeAssistantModel = HomeAssistantModel.of(context);
final EntityModel entityModel = EntityModel.of(context);
final Entity entity = entityModel.entity;
if (!_needToUpdateHistory) {
_needToUpdateHistory = true;
} else {
_loadHistory(homeAssistantModel.homeAssistant, entity.entityId);
}
return _buildChart();
}
Widget _buildChart() {
List<Widget> children = [];
if (_history == null) {
children.add(
Text("Loading history...")
);
} else if (_history.isEmpty) {
children.add(
Text("No history")
);
} else {
children.add(
_selectChartWidget()
);
}
children.add(Divider());
return Padding(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, Entity.rowPadding),
child: Column(
children: children,
),
);
}
Widget _selectChartWidget() {
TheLogger.debug(" selecting history widget (${widget.config.chartType})");
switch (widget.config.chartType) {
case EntityHistoryWidgetType.simple: {
TheLogger.debug(" Simple selected");
return SimpleStateHistoryChartWidget(
rawHistory: _history,
);
}
case EntityHistoryWidgetType.numericState: {
TheLogger.debug(" EntityHistory selected");
return NumericStateHistoryChartWidget(
rawHistory: _history,
config: widget.config,
);
}
case EntityHistoryWidgetType.numericAttributes: {
TheLogger.debug(" NumericAttributes selected");
return CombinedHistoryChartWidget(
rawHistory: _history,
config: widget.config,
);
}
default: {
TheLogger.debug(" Simple selected as default");
return SimpleStateHistoryChartWidget(
rawHistory: _history,
);
}
}
}
}

View File

@ -0,0 +1,25 @@
part of '../../main.dart';
class EntityHistoryMoment {
final DateTime startTime;
final DateTime endTime;
final double value;
final double previousValue;
final int id;
final int colorId;
final String state;
final bool hiddenDot;
final bool hiddenLine;
EntityHistoryMoment({
this.value,
this.previousValue,
this.hiddenDot,
this.hiddenLine,
this.state,
@required this.startTime,
this.endTime,
@required this.id,
this.colorId
});
}

View File

@ -0,0 +1,86 @@
part of '../../main.dart';
class HistoryControlWidget extends StatelessWidget {
final Function onPrevTap;
final Function onNextTap;
final DateTime selectedTimeStart;
final DateTime selectedTimeEnd;
final List<String> selectedStates;
final List<int> colorIndexes;
const HistoryControlWidget({Key key, this.onPrevTap, this.onNextTap, this.selectedTimeStart, this.selectedTimeEnd, this.selectedStates, @ required this.colorIndexes}) : super(key: key);
@override
Widget build(BuildContext context) {
if (selectedTimeStart != null) {
return
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
IconButton(
icon: Icon(Icons.chevron_left),
padding: EdgeInsets.all(0.0),
iconSize: 40.0,
onPressed: onPrevTap,
),
Expanded(
child: Padding(
padding: EdgeInsets.only(right: 10.0),
child: _buildStates(),
),
),
_buildTime(),
IconButton(
icon: Icon(Icons.chevron_right),
padding: EdgeInsets.all(0.0),
iconSize: 40.0,
onPressed: onNextTap,
),
],
);
} else {
return Container(height: 48.0);
}
}
Widget _buildStates() {
List<Widget> children = [];
for (int i = 0; i < selectedStates.length; i++) {
children.add(
Text(
"${selectedStates[i] ?? '-'}",
textAlign: TextAlign.right,
style: TextStyle(
fontWeight: FontWeight.bold,
color: EntityColors.historyStateColor(selectedStates[i], colorIndexes[i]),
fontSize: 22.0
),
)
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: children,
);
}
Widget _buildTime() {
List<Widget> children = [];
children.add(
Text("${formatDate(selectedTimeStart, [M, ' ', d, ', ', HH, ':', nn, ':', ss])}", textAlign: TextAlign.left,)
);
if (selectedTimeEnd != null) {
children.add(
Text("${formatDate(selectedTimeEnd, [M, ' ', d, ', ', HH, ':', nn, ':', ss])}", textAlign: TextAlign.left,)
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
);
}
}

View File

@ -0,0 +1,160 @@
part of '../../main.dart';
class NumericStateHistoryChartWidget extends StatefulWidget {
final rawHistory;
final EntityHistoryConfig config;
const NumericStateHistoryChartWidget({Key key, @required this.rawHistory, @required this.config}) : super(key: key);
@override
State<StatefulWidget> createState() {
return new _NumericStateHistoryChartWidgetState();
}
}
class _NumericStateHistoryChartWidgetState extends State<NumericStateHistoryChartWidget> {
int _selectedId = -1;
List<charts.Series<EntityHistoryMoment, DateTime>> _parsedHistory;
@override
Widget build(BuildContext context) {
_parsedHistory = _parseHistory();
DateTime selectedTime;
double selectedState;
if ((_selectedId > -1) && (_parsedHistory != null) && (_parsedHistory.first.data.length >= (_selectedId + 1))) {
selectedTime = _parsedHistory.first.data[_selectedId].startTime;
selectedState = _parsedHistory.first.data[_selectedId].value;
}
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
HistoryControlWidget(
selectedTimeStart: selectedTime,
selectedStates: ["${selectedState ?? '-'}"],
onPrevTap: () => _selectPrev(),
onNextTap: () => _selectNext(),
colorIndexes: [-1],
),
SizedBox(
height: 150.0,
child: charts.TimeSeriesChart(
_parsedHistory,
animate: false,
primaryMeasureAxis: new charts.NumericAxisSpec(
tickProviderSpec:
new charts.BasicNumericTickProviderSpec(zeroBound: false)),
dateTimeFactory: const charts.LocalDateTimeFactory(),
defaultRenderer: charts.LineRendererConfig(
includePoints: true
),
/*primaryMeasureAxis: charts.NumericAxisSpec(
renderSpec: charts.NoneRenderSpec()
),*/
selectionModels: [
new charts.SelectionModelConfig(
type: charts.SelectionModelType.info,
listener: (model) => _onSelectionChanged(model),
)
],
),
)
],
);
}
List<charts.Series<EntityHistoryMoment, DateTime>> _parseHistory() {
List<EntityHistoryMoment> data = [];
DateTime now = DateTime.now();
for (var i = 0; i < widget.rawHistory.length; i++) {
var stateData = widget.rawHistory[i];
DateTime time = DateTime.tryParse(stateData["last_updated"])?.toLocal();
double value = double.tryParse(stateData["state"]);
double previousValue = 0.0;
bool hiddenDot = (value == null);
bool hiddenLine;
if (hiddenDot && i > 0) {
previousValue = data[i-1].value ?? data[i-1].previousValue;
}
if (i < (widget.rawHistory.length - 1)) {
double nextValue = double.tryParse(widget.rawHistory[i+1]["state"]);
hiddenLine = (nextValue == null || hiddenDot);
} else {
hiddenLine = hiddenDot;
}
data.add(EntityHistoryMoment(
value: value,
previousValue: previousValue,
hiddenDot: hiddenDot,
hiddenLine: hiddenLine,
startTime: time,
id: i
));
}
data.add(EntityHistoryMoment(
value: data.last.value,
previousValue: data.last.previousValue,
hiddenDot: data.last.hiddenDot,
hiddenLine: data.last.hiddenLine,
startTime: now,
id: widget.rawHistory.length
));
if (_selectedId == -1) {
_selectedId = 0;
}
return [
new charts.Series<EntityHistoryMoment, DateTime>(
id: 'State',
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor("on", -1),
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
measureFn: (EntityHistoryMoment historyMoment, _) => historyMoment.value ?? historyMoment.previousValue,
data: data,
strokeWidthPxFn: (EntityHistoryMoment historyMoment, __) => historyMoment.hiddenLine ? 0.0 : 2.0,
radiusPxFn: (EntityHistoryMoment historyMoment, __) {
if (historyMoment.hiddenDot) {
return 0.0;
} else if (historyMoment.id == _selectedId) {
return 5.0;
} else {
return 1.0;
}
},
)
];
}
void _selectPrev() {
if (_selectedId > 0) {
setState(() {
_selectedId -= 1;
});
}
}
void _selectNext() {
if (_selectedId < (_parsedHistory.first.data.length - 1)) {
setState(() {
_selectedId += 1;
});
}
}
void _onSelectionChanged(charts.SelectionModel model) {
final selectedDatum = model.selectedDatum;
int selectedId;
if (selectedDatum.isNotEmpty) {
selectedId = selectedDatum.first.datum.id;
setState(() {
_selectedId = selectedId;
});
} else {
setState(() {
});
}
}
}

View File

@ -0,0 +1,176 @@
part of '../../main.dart';
class SimpleStateHistoryChartWidget extends StatefulWidget {
final rawHistory;
const SimpleStateHistoryChartWidget({Key key, this.rawHistory}) : super(key: key);
@override
State<StatefulWidget> createState() {
return new _SimpleStateHistoryChartWidgetState();
}
}
class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartWidget> {
int _selectedId = -1;
List<charts.Series<EntityHistoryMoment, DateTime>> _parsedHistory;
@override
Widget build(BuildContext context) {
_parsedHistory = _parseHistory();
DateTime selectedTimeStart;
DateTime selectedTimeEnd;
String selectedState;
if ((_selectedId > -1) && (_parsedHistory != null) && (_parsedHistory.first.data.length >= (_selectedId + 1))) {
selectedTimeStart = _parsedHistory.first.data[_selectedId].startTime;
selectedTimeEnd = _parsedHistory.first.data[_selectedId].endTime;
selectedState = _parsedHistory.first.data[_selectedId].state;
}
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
HistoryControlWidget(
selectedTimeStart: selectedTimeStart,
selectedTimeEnd: selectedTimeEnd,
selectedStates: [selectedState],
onPrevTap: () => _selectPrev(),
onNextTap: () => _selectNext(),
colorIndexes: [_parsedHistory.first.data[_selectedId].colorId],
),
SizedBox(
height: 70.0,
child: charts.TimeSeriesChart(
_parsedHistory,
animate: false,
dateTimeFactory: const charts.LocalDateTimeFactory(),
primaryMeasureAxis: charts.NumericAxisSpec(
renderSpec: charts.NoneRenderSpec()
),
selectionModels: [
new charts.SelectionModelConfig(
type: charts.SelectionModelType.info,
listener: (model) => _onSelectionChanged(model),
)
],
customSeriesRenderers: [
new charts.PointRendererConfig(
// ID used to link series to this renderer.
customRendererId: 'startValuePoints'),
new charts.PointRendererConfig(
// ID used to link series to this renderer.
customRendererId: 'endValuePoints')
],
),
)
],
);
}
List<charts.Series<EntityHistoryMoment, DateTime>> _parseHistory() {
List<EntityHistoryMoment> data = [];
DateTime now = DateTime.now();
Map<String, int> cachedStates = {};
for (var i = 0; i < widget.rawHistory.length; i++) {
var stateData = widget.rawHistory[i];
DateTime startTime = DateTime.tryParse(stateData["last_updated"])?.toLocal();
DateTime endTime;
if (i < (widget.rawHistory.length - 1)) {
endTime = DateTime.tryParse(widget.rawHistory[i+1]["last_updated"])?.toLocal();
} else {
endTime = now;
}
if (cachedStates[stateData["state"]] == null) {
cachedStates.addAll({"${stateData["state"]}": cachedStates.length});
}
data.add(EntityHistoryMoment(
state: stateData["state"],
startTime: startTime,
endTime: endTime,
id: i,
colorId: cachedStates[stateData["state"]]
));
}
data.add(EntityHistoryMoment(
state: data.last.state,
startTime: now,
id: widget.rawHistory.length,
colorId: data.last.colorId
));
if (_selectedId == -1) {
_selectedId = 0;
}
return [
new charts.Series<EntityHistoryMoment, DateTime>(
id: 'State',
strokeWidthPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 6.0 : 3.0,
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
data: data,
),
new charts.Series<EntityHistoryMoment, DateTime>(
id: 'State',
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 3.0,
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
data: data,
)..setAttribute(charts.rendererIdKey, 'startValuePoints'),
new charts.Series<EntityHistoryMoment, DateTime>(
id: 'State',
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 3.0,
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.endTime ?? DateTime.now(),
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
data: data,
)..setAttribute(charts.rendererIdKey, 'endValuePoints')
];
}
void _selectPrev() {
if (_selectedId > 0) {
setState(() {
_selectedId -= 1;
});
}
}
void _selectNext() {
if (_selectedId < (_parsedHistory.first.data.length - 2)) {
setState(() {
_selectedId += 1;
});
}
}
void _onSelectionChanged(charts.SelectionModel model) {
final selectedDatum = model.selectedDatum;
int selectedId;
if ((selectedDatum.isNotEmpty) &&(selectedDatum.first.datum.endTime != null)) {
selectedId = selectedDatum.first.datum.id;
setState(() {
_selectedId = selectedId;
});
} else {
setState(() {
});
}
}
}
/*
class SimpleEntityStateHistoryMoment {
final DateTime startTime;
final DateTime endTime;
final String state;
final int id;
final int colorId;
SimpleEntityStateHistoryMoment(this.state, this.startTime, this.endTime, this.id, this.colorId);
}*/

View File

@ -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),
),
);
}
}

View File

@ -0,0 +1,62 @@
part of '../main.dart';
class ModeSelectorWidget extends StatelessWidget {
final String caption;
final List<String> 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: <Widget>[
Text("$caption", style: TextStyle(
fontSize: captionFontSize ?? Entity.stateFontSize
)),
Row(
children: <Widget>[
Expanded(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton<String>(
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<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (mode) => onChange(mode),
),
),
)
],
),
Container(height: bottomPadding ?? Entity.rowPadding,)
],
);
}
}

View File

@ -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: <Widget>[
Expanded(
child: Text(
"$caption",
style: TextStyle(
fontSize: captionFontSize ?? Entity.stateFontSize
),
),
),
Switch(
onChanged: (value) => onChange(value),
value: value ?? false,
)
],
);
}
}

View File

@ -0,0 +1,42 @@
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;
}
}
class HomeAssistantModel extends InheritedWidget {
const HomeAssistantModel({
Key key,
@required this.homeAssistant,
@required Widget child,
}) : super(key: key, child: child);
final HomeAssistant homeAssistant;
static HomeAssistantModel of(BuildContext context) {
return context.inheritFromWidgetOfExactType(HomeAssistantModel);
}
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
}

View File

@ -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),
),
);
}
}

View File

@ -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: <Widget>[
Row(
children: <Widget>[
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,
));
}
}

View File

@ -0,0 +1,65 @@
part of '../../main.dart';
class CoverStateWidget extends StatelessWidget {
void _open(CoverEntity entity) {
eventBus.fire(new ServiceCallEvent(
entity.domain, "open_cover", entity.entityId, null));
}
void _close(CoverEntity entity) {
eventBus.fire(new ServiceCallEvent(
entity.domain, "close_cover", entity.entityId, null));
}
void _stop(CoverEntity entity) {
eventBus.fire(new ServiceCallEvent(
entity.domain, "stop_cover", entity.entityId, null));
}
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
final CoverEntity entity = entityModel.entity;
List<Widget> 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,
);
}
}

View File

@ -0,0 +1,75 @@
part of '../../main.dart';
class DateTimeStateWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
final DateTimeEntity entity = entityModel.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));
}
}

View File

@ -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<SelectStateWidget> {
void setNewState(domain, entityId, newValue) {
eventBus.fire(new ServiceCallEvent(domain, "select_option", entityId,
{"option": "$newValue"}));
}
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
final SelectEntity entity = entityModel.entity;
Widget ctrl;
if (entity.listOptions.isNotEmpty) {
ctrl = DropdownButton<String>(
value: entity.state,
items: entity.listOptions.map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
onChanged: (_) {
setNewState(entity.domain, entity.entityId,_);
},
);
} else {
ctrl = Text('---');
}
return Expanded(
//width: Entity.INPUT_WIDTH,
child: ctrl,
);
}
}

View File

@ -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,
));
}
}

View File

@ -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<SliderStateWidget> {
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;
}
}
}

View File

@ -0,0 +1,60 @@
part of '../../main.dart';
class SwitchStateWidget extends StatefulWidget {
@override
_SwitchStateWidgetState createState() => _SwitchStateWidgetState();
}
class _SwitchStateWidgetState extends State<SwitchStateWidget> {
@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: <Widget>[
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
)
],
);
}
}
}

View File

@ -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<TextInputStateWidget> {
String _tmpValue;
String _entityState;
String _entityDomain;
String _entityId;
int _minLength;
int _maxLength;
FocusNode _focusNode = FocusNode();
bool validValue = false;
@override
void initState() {
super.initState();
_focusNode.addListener(_focusListener);
}
void setNewState(newValue, domain, entityId) {
if (validate(newValue, _minLength, _maxLength)) {
eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId,
{"value": "$newValue"}));
} else {
setState(() {
_tmpValue = _entityState;
});
}
}
bool validate(newValue, minLength, maxLength) {
if (newValue is String) {
validValue = (newValue.length >= minLength) &&
(maxLength == -1 ||
(newValue.length <= maxLength));
} else {
validValue = true;
}
return validValue;
}
void _focusListener() {
if (!_focusNode.hasFocus && (_tmpValue != _entityState)) {
setNewState(_tmpValue, _entityDomain, _entityId);
}
}
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
final TextEntity entity = entityModel.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();
}
}

View File

@ -4,6 +4,7 @@ class HomeAssistant {
String _webSocketAPIEndpoint;
String _password;
String _authType;
bool _useLovelace;
IOWebSocketChannel _hassioChannel;
SendMessageQueue _messageQueue;
@ -14,14 +15,18 @@ class HomeAssistant {
int _subscriptionMessageId = 0;
int _configMessageId = 0;
int _userInfoMessageId = 0;
EntityCollection _entities;
ViewBuilder _viewBuilder;
int _lovelaceMessageId = 0;
EntityCollection entities;
HomeAssistantUI ui;
Map _instanceConfig = {};
String _userName;
Map _rawLovelaceData;
Completer _fetchCompleter;
Completer _statesCompleter;
Completer _servicesCompleter;
Completer _lovelaceCompleter;
Completer _configCompleter;
Completer _connectionCompleter;
Completer _userInfoCompleter;
@ -38,28 +43,28 @@ class HomeAssistant {
String get locationName => _instanceConfig["location_name"] ?? "";
String get userName => _userName ?? locationName;
String get userAvatarText => userName.length > 0 ? userName[0] : "";
int get viewsCount => _entities.views.length ?? 0;
EntityCollection get entities => _entities;
//int get viewsCount => entities.views.length ?? 0;
HomeAssistant() {
_entities = EntityCollection();
entities = EntityCollection();
_messageQueue = SendMessageQueue(messageExpirationTime);
}
void updateConnectionSettings(String url, String password, String authType) {
void updateSettings(String url, String password, String authType, bool useLovelace) {
_webSocketAPIEndpoint = url;
_password = password;
_authType = authType;
_useLovelace = useLovelace;
TheLogger.debug( "Use lovelace is $_useLovelace");
}
Future fetch() {
if ((_fetchCompleter != null) && (!_fetchCompleter.isCompleted)) {
TheLogger.log("Warning","Previous fetch is not complited");
TheLogger.warning("Previous fetch is not complited");
} else {
_fetchCompleter = new Completer();
_fetchTimer = Timer(fetchTimeout, () {
TheLogger.log("Error", "Data fetching timeout");
TheLogger.error( "Data fetching timeout");
disconnect().then((_) {
_completeFetching({
"errorCode": 9,
@ -79,7 +84,7 @@ class HomeAssistant {
disconnect() async {
if ((_hassioChannel != null) && (_hassioChannel.closeCode == null) && (_hassioChannel.sink != null)) {
await _hassioChannel.sink.close().timeout(Duration(seconds: 3),
onTimeout: () => TheLogger.log("Debug", "Socket sink closed")
onTimeout: () => TheLogger.debug( "Socket sink closed")
);
await _socketSubscription.cancel();
_hassioChannel = null;
@ -89,15 +94,15 @@ class HomeAssistant {
Future _connection() {
if ((_connectionCompleter != null) && (!_connectionCompleter.isCompleted)) {
TheLogger.log("Debug","Previous connection is not complited");
TheLogger.debug("Previous connection is not complited");
} else {
if ((_hassioChannel == null) || (_hassioChannel.closeCode != null)) {
_connectionCompleter = new Completer();
autoReconnect = false;
disconnect().then((_){
TheLogger.log("Debug", "Socket connecting...");
TheLogger.debug( "Socket connecting...");
_connectionTimer = Timer(connectTimeout, () {
TheLogger.log("Error", "Socket connection timeout");
TheLogger.error( "Socket connection timeout");
_handleSocketError(null);
});
if (_socketSubscription != null) {
@ -120,15 +125,15 @@ class HomeAssistant {
}
void _handleSocketClose() {
TheLogger.log("Debug","Socket disconnected. Automatic reconnect is $autoReconnect");
TheLogger.debug("Socket disconnected. Automatic reconnect is $autoReconnect");
if (autoReconnect) {
_reconnect();
}
}
void _handleSocketError(e) {
TheLogger.log("Error","Socket stream Error: $e");
TheLogger.log("Debug","Automatic reconnect is $autoReconnect");
TheLogger.error("Socket stream Error: $e");
TheLogger.debug("Automatic reconnect is $autoReconnect");
if (autoReconnect) {
_reconnect();
} else {
@ -152,11 +157,15 @@ class HomeAssistant {
_getData() async {
List<Future> futures = [];
futures.add(_getStates());
if (_useLovelace) {
futures.add(_getLovelace());
}
futures.add(_getConfig());
futures.add(_getServices());
futures.add(_getUserInfo());
try {
await Future.wait(futures);
_createUI();
_completeFetching(null);
} catch (error) {
_completeFetching(error);
@ -171,7 +180,7 @@ class HomeAssistant {
_fetchCompleter.completeError(error);
} else {
autoReconnect = true;
TheLogger.log("Debug", "Fetch complete successful");
TheLogger.debug( "Fetch complete successful");
_fetchCompleter.complete();
}
}
@ -186,8 +195,13 @@ class HomeAssistant {
_connectionCompleter.complete();
}
} else if (error != null) {
if (error is Error) {
eventBus.fire(ShowErrorEvent(error.toString(), 12));
} else {
eventBus.fire(ShowErrorEvent(error["errorMessage"], error["errorCode"]));
}
}
}
_handleMessage(String message) {
@ -204,24 +218,26 @@ class HomeAssistant {
_parseConfig(data);
} else if (data["id"] == _statesMessageId) {
_parseEntities(data);
} else if (data["id"] == _lovelaceMessageId) {
_handleLovelace(data);
} else if (data["id"] == _servicesMessageId) {
_parseServices(data);
} else if (data["id"] == _userInfoMessageId) {
_parseUserInfo(data);
} else if (data["id"] == _currentMessageId) {
TheLogger.log("Debug","[Received] => Request id:$_currentMessageId was successful");
TheLogger.debug("[Received] => Request id:$_currentMessageId was successful");
}
} else if (data["type"] == "event") {
if ((data["event"] != null) && (data["event"]["event_type"] == "state_changed")) {
TheLogger.log("Debug","[Received] => ${data['type']}.${data["event"]["event_type"]}: ${data["event"]["data"]["entity_id"]}");
TheLogger.debug("[Received] => ${data['type']}.${data["event"]["event_type"]}: ${data["event"]["data"]["entity_id"]}");
_handleEntityStateChange(data["event"]["data"]);
} else if (data["event"] != null) {
TheLogger.log("Warning","Unhandled event type: ${data["event"]["event_type"]}");
TheLogger.warning("Unhandled event type: ${data["event"]["event_type"]}");
} else {
TheLogger.log("Error","Event is null: $message");
TheLogger.error("Event is null: $message");
}
} else {
TheLogger.log("Warning","Unknown message type: $message");
TheLogger.warning("Unknown message type: $message");
}
}
@ -249,6 +265,15 @@ class HomeAssistant {
return _statesCompleter.future;
}
Future _getLovelace() {
_lovelaceCompleter = new Completer();
_incrementMessageId();
_lovelaceMessageId = _currentMessageId;
_sendMessageRaw('{"id": $_lovelaceMessageId, "type": "lovelace/config"}', false);
return _lovelaceCompleter.future;
}
Future _getUserInfo() {
_userInfoCompleter = new Completer();
_incrementMessageId();
@ -272,7 +297,7 @@ class HomeAssistant {
}
void _sendAuthMessageRaw(String message) {
TheLogger.log("Debug", "[Sending] ==> auth request");
TheLogger.debug( "[Sending] ==> auth request");
_hassioChannel.sink.add(message);
}
@ -281,11 +306,11 @@ class HomeAssistant {
if (queued) _messageQueue.add(message);
_connection().then((r) {
_messageQueue.getActualMessages().forEach((message){
TheLogger.log("Debug", "[Sending queued] ==> $message");
TheLogger.debug( "[Sending queued] ==> $message");
_hassioChannel.sink.add(message);
});
if (!queued) {
TheLogger.log("Debug", "[Sending] ==> $message");
TheLogger.debug( "[Sending] ==> $message");
_hassioChannel.sink.add(message);
}
sendCompleter.complete();
@ -312,9 +337,9 @@ class HomeAssistant {
}
void _handleEntityStateChange(Map eventData) {
//TheLogger.log("Debug", "New state for ${eventData['entity_id']}");
//TheLogger.debug( "New state for ${eventData['entity_id']}");
Map data = Map.from(eventData);
_entities.updateState(data);
entities.updateState(data);
eventBus.fire(new StateChangedEvent(data["entity_id"], null, false));
}
@ -338,28 +363,70 @@ class HomeAssistant {
void _parseServices(response) {
_servicesCompleter.complete();
/*if (response["success"] == false) {
_servicesCompleter.completeError({"errorCode": 4, "errorMessage": response["error"]["message"]});
return;
}
try {
Map data = response["result"];
Map result = {};
TheLogger.log("Debug","Parsing ${data.length} Home Assistant service domains");
data.forEach((domain, services) {
result[domain] = Map.from(services);
services.forEach((serviceName, serviceData) {
if (_entitiesData.isExist("$domain.$serviceName")) {
result[domain].remove(serviceName);
void _handleLovelace(response) {
if (response["success"] == true) {
_rawLovelaceData = response["result"];
} else {
_rawLovelaceData = null;
}
_lovelaceCompleter.complete();
}
void _parseLovelace() {
ui = HomeAssistantUI();
TheLogger.debug("Parsing lovelace config");
TheLogger.debug("--Title: ${_rawLovelaceData["title"]}");
int viewCounter = 0;
TheLogger.debug("--Views count: ${_rawLovelaceData['views'].length}");
_rawLovelaceData["views"].forEach((rawView){
TheLogger.debug("----view id: ${rawView['id']}");
HAView view = HAView(
count: viewCounter,
id: "${rawView['id']}",
name: rawView['title'],
iconName: rawView['icon']
);
view.cards.addAll(_createLovelaceCards(rawView["cards"] ?? []));
ui.views.add(
view
);
viewCounter += 1;
});
}
List<HACard> _createLovelaceCards(List rawCards) {
List<HACard> result = [];
rawCards.forEach((rawCard){
if (rawCard["cards"] != null) {
TheLogger.debug("------card: ${rawCard['type']} has child cards");
result.addAll(_createLovelaceCards(rawCard["cards"]));
} else {
TheLogger.debug("------card: ${rawCard['type']}");
HACard card = HACard(
id: "card",
name: rawCard["title"],
type: rawCard['type']
);
rawCard["entities"]?.forEach((rawEntity) {
if (rawEntity is String) {
if (entities.isExist(rawEntity)) {
card.entities.add(entities.get(rawEntity));
}
} else {
if (entities.isExist(rawEntity["entity"])) {
card.entities.add(entities.get(rawEntity["entity"]));
}
}
});
if (rawCard["entity"] != null) {
card.linkedEntity = entities.get(rawCard["entity"]);
}
result.add(card);
}
});
_servicesData = result;
_servicesCompleter.complete();
} catch (e) {
TheLogger.log("Error","Error parsing services. But they are not used :-)");
_servicesCompleter.complete();
}*/
return result;
}
void _parseEntities(response) async {
@ -367,22 +434,56 @@ class HomeAssistant {
_statesCompleter.completeError({"errorCode": 3, "errorMessage": response["error"]["message"]});
return;
}
_entities.parse(response["result"]);
_viewBuilder = ViewBuilder(entityCollection: _entities);
entities.parse(response["result"]);
_statesCompleter.complete();
}
Widget buildViews(BuildContext context) {
return _viewBuilder.buildWidget(context);
void _createUI() {
if ((_useLovelace) && (_rawLovelaceData != null)) {
_parseLovelace();
} else {
ui = HomeAssistantUI();
int viewCounter = 0;
if (!entities.hasDefaultView) {
TheLogger.debug( "--Default view");
HAView view = HAView(
count: viewCounter,
id: "group.default_view",
name: "Home",
childEntities: entities.filterEntitiesForDefaultView()
);
ui.views.add(
view
);
viewCounter += 1;
}
entities.viewEntities.forEach((viewEntity) {
TheLogger.debug( "--View: ${viewEntity.entityId}");
HAView view = HAView(
count: viewCounter,
id: viewEntity.entityId,
name: viewEntity.displayName,
childEntities: viewEntity.childEntities
);
view.linkedEntity = viewEntity;
ui.views.add(
view
);
viewCounter += 1;
});
}
}
Widget buildViews(BuildContext context, bool lovelace) {
return ui.build(context);
}
Future<List> getHistory(String entityId) async {
DateTime now = DateTime.now();
//String endTime = formatDate(now, [yyyy, '-', mm, '-', dd, 'T', HH, ':', nn, ':', ss, z]);
String startTime = formatDate(now.subtract(Duration(hours: 24)), [yyyy, '-', mm, '-', dd, 'T', HH, ':', nn, ':', ss, z]);
TheLogger.log("Debug", "$startTime");
String url = "$homeAssistantWebHost/api/history/period/$startTime?&filter_entity_id=$entityId&skip_initial_state";
TheLogger.log("Debug", "$url");
String url = "$homeAssistantWebHost/api/history/period/$startTime?&filter_entity_id=$entityId";
TheLogger.debug( "$url");
http.Response historyResponse;
if (_authType == "access_token") {
historyResponse = await http.get(url, headers: {
@ -395,12 +496,12 @@ class HomeAssistant {
"Content-Type": "application/json"
});
}
var _history = json.decode(historyResponse.body);
if (_history is Map) {
return null;
} else if (_history is List) {
TheLogger.log("Debug", "${_history[0].toString()}");
return _history;
var history = json.decode(historyResponse.body);
if (history is List) {
TheLogger.debug( "Got ${history.first.length} history recors");
return history;
} else {
return [];
}
}
}

View File

@ -51,7 +51,6 @@ class _LogViewPageState extends State<LogViewPage> {
),
body: TextField(
maxLines: null,
controller: TextEditingController(
text: _logData
),

View File

@ -13,11 +13,49 @@ import 'package:flutter/services.dart';
import 'package:date_format/date_format.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_colorpicker/material_picker.dart';
import 'package:charts_flutter/flutter.dart' as charts;
part 'entity_class/entity.class.dart';
part 'entity_class/stateless_widgets.dart';
part 'entity_class/stateful_widgets.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/other_entity.class.dart';
part 'entity_class/slider_entity.dart';
part 'entity_class/media_player_entity.class.dart';
part 'entity_widgets/badge.dart';
part 'entity_widgets/model_widgets.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_colors.class.dart';
part 'entity_widgets/entity_page_container.dart';
part 'entity_widgets/history_chart/entity_history.dart';
part 'entity_widgets/history_chart/simple_state_history_chart.dart';
part 'entity_widgets/history_chart/numeric_state_history_chart.dart';
part 'entity_widgets/history_chart/combined_history_chart.dart';
part 'entity_widgets/history_chart/history_control_widget.dart';
part 'entity_widgets/history_chart/entity_history_moment.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';
@ -25,19 +63,25 @@ part 'entity.page.dart';
part 'utils.class.dart';
part 'mdi.class.dart';
part 'entity_collection.class.dart';
part 'view_builder.class.dart';
part 'view_class.dart';
part 'card_class.dart';
part 'ui_class/ui.dart';
part 'ui_class/view.class.dart';
part 'ui_class/card.class.dart';
part 'ui_widgets/view.dart';
part 'ui_widgets/entities_card.dart';
part 'ui_widgets/unsupported_card.dart';
part 'ui_widgets/media_control_card.dart';
part 'ui_widgets/card_header_widget.dart';
EventBus eventBus = new EventBus();
const String appName = "HA Client";
const appVersion = "0.3.3";
const appVersion = "0.3.5";
String homeAssistantWebHost;
void main() {
FlutterError.onError = (errorDetails) {
TheLogger.log("Error", "${errorDetails.exception}");
TheLogger.error( "${errorDetails.exception}");
if (TheLogger.isInDebugMode) {
FlutterError.dumpErrorToConsole(errorDetails);
}
@ -46,7 +90,8 @@ void main() {
runZoned(() {
runApp(new HAClientApp());
}, onError: (error, stack) {
TheLogger.log("Global error", "$error");
TheLogger.error("$error");
TheLogger.error("$stack");
if (TheLogger.isInDebugMode) {
debugPrint("$stack");
}
@ -65,7 +110,7 @@ class HAClientApp extends StatelessWidget {
initialRoute: "/",
routes: {
"/": (context) => MainPage(title: 'HA Client'),
"/connection-settings": (context) => ConnectionSettingsPage(title: "Connection Settings"),
"/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"),
"/log-view": (context) => LogViewPage(title: "Log")
},
);
@ -88,7 +133,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
String _webSocketApiEndpoint;
String _password;
String _authType;
int _uiViewsCount = 0;
//int _uiViewsCount = 0;
String _instanceHost;
StreamSubscription _stateSubscription;
StreamSubscription _settingsSubscription;
@ -99,6 +144,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
int _isLoading = 1;
bool _settingsLoaded = false;
bool _accountMenuExpanded = false;
bool _useLovelaceUI;
@override
void initState() {
@ -109,7 +155,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_homeAssistant = HomeAssistant();
_settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) {
TheLogger.log("Debug","Settings change event: reconnect=${event.reconnect}");
TheLogger.debug("Settings change event: reconnect=${event.reconnect}");
if (event.reconnect) {
_homeAssistant.disconnect().then((_){
_initialLoad();
@ -133,7 +179,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
TheLogger.log("Debug","$state");
TheLogger.debug("$state");
if (state == AppLifecycleState.resumed && _settingsLoaded) {
_refreshData();
}
@ -148,6 +194,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
homeAssistantWebHost = "${prefs.getString('hassio-res-protocol')}://$domain:$port";
_password = prefs.getString('hassio-password');
_authType = prefs.getString('hassio-auth-type');
_useLovelaceUI = prefs.getBool('use-lovelace') ?? false;
if ((domain == null) || (port == null) || (_password == null) ||
(domain.length == 0) || (port.length == 0) || (_password.length == 0)) {
throw("Check connection settings");
@ -158,6 +205,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_subscribe() {
if (_stateSubscription == null) {
//TODO Move to homeAssistant or remove
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
setState(() {
if (event.localChange) {
@ -197,7 +245,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
}
_refreshData() async {
_homeAssistant.updateConnectionSettings(_webSocketApiEndpoint, _password, _authType);
_homeAssistant.updateSettings(_webSocketApiEndpoint, _password, _authType, _useLovelaceUI);
setState(() {
_hideErrorSnackBar();
_isLoading = 1;
@ -206,8 +254,8 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
setState(() {
//_instanceConfig = _homeAssistant.instanceConfig;
_entities = _homeAssistant.entities;
_uiViewsCount = _homeAssistant.viewsCount;
TheLogger.log("Debug","_uiViewsCount=$_uiViewsCount");
//_uiViewsCount = _homeAssistant.viewsCount;
//TheLogger.debug("_uiViewsCount=$_uiViewsCount");
_isLoading = 0;
});
}).catchError((e) {
@ -220,11 +268,20 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
setState(() {
_isLoading = 2;
});
if (e is Error) {
TheLogger.error(e.toString());
TheLogger.error("${e.stackTrace}");
_showErrorSnackBar(
message: "There was some error",
errorCode: 13
);
} else {
_showErrorSnackBar(
message: e != null ? e["errorMessage"] ?? "$e" : "Unknown error",
errorCode: e["errorCode"] != null ? e["errorCode"] : 99
);
}
}
void _callService(String domain, String service, String entityId, Map<String, dynamic> additionalParams) {
_homeAssistant.callService(domain, service, entityId, additionalParams).catchError((e) => _setErrorState(e));
@ -241,30 +298,36 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
List<Tab> buildUIViewTabs() {
List<Tab> result = [];
if (!_entities.isEmpty) {
if (!_entities.hasDefaultView) {
if (_homeAssistant.ui.views.isNotEmpty) {
_homeAssistant.ui.views.forEach((HAView view) {
if (view.linkedEntity == null) {
result.add(
Tab(
icon:
Icon(
MaterialDesignIcons.createIconDataFromIconName("mdi:home-assistant"),
MaterialDesignIcons.createIconDataFromIconName(
view.iconName ?? "mdi:home-assistant"),
size: 24.0,
)
)
);
}
_entities.views.forEach((viewId, groupEntity) {
} else {
result.add(
Tab(
icon: MaterialDesignIcons.createIconWidgetFromEntityData(groupEntity, 24.0, null) ??
icon: MaterialDesignIcons.createIconWidgetFromEntityData(
view.linkedEntity, 24.0, null) ??
Icon(
MaterialDesignIcons.createIconDataFromIconName("mdi:home-assistant"),
MaterialDesignIcons.createIconDataFromIconName(
"mdi:home-assistant"),
size: 24.0,
)
)
);
}
});
}
return result;
}
@ -318,7 +381,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
menuItems.addAll([
ListTile(
leading: Icon(Icons.settings),
title: Text("Connection settings"),
title: Text("Settings"),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).pushNamed('/connection-settings');
@ -424,6 +487,17 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
);
break;
}
default: {
action = SnackBarAction(
label: "Reload",
onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar();
_refreshData();
},
);
break;
}
}
_scaffoldKey.currentState.hideCurrentSnackBar();
_scaffoldKey.currentState.showSnackBar(
@ -451,6 +525,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
});
},
),
primary: true,
bottom: empty ? null : TabBar(
tabs: buildUIViewTabs(),
isScrollable: true,
@ -471,18 +546,18 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
),
)
:
_homeAssistant.buildViews(context)
_homeAssistant.buildViews(context, _useLovelaceUI)
);
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called.
if (_entities == null) {
if (_homeAssistant.ui == null || _homeAssistant.ui.views == null) {
return _buildScaffold(true);
} else {
return DefaultTabController(
length: _uiViewsCount,
length: _homeAssistant.ui.views.length,
child: _buildScaffold(false)
);
}

View File

@ -20,6 +20,8 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
String _newSocketProtocol = "wss";
String _authType = "access_token";
String _newAuthType = "access_token";
bool _useLovelace = false;
bool _newUseLovelace = false;
bool _edited = false;
FocusNode _domainFocusNode;
FocusNode _portFocusNode;
@ -46,6 +48,11 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
_hassioPassword = _newHassioPassword = prefs.getString("hassio-password") ?? "";
_socketProtocol = _newSocketProtocol = prefs.getString("hassio-protocol") ?? 'wss';
_authType = _newAuthType = prefs.getString("hassio-auth-type") ?? 'access_token';
try {
_useLovelace = _newUseLovelace = prefs.getBool("use-lovelace") ?? false;
} catch (e) {
_useLovelace = _newUseLovelace = false;
}
});
}
@ -55,7 +62,8 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
(_newHassioPort != _hassioPort) ||
(_newHassioDomain != _hassioDomain) ||
(_newSocketProtocol != _socketProtocol) ||
(_newAuthType != _authType));
(_newAuthType != _authType) ||
(_newUseLovelace != _useLovelace));
});
}
@ -70,6 +78,7 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
prefs.setString("hassio-protocol", _newSocketProtocol);
prefs.setString("hassio-res-protocol", _newSocketProtocol == "wss" ? "https" : "http");
prefs.setString("hassio-auth-type", _newAuthType);
prefs.setBool("use-lovelace", _newUseLovelace);
}
@override
@ -95,6 +104,13 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
body: ListView(
padding: const EdgeInsets.all(20.0),
children: <Widget>[
Text(
"Connection settings",
style: TextStyle(
color: Colors.black45,
fontSize: 20.0
),
),
new Row(
children: [
Text("Use ssl (HTTPS)"),
@ -172,9 +188,31 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
},
focusNode: _passwordFocusNode,
onEditingComplete: _checkConfigChanged,
),
Padding(
padding: EdgeInsets.only(top: 20.0),
child: Text(
"UI",
style: TextStyle(
color: Colors.black45,
fontSize: 20.0
),
),
),
new Row(
children: [
Text("Use Lovelace UI"),
Switch(
value: _newUseLovelace,
onChanged: (value) {
_newUseLovelace = value;
_checkConfigChanged();
},
)
],
),
],
),
);
}

View File

@ -0,0 +1,60 @@
part of '../main.dart';
class HACard {
List<Entity> entities = [];
Entity linkedEntity;
String name;
String id;
String type;
HACard({
this.name,
this.id,
this.linkedEntity,
@required this.type
});
Widget build(BuildContext context) {
switch (type) {
case "entities": {
return EntitiesCardWidget(
card: this,
);
}
case "weather-forecast":
case "thermostat":
case "sensor":
case "plant-status":
case "picture-entity":
case "picture-elements":
case "picture":
case "map":
case "iframe":
case "gauge":
case "entity-button":
case "conditional":
case "alarm-panel":
case "media-control": {
return UnsupportedCardWidget(
card: this,
);
}
default: {
if ((linkedEntity == null) && (entities.isNotEmpty)) {
return EntitiesCardWidget(
card: this,
);
} else {
return UnsupportedCardWidget(
card: this,
);
}
}
}
}
}

26
lib/ui_class/ui.dart Normal file
View File

@ -0,0 +1,26 @@
part of '../main.dart';
class HomeAssistantUI {
List<HAView> views;
HomeAssistantUI() {
views = [];
}
Widget build(BuildContext context) {
return TabBarView(
children: _buildViews(context)
);
}
List<Widget> _buildViews(BuildContext context) {
List<Widget> result = [];
views.forEach((view) {
result.add(
view.build(context)
);
});
return result;
}
}

View File

@ -0,0 +1,66 @@
part of '../main.dart';
class HAView {
List<HACard> cards = [];
List<Entity> badges = [];
Entity linkedEntity;
String name;
String id;
String iconName;
int count;
HAView({
this.name,
this.id,
this.count,
this.iconName,
List<Entity> childEntities
}) {
if (childEntities != null) {
_fillView(childEntities);
}
}
void _fillView(List<Entity> childEntities) {
List<HACard> autoGeneratedCards = [];
childEntities.forEach((entity) {
if (entity.isBadge) {
badges.add(entity);
TheLogger.debug("----Badge: ${entity.entityId}");
} else {
if (!entity.isGroup) {
String groupIdToAdd = "${entity.domain}.${entity.domain}$count";
if (autoGeneratedCards.every((HACard card) => card.id != groupIdToAdd )) {
HACard card = HACard(
id: groupIdToAdd,
name: entity.domain,
type: "entities"
);
TheLogger.debug("----Creating card: $groupIdToAdd");
card.entities.add(entity);
autoGeneratedCards.add(card);
} else {
autoGeneratedCards.firstWhere((card) => card.id == groupIdToAdd).entities.add(entity);
}
} else {
TheLogger.debug("----Card: ${entity.entityId}");
HACard card = HACard(
name: entity.displayName,
id: entity.entityId,
linkedEntity: entity,
type: "entities"
);
card.entities.addAll(entity.childEntities);
cards.add(card);
}
}
});
cards.addAll(autoGeneratedCards);
}
Widget build(BuildContext context) {
return ViewWidget(
view: this,
);
}
}

View File

@ -0,0 +1,25 @@
part of '../main.dart';
class CardHeaderWidget extends StatelessWidget {
final String name;
const CardHeaderWidget({Key key, this.name}) : super(key: key);
@override
Widget build(BuildContext context) {
var result;
if ((name != null) && (name.trim().length > 0)) {
result = new ListTile(
title: Text("$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;
}
}

View File

@ -0,0 +1,39 @@
part of '../main.dart';
class EntitiesCardWidget extends StatelessWidget {
final HACard card;
const EntitiesCardWidget({
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<Widget> body = [];
body.add(CardHeaderWidget(name: card.name));
body.addAll(_buildCardBody(context));
return Card(
child: new Column(mainAxisSize: MainAxisSize.min, children: body)
);
}
List<Widget> _buildCardBody(BuildContext context) {
List<Widget> 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;
}
}

View File

@ -0,0 +1,27 @@
part of '../main.dart';
class MediaControlCardWidget extends StatelessWidget {
final HACard card;
const MediaControlCardWidget({
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<Widget> body = [];
return Card(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: body
)
);
}
}

View File

@ -0,0 +1,49 @@
part of '../main.dart';
class UnsupportedCardWidget extends StatelessWidget {
final HACard card;
const UnsupportedCardWidget({
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<Widget> body = [];
body.add(CardHeaderWidget(name: card.name ?? ""));
body.addAll(_buildCardBody(context));
return Card(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: body
)
);
}
List<Widget> _buildCardBody(BuildContext context) {
List<Widget> result = [];
if (card.linkedEntity != null) {
result.addAll(<Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0.0, Entity.rowPadding, 0.0, Entity.rowPadding),
child: card.linkedEntity.buildDefaultWidget(context),
)
]);
} else {
result.addAll(<Widget>[
Padding(
padding: EdgeInsets.fromLTRB(Entity.leftWidgetPadding, Entity.rowPadding, Entity.rightWidgetPadding, Entity.rowPadding),
child: Text("'${card.type}' card is not supported yet"),
),
]);
}
return result;
}
}

95
lib/ui_widgets/view.dart Normal file
View File

@ -0,0 +1,95 @@
part of '../main.dart';
class ViewWidget extends StatefulWidget {
final HAView view;
const ViewWidget({
Key key,
this.view
}) : super(key: key);
@override
State<StatefulWidget> createState() {
return ViewWidgetState();
}
}
class ViewWidgetState extends State<ViewWidget> {
StreamSubscription _refreshDataSubscription;
Completer _refreshCompleter;
@override
void initState() {
super.initState();
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) {
_refreshCompleter.complete();
}
});
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
color: Colors.amber,
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: _buildChildren(context),
),
onRefresh: () => _refreshData(),
);
}
List<Widget> _buildChildren(BuildContext context) {
List<Widget> result = [];
if (widget.view.badges.isNotEmpty) {
result.insert(0,
Wrap(
alignment: WrapAlignment.center,
spacing: 10.0,
runSpacing: 1.0,
children: _buildBadges(context),
)
);
}
widget.view.cards.forEach((HACard card){
result.add(
card.build(context)
);
});
return result;
}
List<Widget> _buildBadges(BuildContext context) {
List<Widget> result = [];
widget.view.badges.forEach((Entity entity) {
if (!entity.isHidden) {
result.add(entity.buildBadgeWidget(context));
}
});
return result;
}
Future _refreshData() {
if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) {
TheLogger.debug("Previous data refresh is still in progress");
} else {
_refreshCompleter = Completer();
eventBus.fire(RefreshDataEvent());
}
return _refreshCompleter.future;
}
@override
void dispose() {
_refreshDataSubscription.cancel();
super.dispose();
}
}

View File

@ -20,12 +20,25 @@ class TheLogger {
return inDebugMode;
}
static void log(String level, String message) {
static void error(String message) {
_writeToLog("Error", message);
}
static void warning(String message) {
_writeToLog("Warning", message);
}
static void debug(String message) {
_writeToLog("Debug", message);
}
static void _writeToLog(String level, String message) {
if (isInDebugMode) {
debugPrint('$message');
}
_log.add("[$level] : $message");
if (_log.length > 50) {
DateTime t = DateTime.now();
_log.add("${formatDate(t, ["mm","dd"," ","HH",":","nn",":","ss"])} [$level] : $message");
if (_log.length > 100) {
_log.removeAt(0);
}
}
@ -37,7 +50,7 @@ class HAUtils {
if (await canLaunch(url)) {
await launch(url);
} else {
TheLogger.log("Error", "Could not launch $url");
TheLogger.error( "Could not launch $url");
}
}
}

View File

@ -1,88 +0,0 @@
part of 'main.dart';
class ViewBuilder{
EntityCollection entityCollection;
List<View> _views;
ViewBuilder({
Key key,
this.entityCollection
}) {
_compose();
}
Widget buildWidget(BuildContext context) {
return ViewBuilderWidget(
entities: _views
);
}
void _compose() {
TheLogger.log("Debug", "Rebuilding all UI...");
_views = [];
if (!entityCollection.hasDefaultView) {
_views.add(_composeDefaultView());
}
_views.addAll(_composeViews());
}
View _composeDefaultView() {
Map<String, List<String>> userGroupsList = entityCollection.getDefaultViewTopLevelEntities();
List<Entity> entitiesForView = [];
userGroupsList["userGroups"].forEach((groupId){
Entity en = entityCollection.get(groupId);
entitiesForView.add(en);
});
userGroupsList["notGroupedEntities"].forEach((entityId){
entitiesForView.add(entityCollection.get(entityId));
});
return View(
entities: entitiesForView,
count: 0
);
}
List<View> _composeViews() {
List<View> result = [];
int counter = 0;
entityCollection.views.forEach((viewId, viewGroupEntity) {
counter += 1;
//try {
result.add(View(
count: counter,
entities: viewGroupEntity.childEntities
));
/*} catch (error) {
TheLogger.log("Error","Error parsing view: $viewId");
}*/
});
return result;
}
}
class ViewBuilderWidget extends StatelessWidget {
final List<View> entities;
const ViewBuilderWidget({
Key key,
this.entities
}) : super(key: key);
@override
Widget build(BuildContext context) {
return TabBarView(
children: _buildChildren(context)
);
}
List<Widget> _buildChildren(BuildContext context) {
List<Widget> result = [];
entities.forEach((View view){
result.add(view.buildWidget(context));
});
return result;
}
}

View File

@ -1,168 +0,0 @@
part of 'main.dart';
class View {
List<Entity> childEntitiesAsBadges;
Map<String, CardSkeleton> childEntitiesAsCards;
int count;
List<Entity> entities;
View({
Key key,
this.count,
this.entities
}) {
childEntitiesAsBadges = [];
childEntitiesAsCards = {};
_filterEntities();
}
Widget buildWidget(BuildContext context) {
return ViewWidget(
badges: childEntitiesAsBadges,
cards: childEntitiesAsCards,
);
}
void _filterEntities() {
entities.forEach((Entity entity){
if (!entity.isGroup) {
if (entity.isBadge) {
childEntitiesAsBadges.add(entity);
} else {
String groupIdToAdd = "${entity.domain}.${entity.domain}$count";
if (childEntitiesAsCards[groupIdToAdd] == null) {
childEntitiesAsCards[groupIdToAdd] = CardSkeleton(
displayName: entity.domain,
);
}
childEntitiesAsCards[groupIdToAdd].childEntities.add(entity);
}
} else {
childEntitiesAsCards[entity.entityId] = CardSkeleton(
displayName: entity.displayName,
groupEntity: entity
);
childEntitiesAsCards[entity.entityId].childEntities = entity.childEntities;
}
});
}
}
class ViewWidget extends StatefulWidget {
final List<Entity> badges;
final Map<String, CardSkeleton> cards;
final String displayName;
const ViewWidget({
Key key,
this.badges,
this.cards,
this.displayName
}) : super(key: key);
@override
State<StatefulWidget> createState() {
return ViewWidgetState();
}
}
class ViewWidgetState extends State<ViewWidget> {
StreamSubscription _refreshDataSubscription;
Completer _refreshCompleter;
@override
void initState() {
super.initState();
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) {
_refreshCompleter.complete();
}
});
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
color: Colors.amber,
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: _buildChildren(context),
),
onRefresh: () => _refreshData(),
);
}
List<Widget> _buildChildren(BuildContext context) {
List<Widget> result = [];
if (widget.badges.isNotEmpty) {
result.insert(0,
Wrap(
alignment: WrapAlignment.center,
spacing: 10.0,
runSpacing: 1.0,
children: _buildBadges(context, widget.badges),
)
);
}
widget.cards.forEach((String id, CardSkeleton skeleton){
result.add(
EntityModel(
entity: skeleton.groupEntity,
handleTap: false,
child: CardWidget(
entities: skeleton.childEntities,
friendlyName: skeleton.displayName,
)
)
);
});
return result;
}
List<Widget> _buildBadges(BuildContext context, List<Entity> badges) {
List<Widget> result = [];
badges.forEach((Entity entity) {
result.add(entity.buildBadgeWidget(context));
});
return result;
}
Future _refreshData() {
if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) {
TheLogger.log("Debug","Previous data refresh is still in progress");
} else {
_refreshCompleter = Completer();
eventBus.fire(RefreshDataEvent());
}
return _refreshCompleter.future;
}
@override
void dispose() {
_refreshDataSubscription.cancel();
super.dispose();
}
}
class CardSkeleton {
String displayName;
List<Entity> childEntities;
Entity groupEntity;
CardSkeleton({
Key key,
this.displayName,
this.childEntities,
this.groupEntity}) {
childEntities = [];
}
}

View File

@ -50,6 +50,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
charts_common:
dependency: transitive
description:
name: charts_common
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0"
charts_flutter:
dependency: "direct main"
description:
name: charts_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0"
collection:
dependency: transitive
description:
@ -181,6 +195,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
intl:
dependency: transitive
description:
name: intl
url: "https://pub.dartlang.org"
source: hosted
version: "0.15.7"
io:
dependency: transitive
description:

View File

@ -1,7 +1,7 @@
name: hass_client
description: Home Assistant Android Client
version: 0.3.3+43
version: 0.3.5+55
environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
@ -16,6 +16,7 @@ dependencies:
url_launcher: ^3.0.3
date_format: ^1.0.5
flutter_colorpicker: ^0.1.0
charts_flutter: ^0.4.0
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.