Compare commits

...

28 Commits

Author SHA1 Message Date
d3c1422b9e Version 0.3.13 2018-12-15 14:37:55 +02:00
b6ac9f985f Entity state by device class 2018-12-15 14:37:00 +02:00
a59de4b6dc Resolves #255 Refresh UI for newly appeared entity 2018-12-15 14:09:37 +02:00
f507d5df0c Resolves #242 2018-12-14 19:37:49 +02:00
f77e46de37 Version 0.3.12 2018-12-14 17:04:52 +02:00
cda17b1217 Resolves #232 2018-12-14 17:03:18 +02:00
be560769ef Resolves #243 2018-12-14 16:57:11 +02:00
3815800e32 Resolves #253 removing trash characters from state string 2018-12-14 16:48:35 +02:00
a3226311a2 Fix default tap actions 2018-12-14 16:31:41 +02:00
79669243c2 Remove some logging 2018-12-14 16:01:13 +02:00
fdc81f6ea4 Resolves #237 2018-12-14 15:59:47 +02:00
7fe44459e7 Resolves #252, Resolves #249 2018-12-14 14:28:23 +02:00
a8500d44e1 Version 0.3.11 2018-12-13 23:42:34 +02:00
b4d4c5abec Relates to #248 old format support 2018-12-13 23:40:20 +02:00
c19a3f272a Resolves #248 tap_action parsing fix 2018-12-13 23:37:54 +02:00
b264534858 Add files via upload 2018-12-12 10:43:26 +02:00
ab53f77f9e Add files via upload 2018-12-12 10:40:50 +02:00
c73956720c Create empty 2018-12-12 10:39:42 +02:00
051041e794 build number 2018-12-07 23:00:20 +02:00
5c83be9fee Resolves #207 Entity filter card support 2018-12-07 22:04:14 +02:00
4bece42693 build number 2018-11-29 21:46:47 +02:00
4ae107fe4c Resolves #230 Vertical stack card 2018-11-29 21:45:46 +02:00
9523ed2562 Build number 2018-11-25 20:45:22 +02:00
9c403480e2 Resolves #120 Horizontal Stack Cards 2018-11-25 20:44:19 +02:00
20b1b90e39 Resolves #206 Entity button with tap and hold events 2018-11-25 18:09:06 +02:00
5633e30448 WIP #206 Entity button card 2018-11-25 17:33:33 +02:00
4492fb9f0c Build number 2018-11-24 17:30:02 +02:00
36410752e4 Fix app version 2018-11-24 17:29:33 +02:00
36 changed files with 634 additions and 432 deletions

1
docs/empty Normal file
View File

@ -0,0 +1 @@

BIN
docs/ha_access_tokens.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/ha_profile-300x247.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/settings-869x1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@ -19,7 +19,7 @@ class _EntityViewPageState extends State<EntityViewPage> {
void initState() { void initState() {
super.initState(); super.initState();
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) { _stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
TheLogger.debug("State change event handled by entity page: ${event.entityId}"); Logger.d("State change event handled by entity page: ${event.entityId}");
if (event.entityId == widget.entityId) { if (event.entityId == widget.entityId) {
setState(() {}); setState(() {});
} }

View File

@ -30,14 +30,54 @@ class EntityState {
static const problem = 'problem'; static const problem = 'problem';
} }
class EntityTapAction { class EntityUIAction {
static const moreInfo = 'more-info'; static const moreInfo = 'more-info';
static const toggle = 'toggle'; static const toggle = 'toggle';
static const callService = 'call-service'; static const callService = 'call-service';
static const navigate = 'navigate';
static const none = 'none'; static const none = 'none';
String tapAction = EntityUIAction.moreInfo;
String tapNavigationPath;
String tapService;
Map<String, dynamic> tapServiceData;
String holdAction = EntityUIAction.none;
String holdNavigationPath;
String holdService;
Map<String, dynamic> holdServiceData;
EntityUIAction({rawEntityData}) {
if (rawEntityData != null) {
if (rawEntityData["tap_action"] != null) {
if (rawEntityData["tap_action"] is String) {
tapAction = rawEntityData["tap_action"];
} else {
tapAction =
rawEntityData["tap_action"]["action"] ?? EntityUIAction.moreInfo;
tapNavigationPath = rawEntityData["tap_action"]["navigation_path"];
tapService = rawEntityData["tap_action"]["service"];
tapServiceData = rawEntityData["tap_action"]["service_data"];
}
}
if (rawEntityData["hold_action"] != null) {
if (rawEntityData["hold_action"] is String) {
holdAction = rawEntityData["hold_action"];
} else {
holdAction =
rawEntityData["hold_action"]["action"] ?? EntityUIAction.none;
holdNavigationPath = rawEntityData["hold_action"]["navigation_path"];
holdService = rawEntityData["hold_action"]["service"];
holdServiceData = rawEntityData["hold_action"]["service_data"];
}
}
}
}
} }
class CardType { class CardType {
static const horizontalStack = "horizontal-stack";
static const verticalStack = "vertical-stack";
static const entities = "entities"; static const entities = "entities";
static const glance = "glance"; static const glance = "glance";
static const mediaControl = "media-control"; static const mediaControl = "media-control";

View File

@ -12,14 +12,65 @@ class Entity {
"sensor" "sensor"
]; ];
static Map StateByDeviceClass = {
"battery.on": "Low",
"battery.off": "Normal",
"cold.on": "Cold",
"cold.off": "Normal",
"connectivity.on": "Connected",
"connectivity.off": "Diconnected",
"door.on": "Open",
"door.off": "Closed",
"garage_door.on": "Open",
"garage_door.off": "Closed",
"gas.on": "Detected",
"gas.off": "Clear",
"heat.on": "Hot",
"heat.off": "Normal",
"light.on": "Detected",
"lignt.off": "No light",
"lock.on": "Unlocked",
"lock.off": "Locked",
"moisture.on": "Wet",
"moisture.off": "Dry",
"motion.on": "Detected",
"motion.off": "Cler",
"moving.on": "Moving",
"moving.off": "Stopped",
"occupancy.on": "Occupied",
"occupancy.off": "Clear",
"opening.on": "Open",
"opening.off": "Closed",
"plug.on": "Plugged in",
"plug.off": "Unplugged",
"power.on": "Powered",
"power.off": "No power",
"presence.on": "Home",
"presence.off": "Away",
"problem.on": "Problem",
"problem.off": "OK",
"safety.on": "Unsafe",
"safety.off": "Safe",
"smoke.on": "Detected",
"smoke.off": "Clear",
"sound.on": "Detected",
"sound.off": "Clear",
"vibration.on": "Detected",
"vibration.off": "Clear",
"window.on": "Open",
"window.off": "Closed"
};
Map attributes; Map attributes;
String domain; String domain;
String entityId; String entityId;
String state; String state;
String displayState;
DateTime _lastUpdated; DateTime _lastUpdated;
List<Entity> childEntities = []; List<Entity> childEntities = [];
List<String> attributesToShow = ["all"]; List<String> attributesToShow = ["all"];
String deviceClass;
EntityHistoryConfig historyConfig = EntityHistoryConfig( EntityHistoryConfig historyConfig = EntityHistoryConfig(
chartType: EntityHistoryWidgetType.simple chartType: EntityHistoryWidgetType.simple
); );
@ -27,7 +78,6 @@ class Entity {
String get displayName => String get displayName =>
attributes["friendly_name"] ?? (attributes["name"] ?? entityId.split(".")[1].replaceAll("_", " ")); attributes["friendly_name"] ?? (attributes["name"] ?? entityId.split(".")[1].replaceAll("_", " "));
String get deviceClass => attributes["device_class"] ?? null;
bool get isView => bool get isView =>
(domain == "group") && (domain == "group") &&
(attributes != null ? attributes["view"] ?? false : false); (attributes != null ? attributes["view"] ?? false : false);
@ -50,7 +100,9 @@ class Entity {
attributes = rawData["attributes"] ?? {}; attributes = rawData["attributes"] ?? {};
domain = rawData["entity_id"].split(".")[0]; domain = rawData["entity_id"].split(".")[0];
entityId = rawData["entity_id"]; entityId = rawData["entity_id"];
deviceClass = attributes["device_class"];
state = rawData["state"]; state = rawData["state"];
displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? state;
_lastUpdated = DateTime.tryParse(rawData["last_updated"]); _lastUpdated = DateTime.tryParse(rawData["last_updated"]);
} }
@ -91,13 +143,6 @@ class Entity {
); );
} }
Widget buildGlanceWidget(BuildContext context, bool showName, bool showState) {
return GlanceEntityContainer(
showName: showName,
showState: showState,
);
}
Widget _buildStatePart(BuildContext context) { Widget _buildStatePart(BuildContext context) {
return SimpleEntityState(); return SimpleEntityState();
} }

View File

@ -4,12 +4,7 @@ class EntityWrapper {
String displayName; String displayName;
String icon; String icon;
String tapAction; EntityUIAction uiAction;
String holdAction;
String tapActionService;
Map<String, dynamic> tapActionServiceData;
String holdActionService;
Map<String, dynamic> holdActionServiceData;
Entity entity; Entity entity;
@ -17,58 +12,68 @@ class EntityWrapper {
this.entity, this.entity,
String icon, String icon,
String displayName, String displayName,
this.tapAction: EntityTapAction.moreInfo, this.uiAction
this.holdAction: EntityTapAction.none,
this.tapActionService,
this.tapActionServiceData,
this.holdActionService,
this.holdActionServiceData
}) { }) {
this.icon = icon ?? entity.icon; this.icon = icon ?? entity.icon;
this.displayName = displayName ?? entity.displayName; this.displayName = displayName ?? entity.displayName;
if (this.uiAction == null) {
this.uiAction = EntityUIAction();
}
} }
void handleTap() { void handleTap() {
switch (tapAction) { switch (uiAction.tapAction) {
case EntityTapAction.toggle: { case EntityUIAction.toggle: {
eventBus.fire( eventBus.fire(
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null)); ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
break; break;
} }
case EntityTapAction.callService: { case EntityUIAction.callService: {
eventBus.fire( if (uiAction.tapService != null) {
ServiceCallEvent(tapActionService.split(".")[0], tapActionService.split(".")[1], null, tapActionServiceData)); eventBus.fire(
ServiceCallEvent(uiAction.tapService.split(".")[0],
uiAction.tapService.split(".")[1], null,
uiAction.tapServiceData));
}
break; break;
} }
case EntityTapAction.none: { case EntityUIAction.none: {
break;
}
case EntityUIAction.moreInfo: {
eventBus.fire(
new ShowEntityPageEvent(entity));
break; break;
} }
default: { default: {
eventBus.fire(
new ShowEntityPageEvent(entity));
break; break;
} }
} }
} }
void handleHold() { void handleHold() {
switch (holdAction) { switch (uiAction.holdAction) {
case EntityTapAction.toggle: { case EntityUIAction.toggle: {
eventBus.fire( eventBus.fire(
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null)); ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
break; break;
} }
case EntityTapAction.callService: { case EntityUIAction.callService: {
eventBus.fire( if (uiAction.holdService != null) {
ServiceCallEvent(tapActionService.split(".")[0], tapActionService.split(".")[1], null, tapActionServiceData)); eventBus.fire(
ServiceCallEvent(uiAction.holdService.split(".")[0],
uiAction.holdService.split(".")[1], null,
uiAction.holdServiceData));
}
break; break;
} }
case EntityTapAction.moreInfo: { case EntityUIAction.moreInfo: {
eventBus.fire( eventBus.fire(
new ShowEntityPageEvent(entity)); new ShowEntityPageEvent(entity));
break; break;

View File

@ -19,7 +19,7 @@ class EntityCollection {
_allEntities.clear(); _allEntities.clear();
//views.clear(); //views.clear();
TheLogger.debug("Parsing ${rawData.length} Home Assistant entities"); Logger.d("Parsing ${rawData.length} Home Assistant entities");
rawData.forEach((rawEntityData) { rawData.forEach((rawEntityData) {
addFromRaw(rawEntityData); addFromRaw(rawEntityData);
}); });
@ -89,11 +89,13 @@ class EntityCollection {
} }
} }
void updateState(Map rawStateData) { bool updateState(Map rawStateData) {
if (isExist(rawStateData["entity_id"])) { if (isExist(rawStateData["entity_id"])) {
updateFromRaw(rawStateData["new_state"] ?? rawStateData["old_state"]); updateFromRaw(rawStateData["new_state"] ?? rawStateData["old_state"]);
return false;
} else { } else {
addFromRaw(rawStateData["new_state"] ?? rawStateData["old_state"]); addFromRaw(rawStateData["new_state"] ?? rawStateData["old_state"]);
return true;
} }
} }
@ -101,10 +103,9 @@ class EntityCollection {
_allEntities[entity.entityId] = entity; _allEntities[entity.entityId] = entity;
} }
Entity addFromRaw(Map rawEntityData) { void addFromRaw(Map rawEntityData) {
Entity entity = _createEntityInstance(rawEntityData); Entity entity = _createEntityInstance(rawEntityData);
_allEntities[entity.entityId] = entity; _allEntities[entity.entityId] = entity;
return entity;
} }
void updateFromRaw(Map rawEntityData) { void updateFromRaw(Map rawEntityData) {

View File

@ -0,0 +1,45 @@
part of '../main.dart';
class ButtonEntityContainer extends StatelessWidget {
ButtonEntityContainer({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
return InkWell(
onTap: () => entityWrapper.handleTap(),
onLongPress: () => entityWrapper.handleHold(),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FractionallySizedBox(
widthFactor: 0.4,
child: FittedBox(
fit: BoxFit.fitHeight,
child: EntityIcon(
padding: EdgeInsets.fromLTRB(2.0, 6.0, 2.0, 2.0),
iconSize: Sizes.iconSize,
)
),
),
_buildName()
],
),
);
}
Widget _buildName() {
return EntityName(
padding: EdgeInsets.fromLTRB(Sizes.buttonPadding, 0.0, Sizes.buttonPadding, Sizes.rowPadding),
textOverflow: TextOverflow.ellipsis,
maxLines: 3,
wordsWrap: true,
textAlign: TextAlign.center,
fontSize: Sizes.nameFontSize,
);
}
}

View File

@ -52,7 +52,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
setState(() { setState(() {
_tmpColor = color; _tmpColor = color;
_changedHere = true; _changedHere = true;
TheLogger.debug( "Color: [${color.red}, ${color.green}, ${color.blue}]"); Logger.d( "Color: [${color.red}, ${color.green}, ${color.blue}]");
if ((color == Colors.black) || ((color.red == color.green) && (color.green == color.blue))) { if ((color == Colors.black) || ((color.red == color.green) && (color.green == color.blue))) {
eventBus.fire(new ServiceCallEvent( eventBus.fire(new ServiceCallEvent(
entity.domain, "turn_off", entity.entityId, entity.domain, "turn_off", entity.entityId,

View File

@ -120,12 +120,12 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
void _setPower(MediaPlayerEntity entity) { void _setPower(MediaPlayerEntity entity) {
if (entity.state != EntityState.unavailable && entity.state != EntityState.unknown) { if (entity.state != EntityState.unavailable && entity.state != EntityState.unknown) {
if (entity.state == EntityState.off) { if (entity.state == EntityState.off) {
TheLogger.debug("${entity.entityId} turn_on"); Logger.d("${entity.entityId} turn_on");
eventBus.fire(new ServiceCallEvent( eventBus.fire(new ServiceCallEvent(
entity.domain, "turn_on", entity.entityId, entity.domain, "turn_on", entity.entityId,
null)); null));
} else { } else {
TheLogger.debug("${entity.entityId} turn_off"); Logger.d("${entity.entityId} turn_off");
eventBus.fire(new ServiceCallEvent( eventBus.fire(new ServiceCallEvent(
entity.domain, "turn_off", entity.entityId, entity.domain, "turn_off", entity.entityId,
null)); null));
@ -134,7 +134,7 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
} }
void _callAction(MediaPlayerEntity entity, String action) { void _callAction(MediaPlayerEntity entity, String action) {
TheLogger.debug("${entity.entityId} $action"); Logger.d("${entity.entityId} $action");
eventBus.fire(new ServiceCallEvent( eventBus.fire(new ServiceCallEvent(
entity.domain, "$action", entity.entityId, entity.domain, "$action", entity.entityId,
null)); null));

View File

@ -7,8 +7,9 @@ class EntityName extends StatelessWidget {
final bool wordsWrap; final bool wordsWrap;
final double fontSize; final double fontSize;
final TextAlign textAlign; final TextAlign textAlign;
final int maxLines;
const EntityName({Key key, this.padding: const EdgeInsets.only(right: 10.0), this.textOverflow: TextOverflow.ellipsis, this.wordsWrap: true, this.fontSize: Sizes.nameFontSize, this.textAlign: TextAlign.left}) : super(key: key); const EntityName({Key key, this.maxLines, this.padding: const EdgeInsets.only(right: 10.0), this.textOverflow: TextOverflow.ellipsis, this.wordsWrap: true, this.fontSize: Sizes.nameFontSize, this.textAlign: TextAlign.left}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -19,6 +20,7 @@ class EntityName extends StatelessWidget {
"${entityWrapper.displayName}", "${entityWrapper.displayName}",
overflow: textOverflow, overflow: textOverflow,
softWrap: wordsWrap, softWrap: wordsWrap,
maxLines: maxLines,
style: TextStyle(fontSize: fontSize), style: TextStyle(fontSize: fontSize),
textAlign: textAlign, textAlign: textAlign,
), ),

View File

@ -4,41 +4,52 @@ class GlanceEntityContainer extends StatelessWidget {
final bool showName; final bool showName;
final bool showState; final bool showState;
final bool nameInTheBottom;
final double iconSize;
final double nameFontSize;
final bool wordsWrapInName;
GlanceEntityContainer({ GlanceEntityContainer({
Key key, @required this.showName, @required this.showState, Key key,
@required this.showName,
@required this.showState,
this.nameInTheBottom: false,
this.iconSize: Sizes.iconSize,
this.nameFontSize: Sizes.smallFontSize,
this.wordsWrapInName: false
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper; final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
List<Widget> result = []; List<Widget> result = [];
if (showName) { if (!nameInTheBottom) {
result.add(EntityName( if (showName) {
padding: EdgeInsets.only(bottom: Sizes.rowPadding), result.add(_buildName());
textOverflow: TextOverflow.ellipsis, }
wordsWrap: false, } else {
textAlign: TextAlign.center, if (showState) {
fontSize: Sizes.smallFontSize, result.add(_buildState());
)); }
} }
result.add( result.add(
EntityIcon( EntityIcon(
padding: EdgeInsets.all(0.0), padding: EdgeInsets.all(0.0),
iconSize: Sizes.iconSize, iconSize: iconSize,
) )
); );
if (showState) { if (!nameInTheBottom) {
result.add(SimpleEntityState( if (showState) {
textAlign: TextAlign.center, result.add(_buildState());
expanded: false, }
padding: EdgeInsets.only(top: Sizes.rowPadding), } else {
)); result.add(_buildName());
} }
return Center( return Center(
child: InkResponse( child: InkResponse(
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints(minWidth: Sizes.iconSize*2), constraints: BoxConstraints(minWidth: Sizes.iconSize * 2),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
//mainAxisAlignment: MainAxisAlignment.start, //mainAxisAlignment: MainAxisAlignment.start,
@ -51,4 +62,23 @@ class GlanceEntityContainer extends StatelessWidget {
), ),
); );
} }
Widget _buildName() {
return EntityName(
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
textOverflow: TextOverflow.ellipsis,
wordsWrap: wordsWrapInName,
textAlign: TextAlign.center,
fontSize: nameFontSize,
);
}
Widget _buildState() {
return SimpleEntityState(
textAlign: TextAlign.center,
expanded: false,
maxLines: 1,
padding: EdgeInsets.only(top: Sizes.rowPadding),
);
}
} }

View File

@ -94,11 +94,11 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
} }
List<charts.Series<EntityHistoryMoment, DateTime>> _parseHistory() { List<charts.Series<EntityHistoryMoment, DateTime>> _parseHistory() {
TheLogger.debug(" parsing history..."); Logger.d(" parsing history...");
Map<String, List<EntityHistoryMoment>> numericDataLists = {}; Map<String, List<EntityHistoryMoment>> numericDataLists = {};
int colorIdCounter = 0; int colorIdCounter = 0;
widget.config.numericAttributesToShow.forEach((String attrName) { widget.config.numericAttributesToShow.forEach((String attrName) {
TheLogger.debug(" parsing attribute $attrName"); Logger.d(" parsing attribute $attrName");
List<EntityHistoryMoment> data = []; List<EntityHistoryMoment> data = [];
DateTime now = DateTime.now(); DateTime now = DateTime.now();
for (var i = 0; i < widget.rawHistory.length; i++) { for (var i = 0; i < widget.rawHistory.length; i++) {
@ -152,7 +152,7 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
} }
List<charts.Series<EntityHistoryMoment, DateTime>> result = []; List<charts.Series<EntityHistoryMoment, DateTime>> result = [];
numericDataLists.forEach((attrName, dataList) { numericDataLists.forEach((attrName, dataList) {
TheLogger.debug(" adding ${dataList.length} data values"); Logger.d(" adding ${dataList.length} data values");
result.add( result.add(
new charts.Series<EntityHistoryMoment, DateTime>( new charts.Series<EntityHistoryMoment, DateTime>(
id: "value", id: "value",

View File

@ -42,7 +42,7 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
void _loadHistory(HomeAssistant ha, String entityId) { void _loadHistory(HomeAssistant ha, String entityId) {
DateTime now = DateTime.now(); DateTime now = DateTime.now();
if (_historyLastUpdated != null) { if (_historyLastUpdated != null) {
TheLogger.debug("History was updated ${now.difference(_historyLastUpdated).inSeconds} seconds ago"); Logger.d("History was updated ${now.difference(_historyLastUpdated).inSeconds} seconds ago");
} }
if (_historyLastUpdated == null || now.difference(_historyLastUpdated).inSeconds > 30) { if (_historyLastUpdated == null || now.difference(_historyLastUpdated).inSeconds > 30) {
_historyLastUpdated = now; _historyLastUpdated = now;
@ -52,7 +52,7 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
_needToUpdateHistory = false; _needToUpdateHistory = false;
}); });
}).catchError((e) { }).catchError((e) {
TheLogger.error("Error loading $entityId history: $e"); Logger.e("Error loading $entityId history: $e");
setState(() { setState(() {
_history = []; _history = [];
_needToUpdateHistory = false; _needToUpdateHistory = false;
@ -122,7 +122,7 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
} }
default: { default: {
TheLogger.debug(" Simple selected as default"); Logger.d(" Simple selected as default");
return SimpleStateHistoryChartWidget( return SimpleStateHistoryChartWidget(
rawHistory: _history, rawHistory: _history,
); );

View File

@ -54,7 +54,7 @@ class DateTimeStateWidget extends StatelessWidget {
} }
}); });
} else { } else {
TheLogger.warning( "${entity.entityId} has no date and no time"); Logger.w( "${entity.entityId} has no date and no time");
} }
} }

View File

@ -5,18 +5,24 @@ class SimpleEntityState extends StatelessWidget {
final bool expanded; final bool expanded;
final TextAlign textAlign; final TextAlign textAlign;
final EdgeInsetsGeometry padding; final EdgeInsetsGeometry padding;
final int maxLines;
const SimpleEntityState({Key key, this.expanded: true, this.textAlign: TextAlign.right, this.padding: const EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0)}) : super(key: key); const SimpleEntityState({Key key, this.maxLines: 10, this.expanded: true, this.textAlign: TextAlign.right, this.padding: const EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0)}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final entityModel = EntityModel.of(context); final entityModel = EntityModel.of(context);
String state = entityModel.entityWrapper.entity.displayState ?? "";
state = state.replaceAll("\n", "").replaceAll("\t", " ").trim();
while (state.contains(" ")){
state = state.replaceAll(" ", " ");
}
Widget result = Padding( Widget result = Padding(
padding: padding, padding: padding,
child: Text( child: Text(
"${entityModel.entityWrapper.entity.state} ${entityModel.entityWrapper.entity.unitOfMeasurement}", "$state ${entityModel.entityWrapper.entity.unitOfMeasurement}",
textAlign: textAlign, textAlign: textAlign,
maxLines: 10, maxLines: maxLines,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
softWrap: true, softWrap: true,
style: new TextStyle( style: new TextStyle(

View File

@ -85,7 +85,7 @@ class _TextInputStateWidgetState extends State<TextInputStateWidget> {
}), }),
); );
} else { } else {
TheLogger.warning( "Unsupported input mode for ${entity.entityId}"); Logger.w( "Unsupported input mode for ${entity.entityId}");
return SimpleEntityState(); return SimpleEntityState();
} }
} }

View File

@ -61,16 +61,16 @@ class HomeAssistant {
_password = password; _password = password;
_authType = authType; _authType = authType;
_useLovelace = useLovelace; _useLovelace = useLovelace;
TheLogger.debug( "Use lovelace is $_useLovelace"); Logger.d( "Use lovelace is $_useLovelace");
} }
Future fetch() { Future fetch() {
if ((_fetchCompleter != null) && (!_fetchCompleter.isCompleted)) { if ((_fetchCompleter != null) && (!_fetchCompleter.isCompleted)) {
TheLogger.warning("Previous fetch is not complited"); Logger.w("Previous fetch is not complited");
} else { } else {
_fetchCompleter = new Completer(); _fetchCompleter = new Completer();
_fetchTimer = Timer(fetchTimeout, () { _fetchTimer = Timer(fetchTimeout, () {
TheLogger.error( "Data fetching timeout"); Logger.e( "Data fetching timeout");
disconnect().then((_) { disconnect().then((_) {
_completeFetching({ _completeFetching({
"errorCode": 9, "errorCode": 9,
@ -90,7 +90,7 @@ class HomeAssistant {
disconnect() async { disconnect() async {
if ((_hassioChannel != null) && (_hassioChannel.closeCode == null) && (_hassioChannel.sink != null)) { if ((_hassioChannel != null) && (_hassioChannel.closeCode == null) && (_hassioChannel.sink != null)) {
await _hassioChannel.sink.close().timeout(Duration(seconds: 3), await _hassioChannel.sink.close().timeout(Duration(seconds: 3),
onTimeout: () => TheLogger.debug( "Socket sink closed") onTimeout: () => Logger.d( "Socket sink closed")
); );
await _socketSubscription.cancel(); await _socketSubscription.cancel();
_hassioChannel = null; _hassioChannel = null;
@ -100,15 +100,15 @@ class HomeAssistant {
Future _connection() { Future _connection() {
if ((_connectionCompleter != null) && (!_connectionCompleter.isCompleted)) { if ((_connectionCompleter != null) && (!_connectionCompleter.isCompleted)) {
TheLogger.debug("Previous connection is not complited"); Logger.d("Previous connection is not complited");
} else { } else {
if ((_hassioChannel == null) || (_hassioChannel.closeCode != null)) { if ((_hassioChannel == null) || (_hassioChannel.closeCode != null)) {
_connectionCompleter = new Completer(); _connectionCompleter = new Completer();
autoReconnect = false; autoReconnect = false;
disconnect().then((_){ disconnect().then((_){
TheLogger.debug( "Socket connecting..."); Logger.d( "Socket connecting...");
_connectionTimer = Timer(connectTimeout, () { _connectionTimer = Timer(connectTimeout, () {
TheLogger.error( "Socket connection timeout"); Logger.e( "Socket connection timeout");
_handleSocketError(null); _handleSocketError(null);
}); });
if (_socketSubscription != null) { if (_socketSubscription != null) {
@ -131,15 +131,15 @@ class HomeAssistant {
} }
void _handleSocketClose() { void _handleSocketClose() {
TheLogger.debug("Socket disconnected. Automatic reconnect is $autoReconnect"); Logger.d("Socket disconnected. Automatic reconnect is $autoReconnect");
if (autoReconnect) { if (autoReconnect) {
_reconnect(); _reconnect();
} }
} }
void _handleSocketError(e) { void _handleSocketError(e) {
TheLogger.error("Socket stream Error: $e"); Logger.e("Socket stream Error: $e");
TheLogger.debug("Automatic reconnect is $autoReconnect"); Logger.d("Automatic reconnect is $autoReconnect");
if (autoReconnect) { if (autoReconnect) {
_reconnect(); _reconnect();
} else { } else {
@ -186,7 +186,7 @@ class HomeAssistant {
_fetchCompleter.completeError(error); _fetchCompleter.completeError(error);
} else { } else {
autoReconnect = true; autoReconnect = true;
TheLogger.debug( "Fetch complete successful"); Logger.d( "Fetch complete successful");
_fetchCompleter.complete(); _fetchCompleter.complete();
} }
} }
@ -220,7 +220,7 @@ class HomeAssistant {
} else if (data["type"] == "auth_invalid") { } else if (data["type"] == "auth_invalid") {
_completeConnecting({"errorCode": 6, "errorMessage": "${data["message"]}"}); _completeConnecting({"errorCode": 6, "errorMessage": "${data["message"]}"});
} else if (data["type"] == "result") { } else if (data["type"] == "result") {
TheLogger.debug("[Received] <== id:${data["id"]}, ${data['success'] ? 'success' : 'error'}"); Logger.d("[Received] <== id:${data["id"]}, ${data['success'] ? 'success' : 'error'}");
if (data["id"] == _configMessageId) { if (data["id"] == _configMessageId) {
_parseConfig(data); _parseConfig(data);
} else if (data["id"] == _statesMessageId) { } else if (data["id"] == _statesMessageId) {
@ -234,15 +234,15 @@ class HomeAssistant {
} }
} else if (data["type"] == "event") { } else if (data["type"] == "event") {
if ((data["event"] != null) && (data["event"]["event_type"] == "state_changed")) { if ((data["event"] != null) && (data["event"]["event_type"] == "state_changed")) {
TheLogger.debug("[Received] <== ${data['type']}.${data["event"]["event_type"]}: ${data["event"]["data"]["entity_id"]}"); Logger.d("[Received] <== ${data['type']}.${data["event"]["event_type"]}: ${data["event"]["data"]["entity_id"]}");
_handleEntityStateChange(data["event"]["data"]); _handleEntityStateChange(data["event"]["data"]);
} else if (data["event"] != null) { } else if (data["event"] != null) {
TheLogger.warning("Unhandled event type: ${data["event"]["event_type"]}"); Logger.w("Unhandled event type: ${data["event"]["event_type"]}");
} else { } else {
TheLogger.error("Event is null: $message"); Logger.e("Event is null: $message");
} }
} else { } else {
TheLogger.warning("Unknown message type: $message"); Logger.w("Unknown message type: $message");
} }
} }
@ -302,7 +302,7 @@ class HomeAssistant {
} }
void _sendAuthMessageRaw(String message) { void _sendAuthMessageRaw(String message) {
TheLogger.debug( "[Sending] ==> auth request"); Logger.d( "[Sending] ==> auth request");
_hassioChannel.sink.add(message); _hassioChannel.sink.add(message);
} }
@ -311,11 +311,11 @@ class HomeAssistant {
if (queued) _messageQueue.add(message); if (queued) _messageQueue.add(message);
_connection().then((r) { _connection().then((r) {
_messageQueue.getActualMessages().forEach((message){ _messageQueue.getActualMessages().forEach((message){
TheLogger.debug( "[Sending queued] ==> $message"); Logger.d( "[Sending queued] ==> $message");
_hassioChannel.sink.add(message); _hassioChannel.sink.add(message);
}); });
if (!queued) { if (!queued) {
TheLogger.debug( "[Sending] ==> $message"); Logger.d( "[Sending] ==> $message");
_hassioChannel.sink.add(message); _hassioChannel.sink.add(message);
} }
sendCompleter.complete(); sendCompleter.complete();
@ -367,8 +367,10 @@ class HomeAssistant {
void _handleEntityStateChange(Map eventData) { void _handleEntityStateChange(Map eventData) {
//TheLogger.debug( "New state for ${eventData['entity_id']}"); //TheLogger.debug( "New state for ${eventData['entity_id']}");
Map data = Map.from(eventData); Map data = Map.from(eventData);
entities.updateState(data); eventBus.fire(new StateChangedEvent(
eventBus.fire(new StateChangedEvent(data["entity_id"], null)); entityId: data["entity_id"],
needToRebuildUI: entities.updateState(data)
));
} }
void _parseConfig(Map data) { void _parseConfig(Map data) {
@ -385,7 +387,7 @@ class HomeAssistant {
_userName = data["result"]["name"]; _userName = data["result"]["name"];
} else { } else {
_userName = null; _userName = null;
TheLogger.warning("There was an error getting current user: $data"); Logger.w("There was an error getting current user: $data");
} }
_userInfoCompleter.complete(); _userInfoCompleter.complete();
} }
@ -398,19 +400,19 @@ class HomeAssistant {
if (response["success"] == true) { if (response["success"] == true) {
_rawLovelaceData = response["result"]; _rawLovelaceData = response["result"];
} else { } else {
TheLogger.error("There was an error getting Lovelace config: $response"); Logger.e("There was an error getting Lovelace config: $response");
_rawLovelaceData = null; _rawLovelaceData = null;
} }
_lovelaceCompleter.complete(); _lovelaceCompleter.complete();
} }
void _parseLovelace() { void _parseLovelace() {
TheLogger.debug("--Title: ${_rawLovelaceData["title"]}"); Logger.d("--Title: ${_rawLovelaceData["title"]}");
ui.title = _rawLovelaceData["title"]; ui.title = _rawLovelaceData["title"];
int viewCounter = 0; int viewCounter = 0;
TheLogger.debug("--Views count: ${_rawLovelaceData['views'].length}"); Logger.d("--Views count: ${_rawLovelaceData['views'].length}");
_rawLovelaceData["views"].forEach((rawView){ _rawLovelaceData["views"].forEach((rawView){
TheLogger.debug("----view id: ${rawView['id']}"); Logger.d("----view id: ${rawView['id']}");
HAView view = HAView( HAView view = HAView(
count: viewCounter, count: viewCounter,
id: "${rawView['id']}", id: "${rawView['id']}",
@ -428,64 +430,63 @@ class HomeAssistant {
List<HACard> _createLovelaceCards(List rawCards) { List<HACard> _createLovelaceCards(List rawCards) {
List<HACard> result = []; List<HACard> result = [];
rawCards.forEach((rawCard){ rawCards.forEach((rawCard){
bool isThereCardOptionsInside = rawCard["card"] != null;
HACard card = HACard(
id: "card",
name: isThereCardOptionsInside ? rawCard["card"]["title"] ?? rawCard["card"]["name"] : rawCard["title"] ?? rawCard["name"],
type: isThereCardOptionsInside ? rawCard["card"]['type'] : rawCard['type'],
columnsCount: isThereCardOptionsInside ? rawCard["card"]['columns'] ?? 4 : rawCard['columns'] ?? 4,
showName: isThereCardOptionsInside ? rawCard["card"]['show_name'] ?? true : rawCard['show_name'] ?? true,
showState: isThereCardOptionsInside ? rawCard["card"]['show_state'] ?? true : rawCard['show_state'] ?? true,
showEmpty: rawCard['show_empty'] ?? true,
stateFilter: rawCard['state_filter'] ?? []
);
if (rawCard["cards"] != null) { if (rawCard["cards"] != null) {
result.addAll(_createLovelaceCards(rawCard["cards"])); card.childCards = _createLovelaceCards(rawCard["cards"]);
} else { }
HACard card = HACard( rawCard["entities"]?.forEach((rawEntity) {
id: "card", if (rawEntity is String) {
name: rawCard["title"], if (entities.isExist(rawEntity)) {
type: rawCard['type'], card.entities.add(EntityWrapper(entity: entities.get(rawEntity)));
columnsCount: rawCard['columns'] ?? 4, }
showName: rawCard['show_name'] ?? true, } else {
showState: rawCard['show_state'] ?? true, if (entities.isExist(rawEntity["entity"])) {
); Entity e = entities.get(rawEntity["entity"]);
rawCard["entities"]?.forEach((rawEntity) { card.entities.add(
if (rawEntity is String) { EntityWrapper(
if (entities.isExist(rawEntity)) {
card.entities.add(EntityWrapper(entity: entities.get(rawEntity)));
}
} else {
if (entities.isExist(rawEntity["entity"])) {
Entity e = entities.get(rawEntity["entity"]);
String tapAction = EntityTapAction.moreInfo;
String holdAction = EntityTapAction.none;
if (card.type == CardType.glance) {
tapAction = rawEntity["tap_action"] ?? EntityTapAction.moreInfo;
holdAction = rawEntity["hold_action"] ?? EntityTapAction.none;
}
card.entities.add(
EntityWrapper(
entity: e, entity: e,
displayName: rawEntity["name"], displayName: rawEntity["name"],
icon: rawEntity["icon"], icon: rawEntity["icon"],
tapAction: tapAction, uiAction: EntityUIAction(rawEntityData: rawEntity)
holdAction: holdAction, )
tapActionService: rawEntity["service"], );
tapActionServiceData: rawEntity["service_data"] ?? {"entity_id": e.entityId}
)
);
}
} }
});
if (rawCard["entity"] != null) {
var en = rawCard["entity"];
if (en is String) {
if (entities.isExist(en)) {
card.linkedEntity = EntityWrapper(entity: entities.get(en));
}
} else {
if (entities.isExist(en["entity"])) {
card.linkedEntity = EntityWrapper(
entity: entities.get(en["entity"]),
icon: en["icon"],
displayName: en["name"]
);
}
}
} }
result.add(card); });
if (rawCard["entity"] != null) {
var en = rawCard["entity"];
if (en is String) {
if (entities.isExist(en)) {
Entity e = entities.get(en);
card.linkedEntityWrapper = EntityWrapper(
entity: e,
uiAction: EntityUIAction(rawEntityData: rawCard)
);
}
} else {
if (entities.isExist(en["entity"])) {
Entity e = entities.get(en["entity"]);
card.linkedEntityWrapper = EntityWrapper(
entity: e,
icon: en["icon"],
displayName: en["name"],
uiAction: EntityUIAction(rawEntityData: rawCard)
);
}
}
} }
result.add(card);
}); });
return result; return result;
} }
@ -502,10 +503,10 @@ class HomeAssistant {
void _createUI() { void _createUI() {
ui = HomeAssistantUI(); ui = HomeAssistantUI();
if ((_useLovelace) && (_rawLovelaceData != null)) { if ((_useLovelace) && (_rawLovelaceData != null)) {
TheLogger.debug("Creating Lovelace UI"); Logger.d("Creating Lovelace UI");
_parseLovelace(); _parseLovelace();
} else { } else {
TheLogger.debug("Creating group-based UI"); Logger.d("Creating group-based UI");
int viewCounter = 0; int viewCounter = 0;
if (!entities.hasDefaultView) { if (!entities.hasDefaultView) {
HAView view = HAView( HAView view = HAView(
@ -544,7 +545,7 @@ class HomeAssistant {
//String endTime = formatDate(now, [yyyy, '-', mm, '-', dd, 'T', HH, ':', nn, ':', ss, z]); //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]); String startTime = formatDate(now.subtract(Duration(hours: 24)), [yyyy, '-', mm, '-', dd, 'T', HH, ':', nn, ':', ss, z]);
String url = "$homeAssistantWebHost/api/history/period/$startTime?&filter_entity_id=$entityId"; String url = "$homeAssistantWebHost/api/history/period/$startTime?&filter_entity_id=$entityId";
TheLogger.debug("[Sending] ==> $url"); Logger.d("[Sending] ==> $url");
http.Response historyResponse; http.Response historyResponse;
if (_authType == "access_token") { if (_authType == "access_token") {
historyResponse = await http.get(url, headers: { historyResponse = await http.get(url, headers: {
@ -559,7 +560,7 @@ class HomeAssistant {
} }
var history = json.decode(historyResponse.body); var history = json.decode(historyResponse.body);
if (history is List) { if (history is List) {
TheLogger.debug( "[Received] <== ${history.first.length} history recors"); Logger.d( "[Received] <== ${history.first.length} history recors");
return history; return history;
} else { } else {
return []; return [];

View File

@ -19,7 +19,7 @@ class _LogViewPageState extends State<LogViewPage> {
} }
_loadLog() async { _loadLog() async {
_logData = TheLogger.getLog(); _logData = Logger.getLog();
} }
@override @override

View File

@ -36,6 +36,7 @@ part 'entity_widgets/common/badge.dart';
part 'entity_widgets/model_widgets.dart'; part 'entity_widgets/model_widgets.dart';
part 'entity_widgets/default_entity_container.dart'; part 'entity_widgets/default_entity_container.dart';
part 'entity_widgets/glance_entity_container.dart'; part 'entity_widgets/glance_entity_container.dart';
part 'entity_widgets/button_entity_container.dart';
part 'entity_widgets/common/entity_attributes_list.dart'; part 'entity_widgets/common/entity_attributes_list.dart';
part 'entity_widgets/entity_icon.dart'; part 'entity_widgets/entity_icon.dart';
part 'entity_widgets/entity_name.dart'; part 'entity_widgets/entity_name.dart';
@ -78,23 +79,20 @@ part 'ui_class/view.class.dart';
part 'ui_class/card.class.dart'; part 'ui_class/card.class.dart';
part 'ui_class/sizes_class.dart'; part 'ui_class/sizes_class.dart';
part 'ui_widgets/view.dart'; part 'ui_widgets/view.dart';
part 'ui_widgets/entities_card.dart'; part 'ui_widgets/card_widget.dart';
part 'ui_widgets/glance_card.dart';
part 'ui_widgets/unsupported_card.dart';
part 'ui_widgets/media_control_card.dart';
part 'ui_widgets/card_header_widget.dart'; part 'ui_widgets/card_header_widget.dart';
EventBus eventBus = new EventBus(); EventBus eventBus = new EventBus();
const String appName = "HA Client"; const String appName = "HA Client";
const appVersion = "0.3.10-73"; const appVersion = "0.3.13";
String homeAssistantWebHost; String homeAssistantWebHost;
void main() { void main() {
FlutterError.onError = (errorDetails) { FlutterError.onError = (errorDetails) {
TheLogger.error( "${errorDetails.exception}"); Logger.e( "${errorDetails.exception}");
if (TheLogger.isInDebugMode) { if (Logger.isInDebugMode) {
FlutterError.dumpErrorToConsole(errorDetails); FlutterError.dumpErrorToConsole(errorDetails);
} }
}; };
@ -102,9 +100,9 @@ void main() {
runZoned(() { runZoned(() {
runApp(new HAClientApp()); runApp(new HAClientApp());
}, onError: (error, stack) { }, onError: (error, stack) {
TheLogger.error("$error"); Logger.e("$error");
TheLogger.error("$stack"); Logger.e("$stack");
if (TheLogger.isInDebugMode) { if (Logger.isInDebugMode) {
debugPrint("$stack"); debugPrint("$stack");
} }
}); });
@ -162,11 +160,11 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_settingsLoaded = false; _settingsLoaded = false;
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
TheLogger.debug("<!!!> Creating new HomeAssistant instance"); Logger.d("<!!!> Creating new HomeAssistant instance");
_homeAssistant = HomeAssistant(); _homeAssistant = HomeAssistant();
_settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) { _settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) {
TheLogger.debug("Settings change event: reconnect=${event.reconnect}"); Logger.d("Settings change event: reconnect=${event.reconnect}");
if (event.reconnect) { if (event.reconnect) {
_homeAssistant.disconnect().then((_){ _homeAssistant.disconnect().then((_){
_initialLoad(); _initialLoad();
@ -187,7 +185,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
TheLogger.debug("$state"); Logger.d("$state");
if (state == AppLifecycleState.resumed && _settingsLoaded) { if (state == AppLifecycleState.resumed && _settingsLoaded) {
_refreshData(); _refreshData();
} }
@ -214,7 +212,12 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_subscribe() { _subscribe() {
if (_stateSubscription == null) { if (_stateSubscription == null) {
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) { _stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
setState(() {}); if (event.needToRebuildUI) {
Logger.d("New entity. Need to rebuild UI");
_refreshData();
} else {
setState(() {});
}
}); });
} }
if (_serviceCallSubscription == null) { if (_serviceCallSubscription == null) {
@ -259,8 +262,8 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_setErrorState(e) { _setErrorState(e) {
if (e is Error) { if (e is Error) {
TheLogger.error(e.toString()); Logger.e(e.toString());
TheLogger.error("${e.stackTrace}"); Logger.e("${e.stackTrace}");
_showErrorBottomBar( _showErrorBottomBar(
message: "There was some error", message: "There was some error",
errorCode: 13 errorCode: 13

View File

@ -41,8 +41,8 @@ class MaterialDesignIcons {
"binary_sensor.heat.off": "mdi:thermometer", "binary_sensor.heat.off": "mdi:thermometer",
"binary_sensor.light.on": "mdi:brightness-7", "binary_sensor.light.on": "mdi:brightness-7",
"binary_sensor.light.off": "mdi:brightness-5", "binary_sensor.light.off": "mdi:brightness-5",
//"binary_sensor.lock.on": "mdi:", "binary_sensor.lock.on": "mdi:lock-open",
//"binary_sensor.lock.off": "mdi:", "binary_sensor.lock.off": "mdi:lock",
"binary_sensor.moisture.on": "mdi:water", "binary_sensor.moisture.on": "mdi:water",
"binary_sensor.moisture.off": "mdi:water-off", "binary_sensor.moisture.off": "mdi:water-off",
"binary_sensor.motion.on": "mdi:run", "binary_sensor.motion.on": "mdi:run",
@ -59,8 +59,8 @@ class MaterialDesignIcons {
"binary_sensor.power.off": "mdi:verified", "binary_sensor.power.off": "mdi:verified",
//"binary_sensor.presence.on": "mdi:", //"binary_sensor.presence.on": "mdi:",
//"binary_sensor.presence.off": "mdi:", //"binary_sensor.presence.off": "mdi:",
//"binary_sensor.problem.on": "mdi:", "binary_sensor.problem.on": "mdi:alert-outline",
//"binary_sensor.problem.off": "mdi:", "binary_sensor.problem.off": "mdi:check-outline",
"binary_sensor.safety.on": "mdi:alert", "binary_sensor.safety.on": "mdi:alert",
"binary_sensor.safety.off": "mdi:verified", "binary_sensor.safety.off": "mdi:verified",
"binary_sensor.smoke.on": "mdi:alert", "binary_sensor.smoke.on": "mdi:alert",
@ -69,13 +69,13 @@ class MaterialDesignIcons {
"binary_sensor.sound.off": "mdi:music-note-off", "binary_sensor.sound.off": "mdi:music-note-off",
"binary_sensor.vibration.on": "mdi:vibrate", "binary_sensor.vibration.on": "mdi:vibrate",
"binary_sensor.vibration.off": "mdi:mdi-crop-portrait", "binary_sensor.vibration.off": "mdi:mdi-crop-portrait",
//"binary_sensor.window.on": "mdi:", "binary_sensor.window.on": "mdi:window-open",
//"binary_sensor.window.off": "mdi:", "binary_sensor.window.off": "mdi:window-closed",
"sensor.battery": "mdi:battery-80", "sensor.battery": "mdi:battery-80",
"sensor.humidity": "mdi:water-percent", "sensor.humidity": "mdi:water-percent",
//"sensor.illuminance": "mdi:", //"sensor.illuminance": "mdi:",
"sensor.temperature": "mdi:thermometer", "sensor.temperature": "mdi:thermometer",
//"cover.window": "mdi:", "cover.window": "mdi:mdi:window-closed",
"cover.garage.closed": "mdi:garage", "cover.garage.closed": "mdi:garage",
"cover.garage.open": "mdi:garage-open", "cover.garage.open": "mdi:garage-open",
"cover.garage.opening": "mdi:garage-open", "cover.garage.opening": "mdi:garage-open",

View File

@ -83,13 +83,13 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
icon: Icon(Icons.check), icon: Icon(Icons.check),
onPressed: (){ onPressed: (){
if (_checkConfigChanged()) { if (_checkConfigChanged()) {
TheLogger.debug("Settings changed. Saving..."); Logger.d("Settings changed. Saving...");
_saveSettings().then((r) { _saveSettings().then((r) {
Navigator.pop(context); Navigator.pop(context);
eventBus.fire(SettingsChangedEvent(true)); eventBus.fire(SettingsChangedEvent(true));
}); });
} else { } else {
TheLogger.debug("Settings was not changed"); Logger.d("Settings was not changed");
Navigator.pop(context); Navigator.pop(context);
} }
} }
@ -150,6 +150,10 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
_newHassioPort = value; _newHassioPort = value;
} }
), ),
new Text(
"Try ports 80 and 443 if default is not working and you don't know why.",
style: TextStyle(color: Colors.grey),
),
new Row( new Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -170,6 +174,10 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
) )
], ],
), ),
new Text(
"You should use access token for HA >= 0.84.1. Legacy password will not work there.",
style: TextStyle(color: Colors.grey),
),
new TextField( new TextField(
decoration: InputDecoration( decoration: InputDecoration(
labelText: _newAuthType == "access_token" ? "Access token" : "API password" labelText: _newAuthType == "access_token" ? "Access token" : "API password"

View File

@ -2,76 +2,45 @@ part of '../main.dart';
class HACard { class HACard {
List<EntityWrapper> entities = []; List<EntityWrapper> entities = [];
EntityWrapper linkedEntity; List<HACard> childCards = [];
EntityWrapper linkedEntityWrapper;
String name; String name;
String id; String id;
String type; String type;
bool showName; bool showName;
bool showState; bool showState;
bool showEmpty;
int columnsCount; int columnsCount;
List stateFilter;
HACard({ HACard({
this.name, this.name,
this.id, this.id,
this.linkedEntity, this.linkedEntityWrapper,
this.columnsCount: 4, this.columnsCount: 4,
this.showName: true, this.showName: true,
this.showState: true, this.showState: true,
this.stateFilter: const [],
this.showEmpty: true,
@required this.type @required this.type
}); });
Widget build(BuildContext context) { List<EntityWrapper> getEntitiesToShow() {
switch (type) { return entities.where((entityWrapper) {
if (entityWrapper.entity.isHidden) {
case CardType.entities: { return false;
return EntitiesCardWidget(
card: this,
);
}
case CardType.glance: {
return GlanceCardWidget(
card: this,
);
}
case CardType.mediaControl: {
return MediaControlCardWidget(
card: this,
);
}
case CardType.weatherForecast:
case CardType.thermostat:
case CardType.sensor:
case CardType.plantStatus:
case CardType.pictureEntity:
case CardType.pictureElements:
case CardType.picture:
case CardType.map:
case CardType.iframe:
case CardType.gauge:
case CardType.entityButton:
case CardType.conditional:
case CardType.alarmPanel: {
return UnsupportedCardWidget(
card: this,
);
}
default: {
if ((linkedEntity == null) && (entities.isNotEmpty)) {
return EntitiesCardWidget(
card: this,
);
} else {
return UnsupportedCardWidget(
card: this,
);
}
}
} }
if (stateFilter.isNotEmpty) {
return stateFilter.contains(entityWrapper.entity.state);
}
return true;
}).toList();
}
Widget build(BuildContext context) {
return CardWidget(
card: this,
);
} }
} }

View File

@ -3,9 +3,10 @@ part of '../main.dart';
class Sizes { class Sizes {
static const rightWidgetPadding = 14.0; static const rightWidgetPadding = 14.0;
static const leftWidgetPadding = 8.0; static const leftWidgetPadding = 8.0;
static const buttonPadding = 4.0;
static const extendedWidgetHeight = 50.0; static const extendedWidgetHeight = 50.0;
static const iconSize = 28.0; static const iconSize = 28.0;
static const largeIconSize = 34.0; static const largeIconSize = 46.0;
static const stateFontSize = 15.0; static const stateFontSize = 15.0;
static const nameFontSize = 15.0; static const nameFontSize = 15.0;
static const smallFontSize = 14.0; static const smallFontSize = 14.0;

View File

@ -28,8 +28,8 @@ class HAView {
HACard card = HACard( HACard card = HACard(
name: e.displayName, name: e.displayName,
id: e.entityId, id: e.entityId,
linkedEntity: EntityWrapper(entity: e), linkedEntityWrapper: EntityWrapper(entity: e),
type: "media-control" type: CardType.mediaControl
); );
cards.add(card); cards.add(card);
}); });
@ -40,7 +40,7 @@ class HAView {
HACard card = HACard( HACard card = HACard(
id: groupIdToAdd, id: groupIdToAdd,
name: entity.domain, name: entity.domain,
type: "entities" type: CardType.entities
); );
card.entities.add(EntityWrapper(entity: entity)); card.entities.add(EntityWrapper(entity: entity));
autoGeneratedCards.add(card); autoGeneratedCards.add(card);
@ -51,16 +51,16 @@ class HAView {
HACard card = HACard( HACard card = HACard(
name: entity.displayName, name: entity.displayName,
id: entity.entityId, id: entity.entityId,
linkedEntity: EntityWrapper(entity: entity), linkedEntityWrapper: EntityWrapper(entity: entity),
type: "entities" type: CardType.entities
); );
card.entities.addAll(entity.childEntities.where((entity) {return entity.domain != "media_player";}).map((e) {return EntityWrapper(entity: e);})); card.entities.addAll(entity.childEntities.where((entity) {return entity.domain != "media_player";}).map((e) {return EntityWrapper(entity: e);}));
entity.childEntities.where((entity) {return entity.domain == "media_player";}).forEach((entity){ entity.childEntities.where((entity) {return entity.domain == "media_player";}).forEach((entity){
HACard mediaCard = HACard( HACard mediaCard = HACard(
name: entity.displayName, name: entity.displayName,
id: entity.entityId, id: entity.entityId,
linkedEntity: EntityWrapper(entity: entity), linkedEntityWrapper: EntityWrapper(entity: entity),
type: "media-control" type: CardType.mediaControl
); );
cards.add(mediaCard); cards.add(mediaCard);
}); });

View File

@ -0,0 +1,217 @@
part of '../main.dart';
class CardWidget extends StatelessWidget {
final HACard card;
const CardWidget({
Key key,
this.card
}) : super(key: key);
@override
Widget build(BuildContext context) {
if ((card.linkedEntityWrapper!= null) && (card.linkedEntityWrapper.entity.isHidden)) {
return Container(width: 0.0, height: 0.0,);
}
switch (card.type) {
case CardType.entities: {
return _buildEntitiesCard(context);
}
case CardType.glance: {
return _buildGlanceCard(context);
}
case CardType.mediaControl: {
return _buildMediaControlsCard(context);
}
case CardType.entityButton: {
return _buildEntityButtonCard(context);
}
case CardType.horizontalStack: {
if (card.childCards.isNotEmpty) {
List<Widget> children = [];
card.childCards.forEach((card) {
if (card.getEntitiesToShow().isNotEmpty || card.showEmpty) {
children.add(
Flexible(
fit: FlexFit.tight,
child: card.build(context),
)
);
}
});
return Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
);
}
return Container(height: 0.0, width: 0.0,);
}
case CardType.verticalStack: {
if (card.childCards.isNotEmpty) {
List<Widget> children = [];
card.childCards.forEach((card) {
children.add(
card.build(context)
);
});
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: children,
);
}
return Container(height: 0.0, width: 0.0,);
}
default: {
if ((card.linkedEntityWrapper == null) && (card.entities.isNotEmpty)) {
return _buildEntitiesCard(context);
} else {
return _buildUnsupportedCard(context);
}
}
}
}
Widget _buildEntitiesCard(BuildContext context) {
List<EntityWrapper> entitiesToShow = card.getEntitiesToShow();
if (entitiesToShow.isEmpty && !card.showEmpty) {
return Container(height: 0.0, width: 0.0,);
}
List<Widget> body = [];
body.add(CardHeaderWidget(name: card.name));
entitiesToShow.forEach((EntityWrapper entity) {
if (!entity.entity.isHidden) {
body.add(
Padding(
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
child: EntityModel(
entityWrapper: entity,
handleTap: true,
child: entity.entity.buildDefaultWidget(context)
),
));
}
});
return Card(
child: new Column(mainAxisSize: MainAxisSize.min, children: body)
);
}
Widget _buildGlanceCard(BuildContext context) {
List<EntityWrapper> entitiesToShow = card.getEntitiesToShow();
if (entitiesToShow.isEmpty && !card.showEmpty) {
return Container(height: 0.0, width: 0.0,);
}
List<Widget> rows = [];
rows.add(CardHeaderWidget(name: card.name));
List<Widget> result = [];
int columnsCount = entitiesToShow.length >= card.columnsCount ? card.columnsCount : entitiesToShow.length;
entitiesToShow.forEach((EntityWrapper entity) {
result.add(
FractionallySizedBox(
widthFactor: 1/columnsCount,
child: EntityModel(
entityWrapper: entity,
child: GlanceEntityContainer(
showName: card.showName,
showState: card.showState,
),
handleTap: true
),
)
);
});
rows.add(
Padding(
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, 2*Sizes.rowPadding),
child: Wrap(
//alignment: WrapAlignment.spaceAround,
runSpacing: Sizes.rowPadding*2,
children: result,
),
)
);
return Card(
child: new Column(mainAxisSize: MainAxisSize.min, children: rows)
);
}
Widget _buildMediaControlsCard(BuildContext context) {
if (card.linkedEntityWrapper == null || card.linkedEntityWrapper.entity == null) {
return Container(width: 0, height: 0,);
} else {
return Card(
child: EntityModel(
entityWrapper: card.linkedEntityWrapper,
handleTap: null,
child: MediaPlayerWidget()
)
);
}
}
Widget _buildEntityButtonCard(BuildContext context) {
if (card.linkedEntityWrapper == null || card.linkedEntityWrapper.entity == null) {
return Container(width: 0, height: 0,);
} else {
card.linkedEntityWrapper.displayName = card.name?.toUpperCase() ??
card.linkedEntityWrapper.displayName.toUpperCase();
return Card(
child: EntityModel(
entityWrapper: card.linkedEntityWrapper,
child: ButtonEntityContainer(),
handleTap: true
)
);
}
}
Widget _buildUnsupportedCard(BuildContext context) {
List<Widget> body = [];
body.add(CardHeaderWidget(name: card.name ?? ""));
List<Widget> result = [];
if (card.linkedEntityWrapper != null) {
result.addAll(<Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
child: EntityModel(
entityWrapper: card.linkedEntityWrapper,
handleTap: true,
child: card.linkedEntityWrapper.entity.buildDefaultWidget(context)
),
)
]);
} else {
result.addAll(<Widget>[
Padding(
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
child: Text("'${card.type}' card is not supported yet"),
),
]);
}
body.addAll(result);
return Card(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: body
)
);
}
}

View File

@ -1,43 +0,0 @@
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.entity.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((EntityWrapper entity) {
if (!entity.entity.isHidden) {
result.add(
Padding(
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
child: EntityModel(
entityWrapper: entity,
handleTap: true,
child: entity.entity.buildDefaultWidget(context)
),
));
}
});
return result;
}
}

View File

@ -1,52 +0,0 @@
part of '../main.dart';
class GlanceCardWidget extends StatelessWidget {
final HACard card;
const GlanceCardWidget({
Key key,
this.card
}) : super(key: key);
@override
Widget build(BuildContext context) {
if ((card.linkedEntity!= null) && (card.linkedEntity.entity.isHidden)) {
return Container(width: 0.0, height: 0.0,);
}
List<Widget> rows = [];
rows.add(CardHeaderWidget(name: card.name));
rows.add(_buildRows(context));
return Card(
child: new Column(mainAxisSize: MainAxisSize.min, children: rows)
);
}
Widget _buildRows(BuildContext context) {
List<Widget> result = [];
List<EntityWrapper> toShow = card.entities.where((entity) {return !entity.entity.isHidden;}).toList();
int columnsCount = toShow.length >= card.columnsCount ? card.columnsCount : toShow.length;
toShow.forEach((EntityWrapper entity) {
result.add(
FractionallySizedBox(
widthFactor: 1/columnsCount,
child: EntityModel(
entityWrapper: entity,
child: entity.entity.buildGlanceWidget(context, card.showName, card.showState),
handleTap: true
),
)
);
});
return Padding(
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, 2*Sizes.rowPadding),
child: Wrap(
//alignment: WrapAlignment.spaceAround,
runSpacing: Sizes.rowPadding*2,
children: result,
),
);
}
}

View File

@ -1,29 +0,0 @@
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.entity.isHidden)) {
return Container(width: 0.0, height: 0.0,);
}
return Card(
child: EntityModel(
entityWrapper: card.linkedEntity,
handleTap: null,
child: MediaPlayerWidget()
)
);
}
}

View File

@ -1,53 +0,0 @@
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.entity.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, Sizes.rowPadding, 0.0, Sizes.rowPadding),
child: EntityModel(
entityWrapper: card.linkedEntity,
handleTap: true,
child: card.linkedEntity.entity.buildDefaultWidget(context)
),
)
]);
} else {
result.addAll(<Widget>[
Padding(
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
child: Text("'${card.type}' card is not supported yet"),
),
]);
}
return result;
}
}

View File

@ -78,7 +78,7 @@ class ViewWidgetState extends State<ViewWidget> {
Future _refreshData() { Future _refreshData() {
if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) { if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) {
TheLogger.debug("Previous data refresh is still in progress"); Logger.d("Previous data refresh is still in progress");
} else { } else {
_refreshCompleter = Completer(); _refreshCompleter = Completer();
eventBus.fire(RefreshDataEvent()); eventBus.fire(RefreshDataEvent());

View File

@ -1,6 +1,6 @@
part of 'main.dart'; part of 'main.dart';
class TheLogger { class Logger {
static List<String> _log = []; static List<String> _log = [];
@ -20,15 +20,15 @@ class TheLogger {
return inDebugMode; return inDebugMode;
} }
static void error(String message) { static void e(String message) {
_writeToLog("Error", message); _writeToLog("Error", message);
} }
static void warning(String message) { static void w(String message) {
_writeToLog("Warning", message); _writeToLog("Warning", message);
} }
static void debug(String message) { static void d(String message) {
_writeToLog("Debug", message); _writeToLog("Debug", message);
} }
@ -50,7 +50,7 @@ class HAUtils {
if (await canLaunch(url)) { if (await canLaunch(url)) {
await launch(url); await launch(url);
} else { } else {
TheLogger.error( "Could not launch $url"); Logger.e( "Could not launch $url");
} }
} }
} }
@ -58,8 +58,13 @@ class HAUtils {
class StateChangedEvent { class StateChangedEvent {
String entityId; String entityId;
String newState; String newState;
bool needToRebuildUI;
StateChangedEvent(this.entityId, this.newState); StateChangedEvent({
this.entityId,
this.newState,
this.needToRebuildUI: false
});
} }
class SettingsChangedEvent { class SettingsChangedEvent {

View File

@ -35,7 +35,7 @@ packages:
name: cached_network_image name: cached_network_image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.0+1" version: "0.5.1"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
@ -83,7 +83,7 @@ packages:
description: description:
path: "." path: "."
ref: HEAD ref: HEAD
resolved-ref: c5727795659e886a7db8b39a14e2c8987280fe1f resolved-ref: e26916e095244a7e5ea61315b030d298d127ed26
url: "https://github.com/MarkOSullivan94/dart_config.git" url: "https://github.com/MarkOSullivan94/dart_config.git"
source: git source: git
version: "0.5.0" version: "0.5.0"
@ -126,7 +126,7 @@ packages:
name: flutter_launcher_icons name: flutter_launcher_icons
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.1" version: "0.7.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -152,7 +152,7 @@ packages:
name: image name: image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.4" version: "2.0.5"
intl: intl:
dependency: transitive dependency: transitive
description: description:
@ -290,7 +290,7 @@ packages:
name: url_launcher name: url_launcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.1" version: "4.0.2"
uuid: uuid:
dependency: transitive dependency: transitive
description: description:
@ -327,5 +327,5 @@ packages:
source: hosted source: hosted
version: "2.1.15" version: "2.1.15"
sdks: sdks:
dart: ">=2.0.0 <=2.1.0-dev.9.3.flutter-9c07fb64c4" dart: ">=2.0.0 <3.0.0"
flutter: ">=0.5.6 <2.0.0" flutter: ">=0.5.6 <2.0.0"

View File

@ -1,7 +1,7 @@
name: hass_client name: hass_client
description: Home Assistant Android Client description: Home Assistant Android Client
version: 0.3.10+73 version: 0.3.13+80
environment: environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0" sdk: ">=2.0.0-dev.68.0 <3.0.0"