Compare commits

...

76 Commits

Author SHA1 Message Date
4bb616b327 Build number 2019-01-31 21:14:20 +02:00
38219618ba version 0.3.14 2019-01-31 21:05:11 +02:00
6774b53758 Disable unfinished camera support 2019-01-31 20:57:33 +02:00
29a94c882f WIP MJPEG stream handling 2019-01-31 01:04:13 +02:00
5897fa3a99 WIP #143 2019-01-30 00:25:41 +02:00
7af92c2dc9 WIP #143 Camera support 2019-01-29 22:03:08 +02:00
1094177a42 Resolves #282 Trigger button for alarm 2019-01-29 18:51:28 +02:00
5e814e8109 Resolves #204 Alarm panel card support 2019-01-29 15:00:15 +02:00
24c7675fa4 Resolves #142 Alarm control panel support 2019-01-29 11:54:26 +02:00
dc3ca38c78 WIP #142 Alarm control panel 2019-01-28 16:48:49 +02:00
96b528e055 Update README.md 2019-01-28 15:05:19 +02:00
3858036631 Resolves #139 Trigger for automations 2019-01-25 23:48:31 +02:00
19d42ceeb3 Fix names null 2019-01-25 23:30:23 +02:00
a2836a3603 Resolves #257 2019-01-25 23:08:12 +02:00
2a45758a6d Resolves #268 Badges for Lovelace UI 2019-01-25 22:55:41 +02:00
dc1bf4d878 WIP #266 fix icons 2019-01-25 22:45:54 +02:00
e82ba60c4e WIP #266 Card parsing proper error handling and toString for some fields 2019-01-25 22:41:26 +02:00
09199d30e8 Resolves #274 Use Lovelace UI by default 2019-01-25 22:29:16 +02:00
724d32dbe2 Resolves #277 Remove legacy password support 2019-01-25 22:27:13 +02:00
949c8ee44e Resolves #264 Throttle for sending thermostat temperature 2019-01-25 22:19:11 +02:00
1a446d34c7 Resolves #262 Cler 2019-01-25 22:19:11 +02:00
22a5847285 Resolves #270 Current light effect for lights 2019-01-24 12:26:38 +02:00
1c8f770f10 Resolves #121 Markdown card support 2019-01-23 23:34:45 +02:00
be5ea55f6b Fix light color controls appearence issue 2018-12-29 17:44:11 +02:00
c65ade9827 Fix icons for entity button 2018-12-25 11:48:37 +02:00
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
0219f7bfbb Version 0.3.10 2018-11-24 17:04:41 +02:00
5f3c77f4b9 Resolves #145 Fan support 2018-11-24 17:00:45 +02:00
a36c7a9ca3 Resolves #186 Switch for group with same domain antities 2018-11-24 11:33:59 +02:00
56ce6dfeeb Improved tap animation for glance card 2018-11-24 11:02:28 +02:00
67c214454f build number 2018-11-24 01:20:18 +02:00
73398378c4 Resolves #227 2018-11-24 00:37:55 +02:00
215871ce9e Resolves #226 2018-11-23 21:59:33 +02:00
fd8ea6befd Show auto groups that is not hidden 2018-11-23 19:30:16 +02:00
809a1a1c8c Resolves #146 Lock support 2018-11-23 19:18:17 +02:00
fc8f2f200f Small screens support 2018-11-23 18:16:38 +02:00
f41c9f9197 Resolves #202 Service call info 2018-11-23 16:38:26 +02:00
cdf55ce68b Resolves #201 New progress indicator in the bottom of the app 2018-11-23 16:30:42 +02:00
12088d9516 Resolves #223, Resolves #197 2018-11-23 16:03:38 +02:00
a0235ee385 Handle entity taps and holds in one place 2018-11-23 15:06:42 +02:00
67fbdb13c6 Proper autogenerateg groups detection 2018-11-23 14:33:03 +02:00
c5960de0be Resolves #193 Source selection support for media_player 2018-11-23 14:18:25 +02:00
da15e880ec Sound mode support for media player 2018-11-23 14:11:34 +02:00
efbe33f4e3 Fix font sizes, long entity states 2018-11-18 16:40:12 +02:00
af84c99a2d build 69 2018-11-18 13:25:00 +02:00
438449cad8 Handling taps on entity name and state for glance card 2018-11-18 13:24:05 +02:00
d9ca55c3b7 Resolves #131 2018-11-18 13:19:00 +02:00
f248268984 New bottom info bar 2018-11-18 12:46:54 +02:00
8ee096595c Resolves #192 Don't fetch data on every app resume 2018-11-18 09:47:22 +02:00
63 changed files with 1988 additions and 772 deletions

View File

@ -4,7 +4,7 @@
Home Assistant Android client on Dart with Flutter.
Visit [www.keyboardcrumbs.io](http://www.keyboardcrumbs.io/ha-client) for more info.
Visit [www.vynn.co](https://www.vynn.co/ha-client) for more info.
Join [Google Group](https://groups.google.com/d/forum/ha-client-alpha-testing) to become an alpha tester

View File

@ -31,6 +31,11 @@ keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
compileSdkVersion 27
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
disable 'InvalidPackage'
}

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

@ -1,9 +1,9 @@
part of 'main.dart';
class EntityViewPage extends StatefulWidget {
EntityViewPage({Key key, @required this.entity, @required this.homeAssistant }) : super(key: key);
EntityViewPage({Key key, @required this.entityId, @required this.homeAssistant }) : super(key: key);
final Entity entity;
final String entityId;
final HomeAssistant homeAssistant;
@override
@ -12,30 +12,26 @@ class EntityViewPage extends StatefulWidget {
class _EntityViewPageState extends State<EntityViewPage> {
String _title;
StreamSubscription _refreshDataSubscription;
StreamSubscription _stateSubscription;
@override
void initState() {
super.initState();
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
if (event.entityId == widget.entity.entityId) {
Logger.d("State change event handled by entity page: ${event.entityId}");
if (event.entityId == widget.entityId) {
setState(() {});
}
});
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
setState(() {});
});
_prepareData();
_getHistory();
}
void _prepareData() async {
_title = widget.entity.displayName;
}
void _getHistory() {
/* widget.homeAssistant.getHistory(widget.entity.entityId).then((List history) {
if (history != null) {
}
});*/
_title = widget.homeAssistant.entities.get(widget.entityId).displayName;
}
@ -54,7 +50,7 @@ class _EntityViewPageState extends State<EntityViewPage> {
padding: EdgeInsets.all(10.0),
child: HomeAssistantModel(
homeAssistant: widget.homeAssistant,
child: widget.entity.buildEntityPageWidget(context)
child: widget.homeAssistant.entities.get(widget.entityId).buildEntityPageWidget(context)
)
),
);
@ -63,6 +59,7 @@ class _EntityViewPageState extends State<EntityViewPage> {
@override
void dispose(){
if (_stateSubscription != null) _stateSubscription.cancel();
if (_refreshDataSubscription != null) _refreshDataSubscription.cancel();
super.dispose();
}
}

View File

@ -0,0 +1,12 @@
part of '../main.dart';
class AlarmControlPanelEntity extends Entity {
AlarmControlPanelEntity(Map rawData) : super(rawData);
@override
Widget _buildAdditionalControlsForPage(BuildContext context) {
return AlarmControlPanelControlsWidget(
extended: false,
);
}
}

View File

@ -0,0 +1,24 @@
part of '../main.dart';
class AutomationEntity extends Entity {
AutomationEntity(Map rawData) : super(rawData);
@override
Widget _buildStatePart(BuildContext context) {
return SwitchStateWidget();
}
@override
Widget _buildAdditionalControlsForPage(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
FlatServiceButton(
text: "TRIGGER",
serviceName: "trigger",
)
],
);
}
}

View File

@ -5,6 +5,8 @@ class ButtonEntity extends Entity {
@override
Widget _buildStatePart(BuildContext context) {
return ButtonStateWidget();
return FlatServiceButton(
text: "EXECUTE",
);
}
}

View File

@ -0,0 +1,19 @@
part of '../main.dart';
class CameraEntity extends Entity {
static const SUPPORT_ON_OFF = 1;
CameraEntity(Map rawData) : super(rawData);
bool get supportOnOff => ((attributes["supported_features"] &
CameraEntity.SUPPORT_ON_OFF) ==
CameraEntity.SUPPORT_ON_OFF);
@override
Widget _buildAdditionalControlsForPage(BuildContext context) {
return CameraControlsWidget(
url: 'https://citadel.vynn.co:8123/api/camera_proxy_stream/camera.demo_camera?token=${this.attributes['access_token']}',
);
}
}

View File

@ -30,8 +30,69 @@ class EntityState {
static const problem = 'problem';
}
class EntityTapAction {
class EntityUIAction {
static const moreInfo = 'more-info';
static const toggle = 'toggle';
static const callService = 'call-service';
static const navigate = 'navigate';
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 {
static const horizontalStack = "horizontal-stack";
static const verticalStack = "vertical-stack";
static const entities = "entities";
static const glance = "glance";
static const mediaControl = "media-control";
static const weatherForecast = "weather-forecast";
static const thermostat = "thermostat";
static const sensor = "sensor";
static const plantStatus = "plant-status";
static const pictureEntity = "picture-entity";
static const pictureElements = "picture-elements";
static const picture = "picture";
static const map = "map";
static const iframe = "iframe";
static const gauge = "gauge";
static const entityButton = "entity-button";
static const conditional = "conditional";
static const alarmPanel = "alarm-panel";
static const markdown = "markdown";
}

View File

@ -12,14 +12,65 @@ class Entity {
"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": "Clear",
"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;
String domain;
String entityId;
String state;
String displayState;
DateTime _lastUpdated;
List<Entity> childEntities = [];
List<String> attributesToShow = ["all"];
String deviceClass;
EntityHistoryConfig historyConfig = EntityHistoryConfig(
chartType: EntityHistoryWidgetType.simple
);
@ -27,7 +78,6 @@ class Entity {
String get displayName =>
attributes["friendly_name"] ?? (attributes["name"] ?? entityId.split(".")[1].replaceAll("_", " "));
String get deviceClass => attributes["device_class"] ?? null;
bool get isView =>
(domain == "group") &&
(attributes != null ? attributes["view"] ?? false : false);
@ -50,7 +100,9 @@ class Entity {
attributes = rawData["attributes"] ?? {};
domain = rawData["entity_id"].split(".")[0];
entityId = rawData["entity_id"];
deviceClass = attributes["device_class"];
state = rawData["state"];
displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? state;
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
}
@ -76,19 +128,21 @@ class Entity {
}
}
List<String> getStringListAttributeValue(String attribute) {
if (attributes["$attribute"] != null) {
List<String> result = (attributes["$attribute"] as List).cast<String>();
return result;
} else {
return null;
}
}
Widget buildDefaultWidget(BuildContext context) {
return DefaultEntityContainer(
state: _buildStatePart(context)
);
}
Widget buildGlanceWidget(BuildContext context, bool showName, bool showState) {
return GlanceEntityContainer(
showName: showName,
showState: showState,
);
}
Widget _buildStatePart(BuildContext context) {
return SimpleEntityState();
}

View File

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

View File

@ -0,0 +1,32 @@
part of '../main.dart';
class FanEntity extends Entity {
static const SUPPORT_SET_SPEED = 1;
static const SUPPORT_OSCILLATE = 2;
static const SUPPORT_DIRECTION = 4;
FanEntity(Map rawData) : super(rawData);
bool get supportSetSpeed => ((attributes["supported_features"] &
FanEntity.SUPPORT_SET_SPEED) ==
FanEntity.SUPPORT_SET_SPEED);
bool get supportOscillate => ((attributes["supported_features"] &
FanEntity.SUPPORT_OSCILLATE) ==
FanEntity.SUPPORT_OSCILLATE);
bool get supportDirection => ((attributes["supported_features"] &
FanEntity.SUPPORT_DIRECTION) ==
FanEntity.SUPPORT_DIRECTION);
List<String> get speedList => getStringListAttributeValue("speed_list");
@override
Widget _buildStatePart(BuildContext context) {
return SwitchStateWidget();
}
@override
Widget _buildAdditionalControlsForPage(BuildContext context) {
return FanControlsWidget();
}
}

View File

@ -0,0 +1,43 @@
part of '../main.dart';
class GroupEntity extends Entity {
GroupEntity(Map rawData) : super(rawData);
final List<String> _domainsForSwitchableGroup = ["switch", "light", "automation", "input_boolean"];
String mutualDomain;
bool switchable = false;
@override
Widget _buildStatePart(BuildContext context) {
if (switchable) {
return SwitchStateWidget(
domainForService: "homeassistant",
);
} else {
return super._buildStatePart(context);
}
}
@override
void update(Map rawData) {
super.update(rawData);
if (_isOneDomain()) {
mutualDomain = attributes['entity_id'][0].split(".")[0];
switchable = _domainsForSwitchableGroup.contains(mutualDomain);
}
}
bool _isOneDomain() {
bool result = false;
if (attributes['entity_id'] != null && attributes['entity_id'] is List && attributes['entity_id'].isNotEmpty) {
String firstChildDomain = attributes['entity_id'][0].split(".")[0];
result = true;
attributes['entity_id'].forEach((childEntityId){
if (childEntityId.split(".")[0] != firstChildDomain) {
result = false;
}
});
}
return result;
}
}

View File

@ -33,12 +33,13 @@ class LightEntity extends Entity {
LightEntity.SUPPORT_WHITE_VALUE);
int get brightness => _getIntAttributeValue("brightness");
String get effect => attributes["effect"];
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();
List<String> get effectList => getStringListAttributeValue("effect_list");
LightEntity(Map rawData) : super(rawData);
@ -55,15 +56,6 @@ class LightEntity extends Entity {
}
}
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();

View File

@ -0,0 +1,12 @@
part of '../main.dart';
class LockEntity extends Entity {
LockEntity(Map rawData) : super(rawData);
bool get isLocked => state == "locked";
@override
Widget _buildStatePart(BuildContext context) {
return LockStateWidget();
}
}

View File

@ -72,6 +72,9 @@ class MediaPlayerEntity extends Entity {
MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE) ==
MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE);
List<String> get soundModeList => getStringListAttributeValue("sound_mode_list");
List<String> get sourceList => getStringListAttributeValue("source_list");
@override
Widget _buildAdditionalControlsForPage(BuildContext context) {
return MediaPlayerControls();

View File

@ -19,7 +19,7 @@ class EntityCollection {
_allEntities.clear();
//views.clear();
TheLogger.debug("Parsing ${rawData.length} Home Assistant entities");
Logger.d("Parsing ${rawData.length} Home Assistant entities");
rawData.forEach((rawEntityData) {
addFromRaw(rawEntityData);
});
@ -44,7 +44,13 @@ class EntityCollection {
case 'sensor': {
return SensorEntity(rawEntityData);
}
case "automation":
case 'lock': {
return LockEntity(rawEntityData);
}
case "automation": {
return AutomationEntity(rawEntityData);
}
case "input_boolean":
case "switch": {
return SwitchEntity(rawEntityData);
@ -52,6 +58,9 @@ class EntityCollection {
case "light": {
return LightEntity(rawEntityData);
}
case "group": {
return GroupEntity(rawEntityData);
}
case "script":
case "scene": {
return ButtonEntity(rawEntityData);
@ -74,17 +83,28 @@ class EntityCollection {
case "cover": {
return CoverEntity(rawEntityData);
}
case "fan": {
return FanEntity(rawEntityData);
}
/*case "camera": {
return CameraEntity(rawEntityData);
}*/
case "alarm_control_panel": {
return AlarmControlPanelEntity(rawEntityData);
}
default: {
return Entity(rawEntityData);
}
}
}
void updateState(Map rawStateData) {
bool updateState(Map rawStateData) {
if (isExist(rawStateData["entity_id"])) {
updateFromRaw(rawStateData["new_state"] ?? rawStateData["old_state"]);
return false;
} else {
addFromRaw(rawStateData["new_state"] ?? rawStateData["old_state"]);
return true;
}
}
@ -92,10 +112,9 @@ class EntityCollection {
_allEntities[entity.entityId] = entity;
}
Entity addFromRaw(Map rawEntityData) {
void addFromRaw(Map rawEntityData) {
Entity entity = _createEntityInstance(rawEntityData);
_allEntities[entity.entityId] = entity;
return entity;
}
void updateFromRaw(Map rawEntityData) {
@ -126,7 +145,7 @@ class EntityCollection {
List<Entity> groups = [];
List<Entity> nonGroupEntities = [];
_allEntities.forEach((id, entity){
if ((id.indexOf("group.") == 0) && (id.indexOf(".all_") == -1) && (!entity.isView)) {
if (entity.isGroup && (entity.attributes['auto'] == null || (entity.attributes['auto'] && !entity.isHidden)) && (!entity.isView)) {
groups.add(entity);
}
if (!entity.isGroup) {

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

@ -0,0 +1,40 @@
part of '../../main.dart';
class FlatServiceButton extends StatelessWidget {
final String serviceDomain;
final String serviceName;
final String text;
final double fontSize;
FlatServiceButton({
Key key,
this.serviceDomain,
this.serviceName: "turn_on",
@required this.text,
this.fontSize: Sizes.stateFontSize
}) : super(key: key);
void _setNewState(Entity entity) {
eventBus.fire(new ServiceCallEvent(serviceDomain ?? entity.domain, serviceName, entity.entityId, null));
}
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
return SizedBox(
height: fontSize*2.5,
child: FlatButton(
onPressed: (() {
_setNewState(entityModel.entityWrapper.entity);
}),
child: Text(
text,
textAlign: TextAlign.right,
style:
new TextStyle(fontSize: fontSize, color: Colors.blue),
),
)
);
}
}

View File

@ -12,7 +12,7 @@ class ModeSelectorWidget extends StatelessWidget {
ModeSelectorWidget({
Key key,
this.caption,
@required this.caption,
@required this.options,
this.value,
@required this.onChange,

View File

@ -6,27 +6,22 @@ class ModeSwitchWidget extends StatelessWidget {
final onChange;
final double captionFontSize;
final bool value;
final bool expanded;
ModeSwitchWidget({
Key key,
@required this.caption,
@required this.onChange,
this.captionFontSize,
this.value
this.value,
this.expanded: true
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
child: Text(
"$caption",
style: TextStyle(
fontSize: captionFontSize ?? Sizes.stateFontSize
),
),
),
_buildCaption(),
Switch(
onChanged: (value) => onChange(value),
value: value ?? false,
@ -35,4 +30,19 @@ class ModeSwitchWidget extends StatelessWidget {
);
}
Widget _buildCaption() {
Widget captionWidget = Text(
"$caption",
style: TextStyle(
fontSize: captionFontSize ?? Sizes.stateFontSize
),
);
if (expanded) {
return Expanded(
child: captionWidget,
);
}
return captionWidget;
}
}

View File

@ -0,0 +1,244 @@
part of '../../main.dart';
class AlarmControlPanelControlsWidget extends StatefulWidget {
final bool extended;
final List states;
const AlarmControlPanelControlsWidget({Key key, @required this.extended, this.states: const ["arm_home", "arm_away"]}) : super(key: key);
@override
_AlarmControlPanelControlsWidgetWidgetState createState() => _AlarmControlPanelControlsWidgetWidgetState();
}
class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPanelControlsWidget> {
String code = "";
void _callService(AlarmControlPanelEntity entity, String service) {
eventBus.fire(new ServiceCallEvent(
entity.domain, service, entity.entityId,
{"code": "$code"}));
setState(() {
code = "";
});
}
void _pinPadHandler(value) {
setState(() {
code += "$value";
});
}
void _pinPadClear() {
setState(() {
code = "";
});
}
void _askToTrigger(AlarmControlPanelEntity entity) {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Are you sure?"),
content: new Text("Are you sure want to trigger alarm ${entity.displayName}?"),
actions: <Widget>[
FlatButton(
child: new Text("Yes"),
onPressed: () {
eventBus.fire(new ServiceCallEvent(entity.domain, "alarm_trigger", entity.entityId, null));
Navigator.of(context).pop();
},
),
FlatButton(
child: new Text("No"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
final AlarmControlPanelEntity entity = entityModel.entityWrapper.entity;
List<Widget> buttons = [];
if (entity.state == EntityState.alarm_disarmed) {
if (widget.states.contains("arm_home")) {
buttons.add(
RaisedButton(
onPressed: () => _callService(entity, "alarm_arm_home"),
child: Text("ARM HOME"),
)
);
}
if (widget.states.contains("arm_away")) {
buttons.add(
RaisedButton(
onPressed: () => _callService(entity, "alarm_arm_away"),
child: Text("ARM AWAY"),
)
);
}
if (widget.extended) {
if (widget.states.contains("arm_night")) {
buttons.add(
RaisedButton(
onPressed: () => _callService(entity, "alarm_arm_night"),
child: Text("ARM NIGHT"),
)
);
}
if (widget.states.contains("arm_custom_bypass")) {
buttons.add(
RaisedButton(
onPressed: () =>
_callService(entity, "alarm_arm_custom_bypass"),
child: Text("ARM CUSTOM BYPASS"),
)
);
}
}
} else {
buttons.add(
RaisedButton(
onPressed: () => _callService(entity, "alarm_disarm"),
child: Text("DISARM"),
)
);
}
Widget pinPad = Padding(
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Wrap(
spacing: 5.0,
children: <Widget>[
RaisedButton(
onPressed: () => _pinPadHandler("1"),
child: Text("1"),
),
RaisedButton(
onPressed: () => _pinPadHandler("2"),
child: Text("2"),
),
RaisedButton(
onPressed: () => _pinPadHandler("3"),
child: Text("3"),
)
],
),
Wrap(
spacing: 5.0,
children: <Widget>[
RaisedButton(
onPressed: () => _pinPadHandler("4"),
child: Text("4"),
),
RaisedButton(
onPressed: () => _pinPadHandler("5"),
child: Text("5"),
),
RaisedButton(
onPressed: () => _pinPadHandler("6"),
child: Text("6"),
)
],
),
Wrap(
spacing: 5.0,
children: <Widget>[
RaisedButton(
onPressed: () => _pinPadHandler("7"),
child: Text("7"),
),
RaisedButton(
onPressed: () => _pinPadHandler("8"),
child: Text("8"),
),
RaisedButton(
onPressed: () => _pinPadHandler("9"),
child: Text("9"),
)
],
),
Wrap(
spacing: 5.0,
alignment: WrapAlignment.end,
children: <Widget>[
RaisedButton(
onPressed: () => _pinPadHandler("0"),
child: Text("0"),
),
RaisedButton(
onPressed: () => _pinPadClear(),
child: Text("CLEAR"),
)
],
)
],
)
);
Widget inputWrapper = Container(
width: 150.0,
child: TextField(
decoration: InputDecoration(
labelText: "Alarm Code"
),
//focusNode: _focusNode,
obscureText: true,
controller: new TextEditingController.fromValue(
new TextEditingValue(
text: code,
selection:
new TextSelection.collapsed(offset: code.length)
)
),
onChanged: (value) {
code = value;
}
)
);
Widget buttonsWrapper = Padding(
padding: EdgeInsets.symmetric(vertical: Sizes.rowPadding),
child: Wrap(
alignment: WrapAlignment.center,
spacing: 15.0,
runSpacing: Sizes.rowPadding,
children: buttons
)
);
Widget triggerButton = Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FlatButton(
child: Text(
"TRIGGER",
style: TextStyle(color: Colors.redAccent)
),
onPressed: () => _askToTrigger(entity),
)
]
);
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
widget.extended ? buttonsWrapper : inputWrapper,
widget.extended ? inputWrapper : buttonsWrapper,
widget.extended ? pinPad : triggerButton
]
);
}
}

View File

@ -0,0 +1,134 @@
part of '../../main.dart';
class CameraControlsWidget extends StatefulWidget {
final String url;
CameraControlsWidget({Key key, @required this.url}) : super(key: key);
@override
_CameraControlsWidgetState createState() => _CameraControlsWidgetState();
}
class _CameraControlsWidgetState extends State<CameraControlsWidget> {
@override
void initState() {
super.initState();
Logger.d("Camera source: ${widget.url}");
_getData();
}
http.Client client;
http.StreamedResponse response;
List<int> binaryImage = [];
void _getData() async {
client = new http.Client(); // create a client to make api calls
http.Request request = new http.Request("GET", Uri.parse(widget.url)); // create get request
//Log.d
Logger.d("==Sending");
response = await client.send(request); // sends request and waits for response stream
Logger.d("==Reading");
int byteCount = 0;
Logger.d("==${response.headers}");
List<int> primaryBuffer=[];
List<int> secondaryBuffer=[];
int imageStart = 0;
int imageEnd = 0;
response.stream.transform(
StreamTransformer.fromHandlers(
handleData: (data, sink) {
primaryBuffer.addAll(data);
Logger.d("== data recived: ${data.length}");
Logger.d("== primary buffer size: ${primaryBuffer.length}");
//Logger.d("${data.toString()}");
for (int i = 0; i < primaryBuffer.length - 15; i++) {
String startBoundary = utf8.decode(primaryBuffer.sublist(i, i+15),allowMalformed: true);
if (startBoundary == "--frameboundary") {
Logger.d("== START found at $i");
imageStart = i;
//secondaryBuffer.addAll(primaryBuffer.sublist(i));
//Logger.d("== secondary.length=${secondaryBuffer.length}. clearinig primary");
//primaryBuffer.clear();
break;
}
/*String startBoundary = utf8.decode(primaryBuffer.sublist(i, i+4),allowMalformed: true);
if (startBoundary == "\r\n\r\n") {
Logger.d("==Binary image start found ($i). primary.length=${primaryBuffer.length}");
secondaryBuffer.addAll(primaryBuffer.sublist(i+5));
Logger.d("==secondary.length=${secondaryBuffer.length}. clearinig primary");
primaryBuffer.clear();
Logger.d("==secondary.length=${secondaryBuffer.length}");
for (int j = 0; j < secondaryBuffer.length - 15; j++) {
String endBoundary = utf8.decode(secondaryBuffer.sublist(j, j+15),allowMalformed: true);
if (endBoundary == "--frameboundary") {
Logger.d("==Binary image end found");
sink.add(secondaryBuffer.sublist(0, j-1));
primaryBuffer.addAll(secondaryBuffer.sublist(j));
secondaryBuffer.clear();
break;
}
}
break;
}*/
}
for (int i = imageStart+15; i < primaryBuffer.length - 15; i++) {
String endBoundary = utf8.decode(primaryBuffer.sublist(i, i+15),allowMalformed: true);
if (endBoundary == "--frameboundary") {
Logger.d("==END found");
imageEnd = i;
sink.add(primaryBuffer.sublist(imageStart, imageEnd - 1));
primaryBuffer = primaryBuffer.sublist(imageEnd);
break;
}
}
//byteCount += data.length;
//Logger.d("$byteCount");
},
handleError: (error, stack, sink) {},
handleDone: (sink) {
sink.close();
},
)
).listen((d) {
setState(() {
binaryImage = d;
});
//Logger.d("==binary imagesize=${d.length}");
});
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Image.memory(Uint8List.fromList(binaryImage)),
FlatButton(
child: Text("VIEW"),
onPressed: () {
setState(() {
});
},
)
],
);
return Image.network("${widget.url}");
return FlatButton(
child: Text("VIEW"),
onPressed: () {
HAUtils.launchURL(widget.url);
},
);
}
@override
void dispose() {
client.close();
super.dispose();
}
}

View File

@ -13,6 +13,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
bool _showPending = false;
bool _changedHere = false;
Timer _resetTimer;
Timer _tempThrottleTimer;
Timer _targetTempThrottleTimer;
double _tmpTemperature = 0.0;
double _tmpTargetLow = 0.0;
double _tmpTargetHigh = 0.0;
@ -71,21 +73,37 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
}
void _setTemperature(ClimateEntity entity) {
if (_tempThrottleTimer!=null) {
_tempThrottleTimer.cancel();
}
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);
_tmpTemperature = double.parse(_tmpTemperature.toStringAsFixed(1));
});
_tempThrottleTimer = Timer(Duration(seconds: 2), () {
setState(() {
_changedHere = true;
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"temperature": "${_tmpTemperature.toStringAsFixed(1)}"}));
_resetStateTimer(entity);
});
});
}
void _setTargetTemp(ClimateEntity entity) {
if (_targetTempThrottleTimer!=null) {
_targetTempThrottleTimer.cancel();
}
setState(() {
_changedHere = true;
_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);
});
_targetTempThrottleTimer = Timer(Duration(seconds: 2), () {
setState(() {
_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);
});
});
}
@ -167,7 +185,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
final entityModel = EntityModel.of(context);
final ClimateEntity entity = entityModel.entityWrapper.entity;
if (_changedHere) {
_showPending = (_tmpTemperature != entity.temperature);
_showPending = (_tmpTemperature != entity.temperature || _tmpTargetHigh != entity.targetHigh || _tmpTargetLow != entity.targetLow);
_changedHere = false;
} else {
_resetTimer?.cancel();
@ -278,10 +296,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
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),
onDec: () => _temperatureDown(entity, 0.5),
onInc: () => _temperatureUp(entity, 0.5),
)
],
);
@ -297,10 +313,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
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),
onDec: () => _targetLowDown(entity, 0.5),
onInc: () => _targetLowUp(entity, 0.5),
),
Expanded(
child: Container(height: 10.0),
@ -312,10 +326,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
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),
onDec: () => _targetHighDown(entity, 0.5),
onInc: () => _targetHighUp(entity, 0.5),
)
);
}
@ -401,18 +413,14 @@ class TemperatureControlWidget extends StatelessWidget {
final double value;
final double fontSize;
final Color fontColor;
final onSmallInc;
final onLargeInc;
final onSmallDec;
final onLargeDec;
final onInc;
final onDec;
TemperatureControlWidget(
{Key key,
@required this.value,
@required this.onSmallInc,
@required this.onSmallDec,
@required this.onLargeInc,
@required this.onLargeDec,
@required this.onInc,
@required this.onDec,
this.fontSize,
this.fontColor})
: super(key: key);
@ -435,29 +443,13 @@ class TemperatureControlWidget extends StatelessWidget {
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
'mdi:chevron-up')),
iconSize: 30.0,
onPressed: () => onSmallInc(),
onPressed: () => onInc(),
),
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(),
onPressed: () => onDec(),
)
],
)

View File

@ -0,0 +1,123 @@
part of '../../main.dart';
class FanControlsWidget extends StatefulWidget {
@override
_FanControlsWidgetState createState() => _FanControlsWidgetState();
}
class _FanControlsWidgetState extends State<FanControlsWidget> {
bool _tmpOscillate;
bool _tmpDirectionForward;
bool _changedHere = false;
String _tmpSpeed;
void _resetState(FanEntity entity) {
_tmpOscillate = entity.attributes["oscillating"] ?? false;
_tmpDirectionForward = entity.attributes["direction"] == "forward";
_tmpSpeed = entity.attributes["speed"];
}
void _setOscillate(FanEntity entity, bool oscillate) {
setState(() {
_tmpOscillate = oscillate;
_changedHere = true;
eventBus.fire(new ServiceCallEvent(
"fan", "oscillate", entity.entityId,
{"oscillating": oscillate}));
});
}
void _setDirection(FanEntity entity, bool forward) {
setState(() {
_tmpDirectionForward = forward;
_changedHere = true;
eventBus.fire(new ServiceCallEvent(
"fan", "set_direction", entity.entityId,
{"direction": forward ? "forward" : "reverse"}));
});
}
void _setSpeed(FanEntity entity, String value) {
setState(() {
_tmpSpeed = value;
_changedHere = true;
eventBus.fire(new ServiceCallEvent(
"fan", "set_speed", entity.entityId,
{"speed": value}));
});
}
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
final FanEntity entity = entityModel.entityWrapper.entity;
if (!_changedHere) {
_resetState(entity);
} else {
_changedHere = false;
}
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
_buildSpeedControl(entity),
_buildOscillateControl(entity),
_buildDirectionControl(entity)
],
);
}
Widget _buildSpeedControl(FanEntity entity) {
if (entity.supportSetSpeed && entity.speedList != null && entity.speedList.isNotEmpty) {
return ModeSelectorWidget(
onChange: (effect) => _setSpeed(entity, effect),
caption: "Speed",
options: entity.speedList,
value: _tmpSpeed
);
} else {
return Container(width: 0.0, height: 0.0);
}
}
Widget _buildOscillateControl(FanEntity entity) {
if (entity.supportOscillate) {
return ModeSwitchWidget(
onChange: (value) => _setOscillate(entity, value),
caption: "Oscillate",
value: _tmpOscillate,
expanded: false,
);
} else {
return Container(width: 0.0, height: 0.0);
}
}
Widget _buildDirectionControl(FanEntity entity) {
if (entity.supportDirection) {
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
onPressed: _tmpDirectionForward ?
() => _setDirection(entity, false) :
null,
icon: Icon(Icons.rotate_left),
),
IconButton(
onPressed: !_tmpDirectionForward ?
() => _setDirection(entity, true) :
null,
icon: Icon(Icons.rotate_right),
),
],
);
} else {
return Container(width: 0.0, height: 0.0);
}
}
}

View File

@ -10,16 +10,16 @@ class LightControlsWidget extends StatefulWidget {
class _LightControlsWidgetState extends State<LightControlsWidget> {
int _tmpBrightness;
int _tmpColorTemp;
Color _tmpColor;
int _tmpColorTemp = 0;
Color _tmpColor = Colors.white;
bool _changedHere = false;
String _tmpEffect;
void _resetState(LightEntity entity) {
_tmpBrightness = entity.brightness ?? 0;
_tmpColorTemp = entity.colorTemp;
_tmpColor = entity.color;
_tmpEffect = null;
_tmpColorTemp = entity.colorTemp ?? entity.minMireds?.toInt();
_tmpColor = entity.color ?? _tmpColor;
_tmpEffect = entity.effect;
}
void _setBrightness(LightEntity entity, double value) {
@ -52,7 +52,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
setState(() {
_tmpColor = color;
_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))) {
eventBus.fire(new ServiceCallEvent(
entity.domain, "turn_off", entity.entityId,
@ -119,7 +119,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
}
Widget _buildColorTempControl(LightEntity entity) {
if ((entity.supportColorTemp) && (_tmpColorTemp != null)) {
if (entity.supportColorTemp) {
return UniversalSlider(
title: "Color temperature",
leading: Text("Cold", style: TextStyle(color: Colors.lightBlue),),
@ -141,7 +141,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
}
Widget _buildColorControl(LightEntity entity) {
if ((entity.supportColor) && (entity.color != null)) {
if (entity.supportColor) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[

View File

@ -79,11 +79,13 @@ class MediaPlayerWidget extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image(
image: CachedNetworkImageProvider("$homeAssistantWebHost${entity.entityPicture}"),
height: 240.0,
width: 320.0,
fit: BoxFit.contain,
Flexible(
child: Image(
image: CachedNetworkImageProvider("$homeAssistantWebHost${entity.entityPicture}"),
height: 240.0,
//width: 320.0,
fit: BoxFit.contain,
),
)
],
),
@ -118,12 +120,12 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
void _setPower(MediaPlayerEntity entity) {
if (entity.state != EntityState.unavailable && entity.state != EntityState.unknown) {
if (entity.state == EntityState.off) {
TheLogger.debug("${entity.entityId} turn_on");
Logger.d("${entity.entityId} turn_on");
eventBus.fire(new ServiceCallEvent(
entity.domain, "turn_on", entity.entityId,
null));
} else {
TheLogger.debug("${entity.entityId} turn_off");
Logger.d("${entity.entityId} turn_off");
eventBus.fire(new ServiceCallEvent(
entity.domain, "turn_off", entity.entityId,
null));
@ -132,7 +134,7 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
}
void _callAction(MediaPlayerEntity entity, String action) {
TheLogger.debug("${entity.entityId} $action");
Logger.d("${entity.entityId} $action");
eventBus.fire(new ServiceCallEvent(
entity.domain, "$action", entity.entityId,
null));
@ -255,6 +257,8 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
double _newVolumeLevel;
bool _changedHere = false;
String _newSoundMode;
String _newSource;
void _setVolume(double value, String entityId) {
setState(() {
@ -276,6 +280,22 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
eventBus.fire(ServiceCallEvent("media_player", "volume_down", entityId, null));
}
void _setSoundMode(String value, String entityId) {
setState(() {
_newSoundMode = value;
_changedHere = true;
eventBus.fire(ServiceCallEvent("media_player", "select_sound_mode", entityId, {"sound_mode": "$value"}));
});
}
void _setSource(String source, String entityId) {
setState(() {
_newSource = source;
_changedHere = true;
eventBus.fire(ServiceCallEvent("media_player", "select_source", entityId, {"source": "$source"}));
});
}
@override
Widget build(BuildContext context) {
final MediaPlayerEntity entity = EntityModel.of(context).entityWrapper.entity;
@ -347,6 +367,38 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
));
}
if (entity.supportSelectSoundMode && entity.soundModeList != null) {
if (!_changedHere) {
_newSoundMode = entity.attributes["sound_mode"];
} else {
_changedHere = false;
}
children.add(
ModeSelectorWidget(
options: entity.soundModeList,
caption: "Sound mode",
value: _newSoundMode,
onChange: (value) => _setSoundMode(value, entity.entityId)
)
);
}
if (entity.supportSelectSource && entity.sourceList != null) {
if (!_changedHere) {
_newSource = entity.attributes["source"];
} else {
_changedHere = false;
}
children.add(
ModeSelectorWidget(
options: entity.sourceList,
caption: "Source",
value: _newSource,
onChange: (value) => _setSource(value, entity.entityId)
)
);
}
}
return Column(
children: children,

View File

@ -10,15 +10,31 @@ class DefaultEntityContainer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
EntityIcon(),
Expanded(
child: EntityName(),
),
state
],
final EntityModel entityModel = EntityModel.of(context);
return InkWell(
onLongPress: () {
if (entityModel.handleTap) {
entityModel.entityWrapper.handleHold();
}
},
onTap: () {
if (entityModel.handleTap) {
entityModel.entityWrapper.handleTap();
}
},
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
EntityIcon(),
Flexible(
fit: FlexFit.tight,
flex: 3,
child: EntityName(),
),
state
],
),
);
}
}

View File

@ -23,6 +23,15 @@ class EntityColor {
"cool": Colors.lightBlue,
EntityState.unavailable: Colors.black26,
EntityState.unknown: Colors.black26,
EntityState.alarm_disarmed: Colors.green,
EntityState.alarm_armed_away: Colors.redAccent,
EntityState.alarm_armed_custom_bypass: Colors.redAccent,
EntityState.alarm_armed_home: Colors.redAccent,
EntityState.alarm_armed_night: Colors.redAccent,
EntityState.alarm_triggered: Colors.redAccent,
EntityState.alarm_arming: Colors.amber,
EntityState.alarm_disarming: Colors.amber,
EntityState.alarm_pending: Colors.amber,
};
static Color stateColor(String state) {

View File

@ -10,59 +10,14 @@ class EntityIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
return GestureDetector(
child: Padding(
padding: padding,
child: MaterialDesignIcons.createIconWidgetFromEntityData(
entityModel.entityWrapper,
iconSize,
EntityColor.stateColor(entityModel.entityWrapper.entity.state)
),
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
return Padding(
padding: padding,
child: MaterialDesignIcons.createIconWidgetFromEntityData(
entityWrapper,
iconSize,
EntityColor.stateColor(entityWrapper.entity.state)
),
onLongPress: () {
if (entityModel.handleTap) {
switch (entityModel.entityWrapper.holdAction) {
case EntityTapAction.toggle: {
eventBus.fire(
ServiceCallEvent("homeassistant", "toggle", entityModel.entityWrapper.entity.entityId, null));
break;
}
default: {
eventBus.fire(
new ShowEntityPageEvent(entityModel.entityWrapper.entity));
break;
}
}
}
},
onTap: () {
if (entityModel.handleTap) {
switch (entityModel.entityWrapper.tapAction) {
case EntityTapAction.toggle: {
eventBus.fire(
ServiceCallEvent("homeassistant", "toggle", entityModel.entityWrapper.entity.entityId, null));
break;
}
case EntityTapAction.callService: {
eventBus.fire(
ServiceCallEvent(entityModel.entityWrapper.actionService.split(".")[0], entityModel.entityWrapper.actionService.split(".")[1], null, entityModel.entityWrapper.actionServiceData));
break;
}
default: {
eventBus.fire(
new ShowEntityPageEvent(entityModel.entityWrapper.entity));
break;
}
}
}
}
);
}
}

View File

@ -7,27 +7,23 @@ class EntityName extends StatelessWidget {
final bool wordsWrap;
final double fontSize;
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
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
return GestureDetector(
child: Padding(
padding: padding,
child: Text(
"${entityModel.entityWrapper.displayName}",
overflow: textOverflow,
softWrap: wordsWrap,
style: TextStyle(fontSize: fontSize),
textAlign: textAlign,
),
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
return Padding(
padding: padding,
child: Text(
"${entityWrapper.displayName}",
overflow: textOverflow,
softWrap: wordsWrap,
maxLines: maxLines,
style: TextStyle(fontSize: fontSize),
textAlign: textAlign,
),
onTap: () =>
entityModel.handleTap
? eventBus.fire(new ShowEntityPageEvent(entityModel.entityWrapper.entity))
: null,
);
}
}

View File

@ -4,39 +4,81 @@ class GlanceEntityContainer extends StatelessWidget {
final bool showName;
final bool showState;
final bool nameInTheBottom;
final double iconSize;
final double nameFontSize;
final bool wordsWrapInName;
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);
@override
Widget build(BuildContext context) {
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
List<Widget> result = [];
if (showName) {
result.add(EntityName(
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
textOverflow: TextOverflow.ellipsis,
wordsWrap: false,
textAlign: TextAlign.center,
fontSize: Sizes.smallFontSize,
));
if (!nameInTheBottom) {
if (showName) {
result.add(_buildName());
}
} else {
if (showState) {
result.add(_buildState());
}
}
result.add(EntityIcon(
padding: EdgeInsets.all(0.0),
iconSize: Sizes.largeIconSize,
));
if (showState) {
result.add(SimpleEntityState(
textAlign: TextAlign.center,
expanded: false,
padding: EdgeInsets.only(top: Sizes.rowPadding),
));
result.add(
EntityIcon(
padding: EdgeInsets.all(0.0),
iconSize: iconSize,
)
);
if (!nameInTheBottom) {
if (showState) {
result.add(_buildState());
}
} else {
result.add(_buildName());
}
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: result,
return Center(
child: InkResponse(
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: Sizes.iconSize * 2),
child: Column(
mainAxisSize: MainAxisSize.min,
//mainAxisAlignment: MainAxisAlignment.start,
//crossAxisAlignment: CrossAxisAlignment.center,
children: result,
),
),
onTap: () => entityWrapper.handleTap(),
onLongPress: () => entityWrapper.handleHold(),
),
);
}
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() {
TheLogger.debug(" parsing history...");
Logger.d(" parsing history...");
Map<String, List<EntityHistoryMoment>> numericDataLists = {};
int colorIdCounter = 0;
widget.config.numericAttributesToShow.forEach((String attrName) {
TheLogger.debug(" parsing attribute $attrName");
Logger.d(" parsing attribute $attrName");
List<EntityHistoryMoment> data = [];
DateTime now = DateTime.now();
for (var i = 0; i < widget.rawHistory.length; i++) {
@ -152,7 +152,7 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
}
List<charts.Series<EntityHistoryMoment, DateTime>> result = [];
numericDataLists.forEach((attrName, dataList) {
TheLogger.debug(" adding ${dataList.length} data values");
Logger.d(" adding ${dataList.length} data values");
result.add(
new charts.Series<EntityHistoryMoment, DateTime>(
id: "value",

View File

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

View File

@ -1,27 +0,0 @@
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 SizedBox(
height: 34.0,
child: FlatButton(
onPressed: (() {
_setNewState(entityModel.entityWrapper.entity);
}),
child: Text(
"EXECUTE",
textAlign: TextAlign.right,
style:
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
),
)
);
}
}

View File

@ -19,39 +19,34 @@ class ClimateStateWidget extends StatelessWidget {
return Padding(
padding: EdgeInsets.fromLTRB(
0.0, 0.0, Sizes.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: Sizes.stateFontSize,
)),
Text(" $targetTemp",
textAlign: TextAlign.right,
style: new TextStyle(
fontSize: Sizes.stateFontSize,
))
],
),
entity.attributes["current_temperature"] != null ?
Text("Currently: ${entity.attributes["current_temperature"]}",
textAlign: TextAlign.right,
style: new TextStyle(
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: Sizes.stateFontSize,
color: Colors.black45)
) :
Container(height: 0.0,)
],
),
onTap: () => entityModel.handleTap
? eventBus.fire(new ShowEntityPageEvent(entity))
: null,
)),
Text(" $targetTemp",
textAlign: TextAlign.right,
style: new TextStyle(
fontSize: Sizes.stateFontSize,
))
],
),
entity.attributes["current_temperature"] != null ?
Text("Currently: ${entity.attributes["current_temperature"]}",
textAlign: TextAlign.right,
style: new TextStyle(
fontSize: Sizes.stateFontSize,
color: Colors.black45)
) :
Container(height: 0.0,)
],
));
}
}

View File

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

View File

@ -0,0 +1,32 @@
part of '../../main.dart';
class LockStateWidget extends StatelessWidget {
void _lock(Entity entity) {
eventBus.fire(new ServiceCallEvent("lock", "lock", entity.entityId, null));
}
void _unlock(Entity entity) {
eventBus.fire(new ServiceCallEvent("lock", "unlock", entity.entityId, null));
}
@override
Widget build(BuildContext context) {
final entityModel = EntityModel.of(context);
final LockEntity entity = entityModel.entityWrapper.entity;
return SizedBox(
height: 34.0,
child: FlatButton(
onPressed: (() {
entity.isLocked ? _unlock(entity) : _lock(entity);
}),
child: Text(
entity.isLocked ? "UNLOCK" : "LOCK",
textAlign: TextAlign.right,
style:
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
),
)
);
}
}

View File

@ -23,6 +23,7 @@ class _SelectStateWidgetState extends State<SelectStateWidget> {
if (entity.listOptions.isNotEmpty) {
ctrl = DropdownButton<String>(
value: entity.state,
isExpanded: true,
items: entity.listOptions.map((String value) {
return new DropdownMenuItem<String>(
value: value,
@ -36,7 +37,9 @@ class _SelectStateWidgetState extends State<SelectStateWidget> {
} else {
ctrl = Text('---');
}
return Expanded(
return Flexible(
flex: 2,
fit: FlexFit.tight,
//width: Entity.INPUT_WIDTH,
child: ctrl,
);

View File

@ -5,32 +5,35 @@ class SimpleEntityState extends StatelessWidget {
final bool expanded;
final TextAlign textAlign;
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
Widget build(BuildContext 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(
padding: padding,
child: GestureDetector(
child: Text(
"${entityModel.entityWrapper.entity.state}${entityModel.entityWrapper.entity.unitOfMeasurement}",
textAlign: textAlign,
maxLines: 4,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: new TextStyle(
fontSize: Sizes.stateFontSize,
)),
onTap: () => entityModel.handleTap
? eventBus.fire(new ShowEntityPageEvent(entityModel.entityWrapper.entity))
: null,
padding: padding,
child: Text(
"$state ${entityModel.entityWrapper.entity.unitOfMeasurement}",
textAlign: textAlign,
maxLines: maxLines,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: new TextStyle(
fontSize: Sizes.stateFontSize,
)
)
);
if (expanded) {
return SizedBox(
width: MediaQuery.of(context).size.width * 0.3,
return Flexible(
fit: FlexFit.tight,
flex: 2,
child: result,
);
} else {

View File

@ -1,6 +1,11 @@
part of '../../main.dart';
class SwitchStateWidget extends StatefulWidget {
final String domainForService;
const SwitchStateWidget({Key key, this.domainForService}) : super(key: key);
@override
_SwitchStateWidgetState createState() => _SwitchStateWidgetState();
}
@ -24,11 +29,17 @@ class _SwitchStateWidgetState extends State<SwitchStateWidget> {
setState(() {
newState = entity.state;
updatedHere = true;
TheLogger.debug("Timer@!!");
//TheLogger.debug("Timer@!!");
});
});
String domain;
if (widget.domainForService != null) {
domain = widget.domainForService;
} else {
domain = entity.domain;
}
eventBus.fire(new ServiceCallEvent(
entity.domain, (newValue as bool) ? "turn_on" : "turn_off", entity.entityId, null));
domain, (newValue as bool) ? "turn_on" : "turn_off", entity.entityId, null));
}
@override

View File

@ -66,7 +66,9 @@ class _TextInputStateWidgetState extends State<TextInputStateWidget> {
_tmpValue = entity.state;
}
if (entity.isTextField || entity.isPasswordField) {
return Expanded(
return Flexible(
fit: FlexFit.tight,
flex: 2,
//width: Entity.INPUT_WIDTH,
child: TextField(
focusNode: _focusNode,
@ -83,7 +85,7 @@ class _TextInputStateWidgetState extends State<TextInputStateWidget> {
}),
);
} else {
TheLogger.warning( "Unsupported input mode for ${entity.entityId}");
Logger.w( "Unsupported input mode for ${entity.entityId}");
return SimpleEntityState();
}
}

View File

@ -3,7 +3,6 @@ part of 'main.dart';
class HomeAssistant {
String _webSocketAPIEndpoint;
String _password;
String _authType;
bool _useLovelace = false;
IOWebSocketChannel _hassioChannel;
@ -56,21 +55,20 @@ class HomeAssistant {
_messageQueue = SendMessageQueue(messageExpirationTime);
}
void updateSettings(String url, String password, String authType, bool useLovelace) {
void updateSettings(String url, String password, bool useLovelace) {
_webSocketAPIEndpoint = url;
_password = password;
_authType = authType;
_useLovelace = useLovelace;
TheLogger.debug( "Use lovelace is $_useLovelace");
Logger.d( "Use lovelace is $_useLovelace");
}
Future fetch() {
if ((_fetchCompleter != null) && (!_fetchCompleter.isCompleted)) {
TheLogger.warning("Previous fetch is not complited");
Logger.w("Previous fetch is not complited");
} else {
_fetchCompleter = new Completer();
_fetchTimer = Timer(fetchTimeout, () {
TheLogger.error( "Data fetching timeout");
Logger.e( "Data fetching timeout");
disconnect().then((_) {
_completeFetching({
"errorCode": 9,
@ -90,7 +88,7 @@ class HomeAssistant {
disconnect() async {
if ((_hassioChannel != null) && (_hassioChannel.closeCode == null) && (_hassioChannel.sink != null)) {
await _hassioChannel.sink.close().timeout(Duration(seconds: 3),
onTimeout: () => TheLogger.debug( "Socket sink closed")
onTimeout: () => Logger.d( "Socket sink closed")
);
await _socketSubscription.cancel();
_hassioChannel = null;
@ -100,15 +98,15 @@ class HomeAssistant {
Future _connection() {
if ((_connectionCompleter != null) && (!_connectionCompleter.isCompleted)) {
TheLogger.debug("Previous connection is not complited");
Logger.d("Previous connection is not complited");
} else {
if ((_hassioChannel == null) || (_hassioChannel.closeCode != null)) {
_connectionCompleter = new Completer();
autoReconnect = false;
disconnect().then((_){
TheLogger.debug( "Socket connecting...");
Logger.d( "Socket connecting...");
_connectionTimer = Timer(connectTimeout, () {
TheLogger.error( "Socket connection timeout");
Logger.e( "Socket connection timeout");
_handleSocketError(null);
});
if (_socketSubscription != null) {
@ -131,15 +129,15 @@ class HomeAssistant {
}
void _handleSocketClose() {
TheLogger.debug("Socket disconnected. Automatic reconnect is $autoReconnect");
Logger.d("Socket disconnected. Automatic reconnect is $autoReconnect");
if (autoReconnect) {
_reconnect();
}
}
void _handleSocketError(e) {
TheLogger.error("Socket stream Error: $e");
TheLogger.debug("Automatic reconnect is $autoReconnect");
Logger.e("Socket stream Error: $e");
Logger.d("Automatic reconnect is $autoReconnect");
if (autoReconnect) {
_reconnect();
} else {
@ -186,7 +184,7 @@ class HomeAssistant {
_fetchCompleter.completeError(error);
} else {
autoReconnect = true;
TheLogger.debug( "Fetch complete successful");
Logger.d( "Fetch complete successful");
_fetchCompleter.complete();
}
}
@ -213,14 +211,14 @@ class HomeAssistant {
_handleMessage(String message) {
var data = json.decode(message);
if (data["type"] == "auth_required") {
_sendAuthMessageRaw('{"type": "auth","$_authType": "$_password"}');
_sendAuthMessageRaw('{"type": "auth","access_token": "$_password"}');
} else if (data["type"] == "auth_ok") {
_completeConnecting(null);
_sendSubscribe();
} else if (data["type"] == "auth_invalid") {
_completeConnecting({"errorCode": 6, "errorMessage": "${data["message"]}"});
} 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) {
_parseConfig(data);
} else if (data["id"] == _statesMessageId) {
@ -234,15 +232,15 @@ class HomeAssistant {
}
} else if (data["type"] == "event") {
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"]);
} else if (data["event"] != null) {
TheLogger.warning("Unhandled event type: ${data["event"]["event_type"]}");
Logger.w("Unhandled event type: ${data["event"]["event_type"]}");
} else {
TheLogger.error("Event is null: $message");
Logger.e("Event is null: $message");
}
} else {
TheLogger.warning("Unknown message type: $message");
Logger.w("Unknown message type: $message");
}
}
@ -302,7 +300,7 @@ class HomeAssistant {
}
void _sendAuthMessageRaw(String message) {
TheLogger.debug( "[Sending] ==> auth request");
Logger.d( "[Sending] ==> auth request");
_hassioChannel.sink.add(message);
}
@ -311,11 +309,11 @@ class HomeAssistant {
if (queued) _messageQueue.add(message);
_connection().then((r) {
_messageQueue.getActualMessages().forEach((message){
TheLogger.debug( "[Sending queued] ==> $message");
Logger.d( "[Sending queued] ==> $message");
_hassioChannel.sink.add(message);
});
if (!queued) {
TheLogger.debug( "[Sending] ==> $message");
Logger.d( "[Sending] ==> $message");
_hassioChannel.sink.add(message);
}
sendCompleter.complete();
@ -367,8 +365,10 @@ class HomeAssistant {
void _handleEntityStateChange(Map eventData) {
//TheLogger.debug( "New state for ${eventData['entity_id']}");
Map data = Map.from(eventData);
entities.updateState(data);
eventBus.fire(new StateChangedEvent(data["entity_id"], null));
eventBus.fire(new StateChangedEvent(
entityId: data["entity_id"],
needToRebuildUI: entities.updateState(data)
));
}
void _parseConfig(Map data) {
@ -385,7 +385,7 @@ class HomeAssistant {
_userName = data["result"]["name"];
} else {
_userName = null;
TheLogger.warning("There was an error getting current user: $data");
Logger.w("There was an error getting current user: $data");
}
_userInfoCompleter.complete();
}
@ -398,25 +398,35 @@ class HomeAssistant {
if (response["success"] == true) {
_rawLovelaceData = response["result"];
} else {
TheLogger.error("There was an error getting Lovelace config: $response");
Logger.e("There was an error getting Lovelace config: $response");
_rawLovelaceData = null;
}
_lovelaceCompleter.complete();
}
void _parseLovelace() {
TheLogger.debug("--Title: ${_rawLovelaceData["title"]}");
Logger.d("--Title: ${_rawLovelaceData["title"]}");
ui.title = _rawLovelaceData["title"];
int viewCounter = 0;
TheLogger.debug("--Views count: ${_rawLovelaceData['views'].length}");
Logger.d("--Views count: ${_rawLovelaceData['views'].length}");
_rawLovelaceData["views"].forEach((rawView){
TheLogger.debug("----view id: ${rawView['id']}");
Logger.d("----view id: ${rawView['id']}");
HAView view = HAView(
count: viewCounter,
id: "${rawView['id']}",
name: rawView['title'],
iconName: rawView['icon']
);
if (rawView['badges'] != null && rawView['badges'] is List) {
rawView['badges'].forEach((entity) {
if (entities.isExist(entity)) {
Entity e = entities.get(entity);
view.badges.add(e);
}
});
}
view.cards.addAll(_createLovelaceCards(rawView["cards"] ?? []));
ui.views.add(
view
@ -428,17 +438,31 @@ class HomeAssistant {
List<HACard> _createLovelaceCards(List rawCards) {
List<HACard> result = [];
rawCards.forEach((rawCard){
if (rawCard["cards"] != null) {
result.addAll(_createLovelaceCards(rawCard["cards"]));
} else {
try {
bool isThereCardOptionsInside = rawCard["card"] != null;
HACard card = HACard(
id: "card",
name: rawCard["title"],
type: rawCard['type'],
columnsCount: rawCard['columns'] ?? 4,
showName: rawCard['show_name'] ?? true,
showState: rawCard['show_state'] ?? true,
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'] ?? [],
states: rawCard['states'],
content: rawCard['content']
);
if (rawCard["cards"] != null) {
card.childCards = _createLovelaceCards(rawCard["cards"]);
}
rawCard["entities"]?.forEach((rawEntity) {
if (rawEntity is String) {
if (entities.isExist(rawEntity)) {
@ -449,13 +473,10 @@ class HomeAssistant {
Entity e = entities.get(rawEntity["entity"]);
card.entities.add(
EntityWrapper(
entity: e,
displayName: rawEntity["name"],
icon: rawEntity["icon"],
tapAction: rawEntity["tap_action"] ?? EntityTapAction.moreInfo,
holdAction: rawEntity["hold_action"] ?? EntityTapAction.moreInfo,
actionService: rawEntity["service"],
actionServiceData: rawEntity["service_data"] ?? {"entity_id": e.entityId}
entity: e,
displayName: rawEntity["name"],
icon: rawEntity["icon"],
uiAction: EntityUIAction(rawEntityData: rawEntity)
)
);
}
@ -465,20 +486,29 @@ class HomeAssistant {
var en = rawCard["entity"];
if (en is String) {
if (entities.isExist(en)) {
card.linkedEntity = EntityWrapper(entity: entities.get(en));
Entity e = entities.get(en);
card.linkedEntityWrapper = EntityWrapper(
entity: e,
icon: rawCard["icon"],
displayName: rawCard["name"],
uiAction: EntityUIAction(rawEntityData: rawCard)
);
}
} else {
if (entities.isExist(en["entity"])) {
card.linkedEntity = EntityWrapper(
entity: entities.get(en["entity"]),
icon: en["icon"],
displayName: en["name"]
Entity e = entities.get(en["entity"]);
card.linkedEntityWrapper = EntityWrapper(
entity: e,
icon: en["icon"],
displayName: en["name"],
uiAction: EntityUIAction(rawEntityData: rawCard)
);
}
}
}
result.add(card);
} catch (e) {
Logger.e("There was an error parsing card: ${e.toString()}");
}
});
return result;
@ -496,10 +526,10 @@ class HomeAssistant {
void _createUI() {
ui = HomeAssistantUI();
if ((_useLovelace) && (_rawLovelaceData != null)) {
TheLogger.debug("Creating Lovelace UI");
Logger.d("Creating Lovelace UI");
_parseLovelace();
} else {
TheLogger.debug("Creating group-based UI");
Logger.d("Creating group-based UI");
int viewCounter = 0;
if (!entities.hasDefaultView) {
HAView view = HAView(
@ -538,22 +568,15 @@ class HomeAssistant {
//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 url = "$homeAssistantWebHost/api/history/period/$startTime?&filter_entity_id=$entityId";
TheLogger.debug("[Sending] ==> $url");
Logger.d("[Sending] ==> $url");
http.Response historyResponse;
if (_authType == "access_token") {
historyResponse = await http.get(url, headers: {
historyResponse = await http.get(url, headers: {
"authorization": "Bearer $_password",
"Content-Type": "application/json"
});
} else {
historyResponse = await http.get(url, headers: {
"X-HA-Access": "$_password",
"Content-Type": "application/json"
});
}
});
var history = json.decode(historyResponse.body);
if (history is List) {
TheLogger.debug( "[Received] <== ${history.first.length} history recors");
Logger.d( "[Received] <== ${history.first.length} history recors");
return history;
} else {
return [];

View File

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

View File

@ -1,10 +1,10 @@
import 'dart:convert';
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/rendering.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:web_socket_channel/io.dart';
import 'package:progress_indicators/progress_indicators.dart';
import 'package:event_bus/event_bus.dart';
import 'package:flutter/widgets.dart';
import 'package:cached_network_image/cached_network_image.dart';
@ -14,6 +14,8 @@ 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;
import 'package:progress_indicators/progress_indicators.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
part 'entity_class/const.dart';
part 'entity_class/entity.class.dart';
@ -29,10 +31,17 @@ 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_class/lock_entity.class.dart';
part 'entity_class/group_entity.class.dart';
part 'entity_class/fan_entity.class.dart';
part 'entity_class/automation_entity.dart';
part 'entity_class/camera_entity.class.dart';
part 'entity_class/alarm_control_panel.class.dart';
part 'entity_widgets/common/badge.dart';
part 'entity_widgets/model_widgets.dart';
part 'entity_widgets/default_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/entity_icon.dart';
part 'entity_widgets/entity_name.dart';
@ -40,6 +49,7 @@ part 'entity_widgets/common/last_updated.dart';
part 'entity_widgets/common/mode_swicth.dart';
part 'entity_widgets/common/mode_selector.dart';
part 'entity_widgets/common/universal_slider.dart';
part 'entity_widgets/common/flat_service_button.dart';
part 'entity_widgets/entity_colors.class.dart';
part 'entity_widgets/entity_page_container.dart';
part 'entity_widgets/history_chart/entity_history.dart';
@ -56,11 +66,14 @@ 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/state/lock_state.dart';
part 'entity_widgets/controls/climate_controls.dart';
part 'entity_widgets/controls/cover_controls.dart';
part 'entity_widgets/controls/light_controls.dart';
part 'entity_widgets/controls/media_player_widgets.dart';
part 'entity_widgets/controls/fan_controls.dart';
part 'entity_widgets/controls/alarm_control_panel_controls.dart';
part 'entity_widgets/controls/camera_controls.dart';
part 'settings.page.dart';
part 'home_assistant.class.dart';
part 'log.page.dart';
@ -73,23 +86,20 @@ part 'ui_class/view.class.dart';
part 'ui_class/card.class.dart';
part 'ui_class/sizes_class.dart';
part 'ui_widgets/view.dart';
part 'ui_widgets/entities_card.dart';
part 'ui_widgets/glance_card.dart';
part 'ui_widgets/unsupported_card.dart';
part 'ui_widgets/media_control_card.dart';
part 'ui_widgets/card_widget.dart';
part 'ui_widgets/card_header_widget.dart';
EventBus eventBus = new EventBus();
const String appName = "HA Client";
const appVersion = "0.3.9";
const appVersion = "0.3.14";
String homeAssistantWebHost;
void main() {
FlutterError.onError = (errorDetails) {
TheLogger.error( "${errorDetails.exception}");
if (TheLogger.isInDebugMode) {
Logger.e( "${errorDetails.exception}");
if (Logger.isInDebugMode) {
FlutterError.dumpErrorToConsole(errorDetails);
}
};
@ -97,9 +107,9 @@ void main() {
runZoned(() {
runApp(new HAClientApp());
}, onError: (error, stack) {
TheLogger.error("$error");
TheLogger.error("$stack");
if (TheLogger.isInDebugMode) {
Logger.e("$error");
Logger.e("$stack");
if (Logger.isInDebugMode) {
debugPrint("$stack");
}
});
@ -138,7 +148,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
//Map _instanceConfig;
String _webSocketApiEndpoint;
String _password;
String _authType;
//int _uiViewsCount = 0;
String _instanceHost;
StreamSubscription _stateSubscription;
@ -147,7 +156,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
StreamSubscription _showEntityPageSubscription;
StreamSubscription _refreshDataSubscription;
StreamSubscription _showErrorSubscription;
int _isLoading = 1;
bool _settingsLoaded = false;
bool _accountMenuExpanded = false;
bool _useLovelaceUI;
@ -158,10 +166,11 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_settingsLoaded = false;
WidgetsBinding.instance.addObserver(this);
Logger.d("<!!!> Creating new HomeAssistant instance");
_homeAssistant = HomeAssistant();
_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) {
_homeAssistant.disconnect().then((_){
_initialLoad();
@ -176,16 +185,13 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_subscribe();
_refreshData();
}, onError: (_) {
setState(() {
_isLoading = 2;
});
_showErrorSnackBar(message: _, errorCode: 5);
_showErrorBottomBar(message: _, errorCode: 5);
});
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
TheLogger.debug("$state");
Logger.d("$state");
if (state == AppLifecycleState.resumed && _settingsLoaded) {
_refreshData();
}
@ -199,8 +205,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_webSocketApiEndpoint = "${prefs.getString('hassio-protocol')}://$domain:$port/api/websocket";
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;
_useLovelaceUI = prefs.getBool('use-lovelace') ?? true;
if ((domain == null) || (port == null) || (_password == null) ||
(domain.length == 0) || (port.length == 0) || (_password.length == 0)) {
throw("Check connection settings");
@ -212,7 +217,12 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_subscribe() {
if (_stateSubscription == null) {
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
setState(() {});
if (event.needToRebuildUI) {
Logger.d("New entity. Need to rebuild UI");
_refreshData();
} else {
setState(() {});
}
});
}
if (_serviceCallSubscription == null) {
@ -226,7 +236,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
if (_showEntityPageSubscription == null) {
_showEntityPageSubscription =
eventBus.on<ShowEntityPageEvent>().listen((event) {
_showEntityPage(event.entity);
_showEntityPage(event.entity.entityId);
});
}
@ -238,21 +248,17 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
if (_showErrorSubscription == null) {
_showErrorSubscription = eventBus.on<ShowErrorEvent>().listen((event){
_showErrorSnackBar(message: event.text, errorCode: event.errorCode);
_showErrorBottomBar(message: event.text, errorCode: event.errorCode);
});
}
}
_refreshData() async {
_homeAssistant.updateSettings(_webSocketApiEndpoint, _password, _authType, _useLovelaceUI);
setState(() {
_hideErrorSnackBar();
_isLoading = 1;
});
_homeAssistant.updateSettings(_webSocketApiEndpoint, _password, _useLovelaceUI);
_hideBottomBar();
_showInfoBottomBar(progress: true,);
await _homeAssistant.fetch().then((result) {
setState(() {
_isLoading = 0;
});
_hideBottomBar();
}).catchError((e) {
_setErrorState(e);
});
@ -260,18 +266,15 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
}
_setErrorState(e) {
setState(() {
_isLoading = 2;
});
if (e is Error) {
TheLogger.error(e.toString());
TheLogger.error("${e.stackTrace}");
_showErrorSnackBar(
Logger.e(e.toString());
Logger.e("${e.stackTrace}");
_showErrorBottomBar(
message: "There was some error",
errorCode: 13
);
} else {
_showErrorSnackBar(
_showErrorBottomBar(
message: e != null ? e["errorMessage"] ?? "$e" : "Unknown error",
errorCode: e["errorCode"] != null ? e["errorCode"] : 99
);
@ -279,14 +282,18 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
}
void _callService(String domain, String service, String entityId, Map<String, dynamic> additionalParams) {
_showInfoBottomBar(
message: "Calling $domain.$service",
duration: Duration(seconds: 3)
);
_homeAssistant.callService(domain, service, entityId, additionalParams).catchError((e) => _setErrorState(e));
}
void _showEntityPage(Entity entity) {
void _showEntityPage(String entityId) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EntityViewPage(entity: entity, homeAssistant: _homeAssistant),
builder: (context) => EntityViewPage(entityId: entityId, homeAssistant: _homeAssistant),
)
);
}
@ -303,31 +310,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
return result;
}
Widget _buildAppTitle() {
Row titleRow = Row(
children: [Text(_homeAssistant != null ? _homeAssistant.locationName : "")],
);
if (_isLoading == 1) {
titleRow.children.add(Padding(
child: JumpingDotsProgressIndicator(
fontSize: 26.0,
color: Colors.white,
),
padding: const EdgeInsets.fromLTRB(5.0, 0.0, 0.0, 30.0),
));
} else if (_isLoading == 2) {
titleRow.children.add(Padding(
child: Icon(
Icons.error_outline,
size: 20.0,
color: Colors.red,
),
padding: const EdgeInsets.fromLTRB(5.0, 0.0, 0.0, 0.0),
));
}
return titleRow;
}
Drawer _buildAppDrawer() {
List<Widget> menuItems = [];
menuItems.add(
@ -385,10 +367,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
GestureDetector(
onTap: () {
Navigator.of(context).pop();
HAUtils.launchURL("http://www.keyboardcrumbs.io/");
HAUtils.launchURL("http://www.vynn.co/");
},
child: Text(
"www.keyboardcrumbs.io",
"www.vynn.co",
style: TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline
@ -409,21 +391,51 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
);
}
void _hideErrorSnackBar() {
_scaffoldKey?.currentState?.hideCurrentSnackBar();
void _hideBottomBar() {
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
setState(() {
_showBottomBar = false;
});
}
void _showErrorSnackBar({Key key, @required String message, @required int errorCode}) {
SnackBarAction action;
Widget _bottomBarAction;
bool _showBottomBar = false;
String _bottomBarText;
bool _bottomBarProgress;
Color _bottomBarColor;
Timer _bottomBarTimer;
void _showInfoBottomBar({String message, bool progress: false, Duration duration}) {
_bottomBarTimer?.cancel();
_bottomBarAction = Container(height: 0.0, width: 0.0,);
_bottomBarColor = Colors.grey.shade50;
setState(() {
_bottomBarText = message;
_bottomBarProgress = progress;
_showBottomBar = true;
});
if (duration != null) {
_bottomBarTimer = Timer(duration, () {
_hideBottomBar();
});
}
}
void _showErrorBottomBar({Key key, @required String message, @required int errorCode}) {
TextStyle textStyle = TextStyle(
color: Colors.blue,
fontSize: Sizes.nameFontSize
);
_bottomBarColor = Colors.red.shade100;
switch (errorCode) {
case 9:
case 11:
case 7:
case 1: {
action = SnackBarAction(
label: "Retry",
_bottomBarAction = FlatButton(
child: Text("Retry", style: textStyle),
onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar();
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
_refreshData();
},
);
@ -432,10 +444,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
case 5: {
message = "Check connection settings";
action = SnackBarAction(
label: "Open",
_bottomBarAction = FlatButton(
child: Text("Open", style: textStyle),
onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar();
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
Navigator.pushNamed(context, '/connection-settings');
},
);
@ -443,10 +455,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
}
case 6: {
action = SnackBarAction(
label: "Settings",
_bottomBarAction = FlatButton(
child: Text("Settings", style: textStyle),
onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar();
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
Navigator.pushNamed(context, '/connection-settings');
},
);
@ -454,10 +466,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
}
case 10: {
action = SnackBarAction(
label: "Refresh",
_bottomBarAction = FlatButton(
child: Text("Refresh", style: textStyle),
onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar();
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
_refreshData();
},
);
@ -465,10 +477,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
}
case 8: {
action = SnackBarAction(
label: "Reconnect",
_bottomBarAction = FlatButton(
child: Text("Reconnect", style: textStyle),
onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar();
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
_refreshData();
},
);
@ -476,24 +488,29 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
}
default: {
action = SnackBarAction(
label: "Reload",
_bottomBarAction = FlatButton(
child: Text("Reload", style: textStyle),
onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar();
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
_refreshData();
},
);
break;
}
}
_scaffoldKey.currentState.hideCurrentSnackBar();
setState(() {
_bottomBarProgress = false;
_bottomBarText = "$message (code: $errorCode)";
_showBottomBar = true;
});
/*_scaffoldKey.currentState.hideCurrentSnackBar();
_scaffoldKey.currentState.showSnackBar(
SnackBar(
content: Text("$message (code: $errorCode)"),
action: action,
duration: Duration(hours: 1),
)
);
);*/
}
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
@ -506,7 +523,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
floating: true,
pinned: true,
primary: true,
title: _buildAppTitle(),
title: Text(_homeAssistant != null ? _homeAssistant.locationName : ""),
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () {
@ -532,7 +549,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
Icon(
MaterialDesignIcons.createIconDataFromIconName("mdi:home-assistant"),
size: 100.0,
color: _isLoading == 2 ? Colors.redAccent : Colors.blue,
color: Colors.blue,
),
]
),
@ -544,12 +561,61 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
Widget bottomBar;
if (_showBottomBar) {
List<Widget> bottomBarChildren = [];
if (_bottomBarText != null) {
bottomBarChildren.add(
Padding(
padding: EdgeInsets.fromLTRB(
Sizes.leftWidgetPadding, Sizes.rowPadding, 0.0,
Sizes.rowPadding),
child: Text(
"$_bottomBarText",
textAlign: TextAlign.left,
softWrap: true,
),
)
);
}
if (_bottomBarProgress) {
bottomBarChildren.add(
CollectionScaleTransition(
children: <Widget>[
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.on),),
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.unavailable),),
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.off),),
],
),
);
}
if (bottomBarChildren.isNotEmpty) {
bottomBar = Container(
color: _bottomBarColor,
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Column(
crossAxisAlignment: _bottomBarProgress ? CrossAxisAlignment.center : CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: bottomBarChildren,
),
),
_bottomBarAction
],
),
);
}
}
// This method is rerun every time setState is called.
if (_homeAssistant.ui == null || _homeAssistant.ui.views == null) {
return Scaffold(
key: _scaffoldKey,
primary: false,
drawer: _buildAppDrawer(),
bottomNavigationBar: bottomBar,
body: _buildScaffoldBody(true)
);
} else {
@ -557,6 +623,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
key: _scaffoldKey,
drawer: _buildAppDrawer(),
primary: false,
bottomNavigationBar: bottomBar,
body: DefaultTabController(
length: _homeAssistant.ui?.views?.length ?? 0,
child: _buildScaffoldBody(false),

View File

@ -22,6 +22,16 @@ class MaterialDesignIcons {
"cover.closed": "mdi:window-closed",
"cover.closing": "mdi:window-open",
"cover.opening": "mdi:window-open",
"lock.locked": "mdi:lock",
"lock.unlocked": "mdi:lock-open",
"fan": "mdi:fan",
"alarm_control_panel.disarmed" : "mdi:bell-outline",
"alarm_control_panel.armed_home" : "mdi:bell-plus",
"alarm_control_panel.armed_away" : "mdi:bell",
"alarm_control_panel.armed_night" : "mdi:bell-sleep",
"alarm_control_panel.armed_custom_bypass" : "mdi:bell",
"alarm_control_panel.triggered" : "mdi:bell-ring",
"alarm_control_panel" : "mdi:bell"
};
static Map _defaultIconsByDeviceClass = {
@ -38,8 +48,8 @@ class MaterialDesignIcons {
"binary_sensor.heat.off": "mdi:thermometer",
"binary_sensor.light.on": "mdi:brightness-7",
"binary_sensor.light.off": "mdi:brightness-5",
//"binary_sensor.lock.on": "mdi:",
//"binary_sensor.lock.off": "mdi:",
"binary_sensor.lock.on": "mdi:lock-open",
"binary_sensor.lock.off": "mdi:lock",
"binary_sensor.moisture.on": "mdi:water",
"binary_sensor.moisture.off": "mdi:water-off",
"binary_sensor.motion.on": "mdi:run",
@ -56,8 +66,8 @@ class MaterialDesignIcons {
"binary_sensor.power.off": "mdi:verified",
//"binary_sensor.presence.on": "mdi:",
//"binary_sensor.presence.off": "mdi:",
//"binary_sensor.problem.on": "mdi:",
//"binary_sensor.problem.off": "mdi:",
"binary_sensor.problem.on": "mdi:alert-outline",
"binary_sensor.problem.off": "mdi:check-outline",
"binary_sensor.safety.on": "mdi:alert",
"binary_sensor.safety.off": "mdi:verified",
"binary_sensor.smoke.on": "mdi:alert",
@ -66,13 +76,13 @@ class MaterialDesignIcons {
"binary_sensor.sound.off": "mdi:music-note-off",
"binary_sensor.vibration.on": "mdi:vibrate",
"binary_sensor.vibration.off": "mdi:mdi-crop-portrait",
//"binary_sensor.window.on": "mdi:",
//"binary_sensor.window.off": "mdi:",
"binary_sensor.window.on": "mdi:window-open",
"binary_sensor.window.off": "mdi:window-closed",
"sensor.battery": "mdi:battery-80",
"sensor.humidity": "mdi:water-percent",
//"sensor.illuminance": "mdi:",
"sensor.temperature": "mdi:thermometer",
//"cover.window": "mdi:",
"cover.window": "mdi:mdi:window-closed",
"cover.garage.closed": "mdi:garage",
"cover.garage.open": "mdi:garage-open",
"cover.garage.opening": "mdi:garage-open",

View File

@ -18,10 +18,8 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
String _newHassioPassword = "";
String _socketProtocol = "wss";
String _newSocketProtocol = "wss";
String _authType = "access_token";
String _newAuthType = "access_token";
bool _useLovelace = false;
bool _newUseLovelace = false;
bool _useLovelace = true;
bool _newUseLovelace = true;
@override
void initState() {
@ -37,11 +35,10 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
_hassioPort = _newHassioPort = prefs.getString("hassio-port") ?? "";
_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;
_useLovelace = _newUseLovelace = prefs.getBool("use-lovelace") ?? true;
} catch (e) {
_useLovelace = _newUseLovelace = false;
_useLovelace = _newUseLovelace = true;
}
});
}
@ -51,7 +48,6 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
(_newHassioPort != _hassioPort) ||
(_newHassioDomain != _hassioDomain) ||
(_newSocketProtocol != _socketProtocol) ||
(_newAuthType != _authType) ||
(_newUseLovelace != _useLovelace));
}
@ -66,7 +62,6 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
prefs.setString("hassio-password", _newHassioPassword);
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);
}
@ -83,13 +78,13 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
icon: Icon(Icons.check),
onPressed: (){
if (_checkConfigChanged()) {
TheLogger.debug("Settings changed. Saving...");
Logger.d("Settings changed. Saving...");
_saveSettings().then((r) {
Navigator.pop(context);
eventBus.fire(SettingsChangedEvent(true));
});
} else {
TheLogger.debug("Settings was not changed");
Logger.d("Settings was not changed");
Navigator.pop(context);
}
}
@ -97,6 +92,7 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
],
),
body: ListView(
scrollDirection: Axis.vertical,
padding: const EdgeInsets.all(20.0),
children: <Widget>[
Text(
@ -149,22 +145,13 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
_newHassioPort = value;
}
),
new Row(
children: [
Text("Login with access token (HA >= 0.78.0)"),
Switch(
value: (_newAuthType == "access_token"),
onChanged: (value) {
setState(() {
_newAuthType = value ? "access_token" : "api_password";
});
},
)
],
new Text(
"Try ports 80 and 443 if default is not working and you don't know why.",
style: TextStyle(color: Colors.grey),
),
new TextField(
decoration: InputDecoration(
labelText: _newAuthType == "access_token" ? "Access token" : "API password"
labelText: "Access token"
),
controller: new TextEditingController.fromValue(
new TextEditingValue(

View File

@ -2,76 +2,49 @@ part of '../main.dart';
class HACard {
List<EntityWrapper> entities = [];
EntityWrapper linkedEntity;
List<HACard> childCards = [];
EntityWrapper linkedEntityWrapper;
String name;
String id;
String type;
bool showName;
bool showState;
bool showEmpty;
int columnsCount;
List stateFilter;
List states;
String content;
HACard({
this.name,
this.id,
this.linkedEntity,
this.linkedEntityWrapper,
this.columnsCount: 4,
this.showName: true,
this.showState: true,
this.stateFilter: const [],
this.showEmpty: true,
this.content,
this.states,
@required this.type
});
Widget build(BuildContext context) {
switch (type) {
case "entities": {
return EntitiesCardWidget(
card: this,
);
}
case "glance": {
return GlanceCardWidget(
card: this,
);
}
case "media-control": {
return MediaControlCardWidget(
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": {
return UnsupportedCardWidget(
card: this,
);
}
default: {
if ((linkedEntity == null) && (entities.isNotEmpty)) {
return EntitiesCardWidget(
card: this,
);
} else {
return UnsupportedCardWidget(
card: this,
);
}
}
List<EntityWrapper> getEntitiesToShow() {
return entities.where((entityWrapper) {
if (entityWrapper.entity.isHidden) {
return false;
}
if (stateFilter.isNotEmpty) {
return stateFilter.contains(entityWrapper.entity.state);
}
return true;
}).toList();
}
Widget build(BuildContext context) {
return CardWidget(
card: this,
);
}
}

View File

@ -3,13 +3,15 @@ part of '../main.dart';
class Sizes {
static const rightWidgetPadding = 14.0;
static const leftWidgetPadding = 8.0;
static const buttonPadding = 4.0;
static const extendedWidgetHeight = 50.0;
static const iconSize = 28.0;
static const largeIconSize = 34.0;
static const stateFontSize = 16.0;
static const nameFontSize = 16.0;
static const largeIconSize = 46.0;
static const stateFontSize = 15.0;
static const nameFontSize = 15.0;
static const smallFontSize = 14.0;
static const largeFontSize = 24.0;
static const mediumFontSize = 21.0;
static const inputWidth = 160.0;
static const rowPadding = 10.0;
}

View File

@ -28,8 +28,8 @@ class HAView {
HACard card = HACard(
name: e.displayName,
id: e.entityId,
linkedEntity: EntityWrapper(entity: e),
type: "media-control"
linkedEntityWrapper: EntityWrapper(entity: e),
type: CardType.mediaControl
);
cards.add(card);
});
@ -40,7 +40,7 @@ class HAView {
HACard card = HACard(
id: groupIdToAdd,
name: entity.domain,
type: "entities"
type: CardType.entities
);
card.entities.add(EntityWrapper(entity: entity));
autoGeneratedCards.add(card);
@ -51,16 +51,16 @@ class HAView {
HACard card = HACard(
name: entity.displayName,
id: entity.entityId,
linkedEntity: EntityWrapper(entity: entity),
type: "entities"
linkedEntityWrapper: EntityWrapper(entity: entity),
type: CardType.entities
);
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){
HACard mediaCard = HACard(
name: entity.displayName,
id: entity.entityId,
linkedEntity: EntityWrapper(entity: entity),
type: "media-control"
linkedEntityWrapper: EntityWrapper(entity: entity),
type: CardType.mediaControl
);
cards.add(mediaCard);
});

View File

@ -3,18 +3,22 @@ part of '../main.dart';
class CardHeaderWidget extends StatelessWidget {
final String name;
final Widget trailing;
final Widget subtitle;
const CardHeaderWidget({Key key, this.name}) : super(key: key);
const CardHeaderWidget({Key key, this.name, this.trailing, this.subtitle}) : super(key: key);
@override
Widget build(BuildContext context) {
var result;
if ((name != null) && (name.trim().length > 0)) {
result = new ListTile(
trailing: trailing,
subtitle: subtitle,
title: Text("$name",
textAlign: TextAlign.left,
overflow: TextOverflow.ellipsis,
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: 25.0)),
style: new TextStyle(fontSize: Sizes.mediumFontSize)),
);
} else {
result = new Container(width: 0.0, height: 0.0);

View File

@ -0,0 +1,292 @@
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.markdown: {
return _buildMarkdownCard(context);
}
case CardType.alarmPanel: {
return _buildAlarmPanelCard(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 _buildMarkdownCard(BuildContext context) {
if (card.content == null) {
return Container(height: 0.0, width: 0.0,);
}
List<Widget> body = [];
body.add(CardHeaderWidget(name: card.name));
body.add(MarkdownBody(data: card.content));
return Card(
child: Padding(
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
child: new Column(mainAxisSize: MainAxisSize.min, children: body),
)
);
}
Widget _buildAlarmPanelCard(BuildContext context) {
if (card.linkedEntityWrapper == null || card.linkedEntityWrapper.entity == null) {
return Container(width: 0, height: 0,);
} else {
List<Widget> body = [];
body.add(CardHeaderWidget(
name: card.name ?? "",
subtitle: Text("${card.linkedEntityWrapper.entity.displayState}",
style: TextStyle(
color: Colors.grey
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
EntityIcon(
iconSize: 50.0,
),
Container(
width: 26.0,
child: IconButton(
padding: EdgeInsets.all(0.0),
alignment: Alignment.centerRight,
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
"mdi:dots-vertical")),
onPressed: () => eventBus.fire(new ShowEntityPageEvent(card.linkedEntityWrapper.entity))
)
)
]
),
));
body.add(
AlarmControlPanelControlsWidget(
extended: true,
states: card.states,
)
);
return Card(
child: EntityModel(
entityWrapper: card.linkedEntityWrapper,
handleTap: null,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
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,54 +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 = [];
double width = MediaQuery.of(context).size.width - Sizes.leftWidgetPadding - (2*Sizes.rightWidgetPadding);
List<EntityWrapper> toShow = card.entities.where((entity) {return !entity.entity.isHidden;}).toList();
int columnsCount = toShow.length >= card.columnsCount ? card.columnsCount : toShow.length;
card.entities.forEach((EntityWrapper entity) {
if (!entity.entity.isHidden) {
result.add(
SizedBox(
width: width / 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() {
if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) {
TheLogger.debug("Previous data refresh is still in progress");
Logger.d("Previous data refresh is still in progress");
} else {
_refreshCompleter = Completer();
eventBus.fire(RefreshDataEvent());

View File

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

View File

@ -7,7 +7,7 @@ packages:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
version: "2.0.8"
args:
dependency: transitive
description:
@ -35,7 +35,7 @@ packages:
name: cached_network_image
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.0+1"
version: "0.6.0-alpha.2"
charcode:
dependency: transitive
description:
@ -70,7 +70,7 @@ packages:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
version: "2.1.1"
crypto:
dependency: transitive
description:
@ -83,7 +83,7 @@ packages:
description:
path: "."
ref: HEAD
resolved-ref: c5727795659e886a7db8b39a14e2c8987280fe1f
resolved-ref: a7ed88a4793e094a4d5d5c2d88a89e55510accde
url: "https://github.com/MarkOSullivan94/dart_config.git"
source: git
version: "0.5.0"
@ -93,7 +93,7 @@ packages:
name: date_format
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
version: "1.0.6"
event_bus:
dependency: "direct main"
description:
@ -112,21 +112,28 @@ packages:
name: flutter_cache_manager
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0+1"
version: "0.3.0-alpha.2"
flutter_colorpicker:
dependency: "direct main"
description:
name: flutter_colorpicker
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.0"
version: "0.2.1"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.1"
version: "0.7.0"
flutter_markdown:
dependency: "direct main"
description:
name: flutter_markdown
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
flutter_test:
dependency: "direct dev"
description: flutter
@ -138,7 +145,7 @@ packages:
name: http
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.0"
version: "0.12.0+1"
http_parser:
dependency: transitive
description:
@ -152,7 +159,7 @@ packages:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
version: "2.0.6"
intl:
dependency: transitive
description:
@ -167,6 +174,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.11.3+2"
markdown:
dependency: transitive
description:
name: markdown
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
matcher:
dependency: transitive
description:
@ -201,7 +215,7 @@ packages:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
version: "2.1.1"
progress_indicators:
dependency: "direct main"
description:
@ -222,7 +236,7 @@ packages:
name: shared_preferences
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.3"
version: "0.5.0"
sky_engine:
dependency: transitive
description: flutter
@ -235,6 +249,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.1"
sqflite:
dependency: transitive
description:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.2+1"
stack_trace:
dependency: transitive
description:
@ -262,7 +283,7 @@ packages:
name: synchronized
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.3"
version: "1.5.3+2"
term_glyph:
dependency: transitive
description:
@ -290,7 +311,7 @@ packages:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.1"
version: "5.0.0"
uuid:
dependency: transitive
description:
@ -318,7 +339,7 @@ packages:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "3.2.3"
version: "3.3.1"
yaml:
dependency: transitive
description:
@ -327,5 +348,5 @@ packages:
source: hosted
version: "2.1.15"
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"

View File

@ -1,7 +1,7 @@
name: hass_client
description: Home Assistant Android Client
version: 0.3.9+67
version: 0.3.14+85
environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
@ -18,10 +18,7 @@ dependencies:
date_format: any
flutter_colorpicker: any
charts_flutter: any
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
#cupertino_icons: ^0.1.2
flutter_markdown: any
dev_dependencies:
flutter_test: