Compare commits
76 Commits
Author | SHA1 | Date | |
---|---|---|---|
4bb616b327 | |||
38219618ba | |||
6774b53758 | |||
29a94c882f | |||
5897fa3a99 | |||
7af92c2dc9 | |||
1094177a42 | |||
5e814e8109 | |||
24c7675fa4 | |||
dc3ca38c78 | |||
96b528e055 | |||
3858036631 | |||
19d42ceeb3 | |||
a2836a3603 | |||
2a45758a6d | |||
dc1bf4d878 | |||
e82ba60c4e | |||
09199d30e8 | |||
724d32dbe2 | |||
949c8ee44e | |||
1a446d34c7 | |||
22a5847285 | |||
1c8f770f10 | |||
be5ea55f6b | |||
c65ade9827 | |||
d3c1422b9e | |||
b6ac9f985f | |||
a59de4b6dc | |||
f507d5df0c | |||
f77e46de37 | |||
cda17b1217 | |||
be560769ef | |||
3815800e32 | |||
a3226311a2 | |||
79669243c2 | |||
fdc81f6ea4 | |||
7fe44459e7 | |||
a8500d44e1 | |||
b4d4c5abec | |||
c19a3f272a | |||
b264534858 | |||
ab53f77f9e | |||
c73956720c | |||
051041e794 | |||
5c83be9fee | |||
4bece42693 | |||
4ae107fe4c | |||
9523ed2562 | |||
9c403480e2 | |||
20b1b90e39 | |||
5633e30448 | |||
4492fb9f0c | |||
36410752e4 | |||
0219f7bfbb | |||
5f3c77f4b9 | |||
a36c7a9ca3 | |||
56ce6dfeeb | |||
67c214454f | |||
73398378c4 | |||
215871ce9e | |||
fd8ea6befd | |||
809a1a1c8c | |||
fc8f2f200f | |||
f41c9f9197 | |||
cdf55ce68b | |||
12088d9516 | |||
a0235ee385 | |||
67fbdb13c6 | |||
c5960de0be | |||
da15e880ec | |||
efbe33f4e3 | |||
af84c99a2d | |||
438449cad8 | |||
d9ca55c3b7 | |||
f248268984 | |||
8ee096595c |
@ -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
|
||||
|
||||
|
@ -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
1
docs/empty
Normal file
@ -0,0 +1 @@
|
||||
|
BIN
docs/ha_access_tokens.png
Normal file
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
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
BIN
docs/settings-869x1024.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 102 KiB |
@ -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();
|
||||
}
|
||||
}
|
12
lib/entity_class/alarm_control_panel.class.dart
Normal file
12
lib/entity_class/alarm_control_panel.class.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
24
lib/entity_class/automation_entity.dart
Normal file
24
lib/entity_class/automation_entity.dart
Normal 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",
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ class ButtonEntity extends Entity {
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return ButtonStateWidget();
|
||||
return FlatServiceButton(
|
||||
text: "EXECUTE",
|
||||
);
|
||||
}
|
||||
}
|
19
lib/entity_class/camera_entity.class.dart
Normal file
19
lib/entity_class/camera_entity.class.dart
Normal 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']}',
|
||||
);
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
32
lib/entity_class/fan_entity.class.dart
Normal file
32
lib/entity_class/fan_entity.class.dart
Normal 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();
|
||||
}
|
||||
}
|
43
lib/entity_class/group_entity.class.dart
Normal file
43
lib/entity_class/group_entity.class.dart
Normal 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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
12
lib/entity_class/lock_entity.class.dart
Normal file
12
lib/entity_class/lock_entity.class.dart
Normal 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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
45
lib/entity_widgets/button_entity_container.dart
Normal file
45
lib/entity_widgets/button_entity_container.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
40
lib/entity_widgets/common/flat_service_button.dart
Normal file
40
lib/entity_widgets/common/flat_service_button.dart
Normal 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),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ class ModeSelectorWidget extends StatelessWidget {
|
||||
|
||||
ModeSelectorWidget({
|
||||
Key key,
|
||||
this.caption,
|
||||
@required this.caption,
|
||||
@required this.options,
|
||||
this.value,
|
||||
@required this.onChange,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
244
lib/entity_widgets/controls/alarm_control_panel_controls.dart
Normal file
244
lib/entity_widgets/controls/alarm_control_panel_controls.dart
Normal 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
|
||||
]
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
134
lib/entity_widgets/controls/camera_controls.dart
Normal file
134
lib/entity_widgets/controls/camera_controls.dart
Normal 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();
|
||||
}
|
||||
}
|
@ -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(),
|
||||
)
|
||||
],
|
||||
)
|
||||
|
123
lib/entity_widgets/controls/fan_controls.dart
Normal file
123
lib/entity_widgets/controls/fan_controls.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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>[
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -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,)
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
32
lib/entity_widgets/state/lock_state.dart
Normal file
32
lib/entity_widgets/state/lock_state.dart
Normal 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),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
);
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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 [];
|
||||
|
@ -19,7 +19,7 @@ class _LogViewPageState extends State<LogViewPage> {
|
||||
}
|
||||
|
||||
_loadLog() async {
|
||||
_logData = TheLogger.getLog();
|
||||
_logData = Logger.getLog();
|
||||
}
|
||||
|
||||
@override
|
||||
|
257
lib/main.dart
257
lib/main.dart
@ -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),
|
||||
|
@ -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",
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
|
292
lib/ui_widgets/card_widget.dart
Normal file
292
lib/ui_widgets/card_widget.dart
Normal 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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
|
@ -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 {
|
||||
|
53
pubspec.lock
53
pubspec.lock
@ -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"
|
||||
|
@ -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:
|
||||
|
Reference in New Issue
Block a user