Compare commits
84 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
a8e79c289b | |||
2cd8533882 | |||
0a21d9c690 | |||
e77bb533b1 | |||
96f1211395 | |||
1e4cb03470 | |||
ab67b557ca | |||
82c9bd26d1 | |||
1bd04abd37 | |||
c5942d22b3 | |||
37ad5e81cf | |||
26187e6233 | |||
b8f6fda8d3 | |||
62b4e99810 | |||
25bf10a64e | |||
874410964d | |||
57c30917b3 | |||
87f89b63e1 | |||
3190b45db3 | |||
f5434e26e5 | |||
86b6ad6bba | |||
8a9641fbed | |||
5142391da2 | |||
01090dc3b1 | |||
0a7bbb5a38 | |||
c347eee9f0 | |||
90f197ba54 | |||
e09917c687 | |||
a69da832cb | |||
c1708fd980 | |||
c85a9bbe27 | |||
d9790dedbb | |||
30e4eaa023 | |||
54e00c3403 | |||
0e3474bbcb | |||
efd06ca547 | |||
69fd37d4fe | |||
4a49372410 | |||
478f58e2d8 | |||
a87aff67ac | |||
644f5e7fc6 | |||
3cddac3dc6 | |||
ab30c64eab | |||
6d79487219 | |||
9f7444eae0 | |||
788d682f2f | |||
66f84952f0 | |||
5d95c3702d | |||
1f0bd8059b | |||
a7830df628 | |||
790446d592 | |||
bb17885b4a | |||
04d8681656 | |||
71c4ac7fed | |||
3f7e21e97e | |||
e24c47b041 | |||
73b32b30a8 | |||
5b6155057c | |||
ff4185effe |
201
LICENSE
Normal file
201
LICENSE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
14
README.md
14
README.md
@ -1,3 +1,13 @@
|
||||
# Android client for Home Assistant
|
||||
# HA Client
|
||||
## Native Android client for Home Assistant
|
||||
### With Lovelace UI support
|
||||
|
||||
Home Assistant Android client using Flutter and Dart.
|
||||
Home Assistant Android client on Dart with Flutter.
|
||||
|
||||
Visit [www.keyboardcrumbs.io](http://www.keyboardcrumbs.io/ha-client) for more info.
|
||||
|
||||
Join [Google Group](https://groups.google.com/d/forum/ha-client-alpha-testing) to become an alpha tester
|
||||
|
||||
Download the app from [Google Play](https://play.google.com/apps/testing/com.keyboardcrumbs.haclient) after joining the group
|
||||
|
||||
Discuss it in [Home Assistant community](https://community.home-assistant.io/t/alpha-testing-ha-client-native-android-client-for-home-assistant/69912) or in [Discord](https://discord.gg/NSaQEQ8)
|
||||
|
@ -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) {
|
||||
TheLogger.debug("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();
|
||||
}
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class ClimateEntity extends Entity {
|
||||
@override
|
||||
double widgetHeight = 38.0;
|
||||
|
||||
@override
|
||||
EntityHistoryConfig historyConfig = EntityHistoryConfig(
|
||||
@ -86,7 +84,7 @@ class ClimateEntity extends Entity {
|
||||
String get fanMode => attributes['fan_mode'];
|
||||
String get swingMode => attributes['swing_mode'];
|
||||
bool get awayMode => attributes['away_mode'] == "on";
|
||||
bool get isOff => state == "off";
|
||||
bool get isOff => state == EntityState.off;
|
||||
bool get auxHeat => attributes['aux_heat'] == "on";
|
||||
|
||||
ClimateEntity(Map rawData) : super(rawData);
|
||||
|
57
lib/entity_class/const.dart
Normal file
57
lib/entity_class/const.dart
Normal file
@ -0,0 +1,57 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class EntityState {
|
||||
static const on = 'on';
|
||||
static const off = 'off';
|
||||
static const home = 'home';
|
||||
static const not_home = 'not_home';
|
||||
static const unknown = 'unknown';
|
||||
static const open = 'open';
|
||||
static const opening = 'opening';
|
||||
static const closed = 'closed';
|
||||
static const closing = 'closing';
|
||||
static const playing = 'playing';
|
||||
static const paused = 'paused';
|
||||
static const idle = 'idle';
|
||||
static const standby = 'standby';
|
||||
static const alarm_disarmed = 'disarmed';
|
||||
static const alarm_armed_home = 'armed_home';
|
||||
static const alarm_armed_away = 'armed_away';
|
||||
static const alarm_armed_night = 'armed_night';
|
||||
static const alarm_armed_custom_bypass = 'armed_custom_bypass';
|
||||
static const alarm_pending = 'pending';
|
||||
static const alarm_arming = 'arming';
|
||||
static const alarm_disarming = 'disarming';
|
||||
static const alarm_triggered = 'triggered';
|
||||
static const locked = 'locked';
|
||||
static const unlocked = 'unlocked';
|
||||
static const unavailable = 'unavailable';
|
||||
static const ok = 'ok';
|
||||
static const problem = 'problem';
|
||||
}
|
||||
|
||||
class EntityTapAction {
|
||||
static const moreInfo = 'more-info';
|
||||
static const toggle = 'toggle';
|
||||
static const callService = 'call-service';
|
||||
static const none = 'none';
|
||||
}
|
||||
|
||||
class CardType {
|
||||
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";
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class CoverEntity extends Entity {
|
||||
@override
|
||||
double widgetHeight = 38.0;
|
||||
|
||||
static const SUPPORT_OPEN = 1;
|
||||
static const SUPPORT_CLOSE = 2;
|
||||
@ -42,10 +40,10 @@ class CoverEntity extends Entity {
|
||||
|
||||
double get currentPosition => _getDoubleAttributeValue('current_position');
|
||||
double get currentTiltPosition => _getDoubleAttributeValue('current_tilt_position');
|
||||
bool get canBeOpened => ((state != "opening") && (state != "open"));
|
||||
bool get canBeClosed => ((state != "closing") && (state != "closed"));
|
||||
bool get canTiltBeOpened => currentPosition < 100;
|
||||
bool get canTiltBeClosed => currentPosition > 0;
|
||||
bool get canBeOpened => ((state != EntityState.opening) && (state != EntityState.open)) || (state == EntityState.open && currentPosition != null && currentPosition > 0.0 && currentPosition < 100.0);
|
||||
bool get canBeClosed => ((state != EntityState.closing) && (state != EntityState.closed));
|
||||
bool get canTiltBeOpened => currentTiltPosition < 100;
|
||||
bool get canTiltBeClosed => currentTiltPosition > 0;
|
||||
|
||||
CoverEntity(Map rawData) : super(rawData);
|
||||
|
||||
@ -59,4 +57,4 @@ class CoverEntity extends Entity {
|
||||
return CoverControlWidget();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,6 @@ part of '../main.dart';
|
||||
|
||||
class Entity {
|
||||
|
||||
static const badgeColors = {
|
||||
"default": Color.fromRGBO(223, 76, 30, 1.0),
|
||||
"binary_sensor": Color.fromRGBO(3, 155, 229, 1.0)
|
||||
};
|
||||
static List badgeDomains = [
|
||||
"alarm_control_panel",
|
||||
"binary_sensor",
|
||||
@ -16,24 +12,10 @@ class Entity {
|
||||
"sensor"
|
||||
];
|
||||
|
||||
static const rightWidgetPadding = 14.0;
|
||||
static const leftWidgetPadding = 8.0;
|
||||
static const extendedWidgetHeight = 50.0;
|
||||
static const iconSize = 28.0;
|
||||
static const stateFontSize = 16.0;
|
||||
static const nameFontSize = 16.0;
|
||||
static const smallFontSize = 14.0;
|
||||
static const largeFontSize = 24.0;
|
||||
static const inputWidth = 160.0;
|
||||
static const rowPadding = 10.0;
|
||||
|
||||
double widgetHeight = 34.0;
|
||||
|
||||
Map attributes;
|
||||
String domain;
|
||||
String entityId;
|
||||
String state;
|
||||
String assumedState;
|
||||
DateTime _lastUpdated;
|
||||
|
||||
List<Entity> childEntities = [];
|
||||
@ -43,7 +25,7 @@ class Entity {
|
||||
);
|
||||
|
||||
String get displayName =>
|
||||
attributes["friendly_name"] ?? (attributes["name"] ?? "_");
|
||||
attributes["friendly_name"] ?? (attributes["name"] ?? entityId.split(".")[1].replaceAll("_", " "));
|
||||
|
||||
String get deviceClass => attributes["device_class"] ?? null;
|
||||
bool get isView =>
|
||||
@ -52,7 +34,7 @@ class Entity {
|
||||
bool get isGroup => domain == "group";
|
||||
bool get isBadge => Entity.badgeDomains.contains(domain);
|
||||
String get icon => attributes["icon"] ?? "";
|
||||
bool get isOn => state == "on";
|
||||
bool get isOn => state == EntityState.on;
|
||||
String get entityPicture => attributes["entity_picture"];
|
||||
String get unitOfMeasurement => attributes["unit_of_measurement"] ?? "";
|
||||
List get childEntityIds => attributes["entity_id"] ?? [];
|
||||
@ -69,7 +51,6 @@ class Entity {
|
||||
domain = rawData["entity_id"].split(".")[0];
|
||||
entityId = rawData["entity_id"];
|
||||
state = rawData["state"];
|
||||
assumedState = state;
|
||||
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
|
||||
}
|
||||
|
||||
@ -95,14 +76,25 @@ 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 EntityModel(
|
||||
entity: this,
|
||||
child: DefaultEntityContainer(
|
||||
state: _buildStatePart(context),
|
||||
height: widgetHeight,
|
||||
),
|
||||
handleTap: true,
|
||||
return DefaultEntityContainer(
|
||||
state: _buildStatePart(context)
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildGlanceWidget(BuildContext context, bool showName, bool showState) {
|
||||
return GlanceEntityContainer(
|
||||
showName: showName,
|
||||
showState: showState,
|
||||
);
|
||||
}
|
||||
|
||||
@ -123,13 +115,14 @@ class Entity {
|
||||
|
||||
Widget buildEntityPageWidget(BuildContext context) {
|
||||
return EntityModel(
|
||||
entity: this,
|
||||
entityWrapper: EntityWrapper(entity: this),
|
||||
child: EntityPageContainer(children: <Widget>[
|
||||
DefaultEntityContainer(state: _buildStatePartForPage(context), height: widgetHeight),
|
||||
DefaultEntityContainer(state: _buildStatePartForPage(context)),
|
||||
LastUpdatedWidget(),
|
||||
Divider(),
|
||||
buildHistoryWidget(),
|
||||
_buildAdditionalControlsForPage(context),
|
||||
Divider(),
|
||||
buildHistoryWidget(),
|
||||
EntityAttributesList()
|
||||
]),
|
||||
handleTap: false,
|
||||
@ -144,7 +137,7 @@ class Entity {
|
||||
|
||||
Widget buildBadgeWidget(BuildContext context) {
|
||||
return EntityModel(
|
||||
entity: this,
|
||||
entityWrapper: EntityWrapper(entity: this),
|
||||
child: BadgeWidget(),
|
||||
handleTap: true,
|
||||
);
|
||||
|
83
lib/entity_class/entity_wrapper.class.dart
Normal file
83
lib/entity_class/entity_wrapper.class.dart
Normal file
@ -0,0 +1,83 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class EntityWrapper {
|
||||
|
||||
String displayName;
|
||||
String icon;
|
||||
String tapAction;
|
||||
String holdAction;
|
||||
String tapActionService;
|
||||
Map<String, dynamic> tapActionServiceData;
|
||||
String holdActionService;
|
||||
Map<String, dynamic> holdActionServiceData;
|
||||
Entity entity;
|
||||
|
||||
|
||||
EntityWrapper({
|
||||
this.entity,
|
||||
String icon,
|
||||
String displayName,
|
||||
this.tapAction: EntityTapAction.moreInfo,
|
||||
this.holdAction: EntityTapAction.none,
|
||||
this.tapActionService,
|
||||
this.tapActionServiceData,
|
||||
this.holdActionService,
|
||||
this.holdActionServiceData
|
||||
}) {
|
||||
this.icon = icon ?? entity.icon;
|
||||
this.displayName = displayName ?? entity.displayName;
|
||||
}
|
||||
|
||||
void handleTap() {
|
||||
switch (tapAction) {
|
||||
case EntityTapAction.toggle: {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityTapAction.callService: {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent(tapActionService.split(".")[0], tapActionService.split(".")[1], null, tapActionServiceData));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityTapAction.none: {
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
eventBus.fire(
|
||||
new ShowEntityPageEvent(entity));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleHold() {
|
||||
switch (holdAction) {
|
||||
case EntityTapAction.toggle: {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityTapAction.callService: {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent(tapActionService.split(".")[0], tapActionService.split(".")[1], null, tapActionServiceData));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityTapAction.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;
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ class LightEntity extends Entity {
|
||||
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 +55,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();
|
||||
}
|
||||
}
|
83
lib/entity_class/media_player_entity.class.dart
Normal file
83
lib/entity_class/media_player_entity.class.dart
Normal file
@ -0,0 +1,83 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class MediaPlayerEntity extends Entity {
|
||||
|
||||
static const SUPPORT_PAUSE = 1;
|
||||
static const SUPPORT_SEEK = 2;
|
||||
static const SUPPORT_VOLUME_SET = 4;
|
||||
static const SUPPORT_VOLUME_MUTE = 8;
|
||||
static const SUPPORT_PREVIOUS_TRACK = 16;
|
||||
static const SUPPORT_NEXT_TRACK = 32;
|
||||
|
||||
static const SUPPORT_TURN_ON = 128;
|
||||
static const SUPPORT_TURN_OFF = 256;
|
||||
static const SUPPORT_PLAY_MEDIA = 512;
|
||||
static const SUPPORT_VOLUME_STEP = 1024;
|
||||
static const SUPPORT_SELECT_SOURCE = 2048;
|
||||
static const SUPPORT_STOP = 4096;
|
||||
static const SUPPORT_CLEAR_PLAYLIST = 8192;
|
||||
static const SUPPORT_PLAY = 16384;
|
||||
static const SUPPORT_SHUFFLE_SET = 32768;
|
||||
static const SUPPORT_SELECT_SOUND_MODE = 65536;
|
||||
|
||||
MediaPlayerEntity(Map rawData) : super(rawData);
|
||||
|
||||
bool get supportPause => ((attributes["supported_features"] &
|
||||
MediaPlayerEntity.SUPPORT_PAUSE) ==
|
||||
MediaPlayerEntity.SUPPORT_PAUSE);
|
||||
bool get supportSeek => ((attributes["supported_features"] &
|
||||
MediaPlayerEntity.SUPPORT_SEEK) ==
|
||||
MediaPlayerEntity.SUPPORT_SEEK);
|
||||
bool get supportVolumeSet => ((attributes["supported_features"] &
|
||||
MediaPlayerEntity.SUPPORT_VOLUME_SET) ==
|
||||
MediaPlayerEntity.SUPPORT_VOLUME_SET);
|
||||
bool get supportVolumeMute => ((attributes["supported_features"] &
|
||||
MediaPlayerEntity.SUPPORT_VOLUME_MUTE) ==
|
||||
MediaPlayerEntity.SUPPORT_VOLUME_MUTE);
|
||||
bool get supportPreviousTrack => ((attributes["supported_features"] &
|
||||
MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK) ==
|
||||
MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK);
|
||||
bool get supportNextTrack => ((attributes["supported_features"] &
|
||||
MediaPlayerEntity.SUPPORT_NEXT_TRACK) ==
|
||||
MediaPlayerEntity.SUPPORT_NEXT_TRACK);
|
||||
|
||||
bool get supportTurnOn => ((attributes["supported_features"] &
|
||||
MediaPlayerEntity.SUPPORT_TURN_ON) ==
|
||||
MediaPlayerEntity.SUPPORT_TURN_ON);
|
||||
bool get supportTurnOff => ((attributes["supported_features"] &
|
||||
MediaPlayerEntity.SUPPORT_TURN_OFF) ==
|
||||
MediaPlayerEntity.SUPPORT_TURN_OFF);
|
||||
bool get supportPlayMedia => ((attributes["supported_features"] &
|
||||
MediaPlayerEntity.SUPPORT_PLAY_MEDIA) ==
|
||||
MediaPlayerEntity.SUPPORT_PLAY_MEDIA);
|
||||
bool get supportVolumeStep => ((attributes["supported_features"] &
|
||||
MediaPlayerEntity.SUPPORT_VOLUME_STEP) ==
|
||||
MediaPlayerEntity.SUPPORT_VOLUME_STEP);
|
||||
bool get supportSelectSource => ((attributes["supported_features"] &
|
||||
MediaPlayerEntity.SUPPORT_SELECT_SOURCE) ==
|
||||
MediaPlayerEntity.SUPPORT_SELECT_SOURCE);
|
||||
bool get supportStop => ((attributes["supported_features"] &
|
||||
MediaPlayerEntity.SUPPORT_STOP) ==
|
||||
MediaPlayerEntity.SUPPORT_STOP);
|
||||
bool get supportClearPlaylist => ((attributes["supported_features"] &
|
||||
MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST) ==
|
||||
MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST);
|
||||
bool get supportPlay => ((attributes["supported_features"] &
|
||||
MediaPlayerEntity.SUPPORT_PLAY) ==
|
||||
MediaPlayerEntity.SUPPORT_PLAY);
|
||||
bool get supportShuffleSet => ((attributes["supported_features"] &
|
||||
MediaPlayerEntity.SUPPORT_SHUFFLE_SET) ==
|
||||
MediaPlayerEntity.SUPPORT_SHUFFLE_SET);
|
||||
bool get supportSelectSoundMode => ((attributes["supported_features"] &
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
@ -8,6 +8,12 @@ class SliderEntity extends Entity {
|
||||
double get valueStep => _getDoubleAttributeValue("step") ?? 1.0;
|
||||
|
||||
@override
|
||||
EntityHistoryConfig historyConfig = EntityHistoryConfig(
|
||||
chartType: EntityHistoryWidgetType.numericState,
|
||||
numericState: true
|
||||
);
|
||||
|
||||
/*@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return Expanded(
|
||||
//width: 200.0,
|
||||
@ -16,7 +22,9 @@ class SliderEntity extends Entity {
|
||||
SliderStateWidget(
|
||||
expanded: true,
|
||||
),
|
||||
SimpleEntityState(),
|
||||
SimpleEntityState(
|
||||
expanded: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -24,13 +32,13 @@ class SliderEntity extends Entity {
|
||||
|
||||
@override
|
||||
Widget _buildStatePartForPage(BuildContext context) {
|
||||
return SimpleEntityState();
|
||||
}
|
||||
return SimpleEntityState(
|
||||
expanded: false,
|
||||
);
|
||||
}*/
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
return SliderStateWidget(
|
||||
expanded: false,
|
||||
);
|
||||
return SliderControlsWidget();
|
||||
}
|
||||
}
|
@ -38,9 +38,15 @@ class EntityCollection {
|
||||
case 'sun': {
|
||||
return SunEntity(rawEntityData);
|
||||
}
|
||||
case "media_player": {
|
||||
return MediaPlayerEntity(rawEntityData);
|
||||
}
|
||||
case 'sensor': {
|
||||
return SensorEntity(rawEntityData);
|
||||
}
|
||||
case 'lock': {
|
||||
return LockEntity(rawEntityData);
|
||||
}
|
||||
case "automation":
|
||||
case "input_boolean":
|
||||
case "switch": {
|
||||
@ -49,6 +55,9 @@ class EntityCollection {
|
||||
case "light": {
|
||||
return LightEntity(rawEntityData);
|
||||
}
|
||||
case "group": {
|
||||
return GroupEntity(rawEntityData);
|
||||
}
|
||||
case "script":
|
||||
case "scene": {
|
||||
return ButtonEntity(rawEntityData);
|
||||
@ -71,6 +80,9 @@ class EntityCollection {
|
||||
case "cover": {
|
||||
return CoverEntity(rawEntityData);
|
||||
}
|
||||
case "fan": {
|
||||
return FanEntity(rawEntityData);
|
||||
}
|
||||
default: {
|
||||
return Entity(rawEntityData);
|
||||
}
|
||||
@ -123,7 +135,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) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
part of '../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class BadgeWidget extends StatelessWidget {
|
||||
@override
|
||||
@ -7,12 +7,12 @@ class BadgeWidget extends StatelessWidget {
|
||||
double iconSize = 26.0;
|
||||
Widget badgeIcon;
|
||||
String onBadgeTextValue;
|
||||
Color iconColor = Entity.badgeColors[entityModel.entity.domain] ??
|
||||
Entity.badgeColors["default"];
|
||||
switch (entityModel.entity.domain) {
|
||||
Color iconColor = EntityColor.badgeColors[entityModel.entityWrapper.entity.domain] ??
|
||||
EntityColor.badgeColors["default"];
|
||||
switch (entityModel.entityWrapper.entity.domain) {
|
||||
case "sun":
|
||||
{
|
||||
badgeIcon = entityModel.entity.state == "below_horizon"
|
||||
badgeIcon = entityModel.entityWrapper.entity.state == "below_horizon"
|
||||
? Icon(
|
||||
MaterialDesignIcons.createIconDataFromIconCode(0xf0dc),
|
||||
size: iconSize,
|
||||
@ -25,10 +25,10 @@ class BadgeWidget extends StatelessWidget {
|
||||
}
|
||||
case "sensor":
|
||||
{
|
||||
onBadgeTextValue = entityModel.entity.unitOfMeasurement;
|
||||
onBadgeTextValue = entityModel.entityWrapper.entity.unitOfMeasurement;
|
||||
badgeIcon = Center(
|
||||
child: Text(
|
||||
"${entityModel.entity.state}",
|
||||
"${entityModel.entityWrapper.entity.state}",
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
textAlign: TextAlign.center,
|
||||
@ -40,14 +40,14 @@ class BadgeWidget extends StatelessWidget {
|
||||
case "device_tracker":
|
||||
{
|
||||
badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData(
|
||||
entityModel.entity, iconSize, Colors.black);
|
||||
onBadgeTextValue = entityModel.entity.state;
|
||||
entityModel.entityWrapper, iconSize, Colors.black);
|
||||
onBadgeTextValue = entityModel.entityWrapper.entity.state;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData(
|
||||
entityModel.entity, iconSize, Colors.black);
|
||||
entityModel.entityWrapper, iconSize, Colors.black);
|
||||
}
|
||||
}
|
||||
Widget onBadgeText;
|
||||
@ -109,7 +109,7 @@ class BadgeWidget extends StatelessWidget {
|
||||
Container(
|
||||
width: 60.0,
|
||||
child: Text(
|
||||
"${entityModel.entity.displayName}",
|
||||
"${entityModel.entityWrapper.displayName}",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 12.0),
|
||||
softWrap: true,
|
||||
@ -120,6 +120,6 @@ class BadgeWidget extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
onTap: () =>
|
||||
eventBus.fire(new ShowEntityPageEvent(entityModel.entity)));
|
||||
eventBus.fire(new ShowEntityPageEvent(entityModel.entityWrapper.entity)));
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
part of '../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class EntityAttributesList extends StatelessWidget {
|
||||
EntityAttributesList({Key key}) : super(key: key);
|
||||
@ -7,14 +7,14 @@ class EntityAttributesList extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
List<Widget> attrs = [];
|
||||
if ((entityModel.entity.attributesToShow == null) ||
|
||||
(entityModel.entity.attributesToShow.contains("all"))) {
|
||||
entityModel.entity.attributes.forEach((name, value) {
|
||||
if ((entityModel.entityWrapper.entity.attributesToShow == null) ||
|
||||
(entityModel.entityWrapper.entity.attributesToShow.contains("all"))) {
|
||||
entityModel.entityWrapper.entity.attributes.forEach((name, value) {
|
||||
attrs.add(_buildSingleAttribute("$name", "$value"));
|
||||
});
|
||||
} else {
|
||||
entityModel.entity.attributesToShow.forEach((String attr) {
|
||||
String attrValue = entityModel.entity.getAttribute("$attr");
|
||||
entityModel.entityWrapper.entity.attributesToShow.forEach((String attr) {
|
||||
String attrValue = entityModel.entityWrapper.entity.getAttribute("$attr");
|
||||
if (attrValue != null) {
|
||||
attrs.add(
|
||||
_buildSingleAttribute("$attr", "$attrValue"));
|
||||
@ -34,7 +34,7 @@ class EntityAttributesList extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
Entity.leftWidgetPadding, Entity.rowPadding, 0.0, 0.0),
|
||||
Sizes.leftWidgetPadding, Sizes.rowPadding, 0.0, 0.0),
|
||||
child: Text(
|
||||
"$name",
|
||||
textAlign: TextAlign.left,
|
||||
@ -44,7 +44,7 @@ class EntityAttributesList extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, Entity.rowPadding, Entity.rightWidgetPadding, 0.0),
|
||||
0.0, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
|
||||
child: Text(
|
||||
"$value",
|
||||
textAlign: TextAlign.right,
|
@ -1,4 +1,4 @@
|
||||
part of '../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class LastUpdatedWidget extends StatelessWidget {
|
||||
@override
|
||||
@ -6,12 +6,12 @@ class LastUpdatedWidget extends StatelessWidget {
|
||||
final entityModel = EntityModel.of(context);
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
Entity.leftWidgetPadding, 0.0, 0.0, 0.0),
|
||||
Sizes.leftWidgetPadding, 0.0, 0.0, 0.0),
|
||||
child: Text(
|
||||
'${entityModel.entity.lastUpdated}',
|
||||
'${entityModel.entityWrapper.entity.lastUpdated}',
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: Entity.smallFontSize, color: Colors.black26),
|
||||
fontSize: Sizes.smallFontSize, color: Colors.black26),
|
||||
),
|
||||
);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
part of '../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class ModeSelectorWidget extends StatelessWidget {
|
||||
|
||||
@ -12,7 +12,7 @@ class ModeSelectorWidget extends StatelessWidget {
|
||||
|
||||
ModeSelectorWidget({
|
||||
Key key,
|
||||
this.caption,
|
||||
@required this.caption,
|
||||
@required this.options,
|
||||
this.value,
|
||||
@required this.onChange,
|
||||
@ -27,7 +27,7 @@ class ModeSelectorWidget extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("$caption", style: TextStyle(
|
||||
fontSize: captionFontSize ?? Entity.stateFontSize
|
||||
fontSize: captionFontSize ?? Sizes.stateFontSize
|
||||
)),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
@ -39,7 +39,7 @@ class ModeSelectorWidget extends StatelessWidget {
|
||||
iconSize: 30.0,
|
||||
isExpanded: true,
|
||||
style: TextStyle(
|
||||
fontSize: valueFontSize ?? Entity.largeFontSize,
|
||||
fontSize: valueFontSize ?? Sizes.largeFontSize,
|
||||
color: Colors.black,
|
||||
),
|
||||
hint: Text("Select ${caption.toLowerCase()}"),
|
||||
@ -55,7 +55,7 @@ class ModeSelectorWidget extends StatelessWidget {
|
||||
)
|
||||
],
|
||||
),
|
||||
Container(height: bottomPadding ?? Entity.rowPadding,)
|
||||
Container(height: bottomPadding ?? Sizes.rowPadding,)
|
||||
],
|
||||
);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
part of '../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class ModeSwitchWidget extends StatelessWidget {
|
||||
|
||||
@ -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 ?? Entity.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;
|
||||
}
|
||||
|
||||
}
|
54
lib/entity_widgets/common/universal_slider.dart
Normal file
54
lib/entity_widgets/common/universal_slider.dart
Normal file
@ -0,0 +1,54 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class UniversalSlider extends StatelessWidget {
|
||||
|
||||
final onChanged;
|
||||
final onChangeEnd;
|
||||
final Widget leading;
|
||||
final Widget closing;
|
||||
final String title;
|
||||
final double min;
|
||||
final double max;
|
||||
final double value;
|
||||
|
||||
const UniversalSlider({Key key, this.onChanged, this.onChangeEnd, this.leading, this.closing, this.title, this.min, this.max, this.value}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List <Widget> row = [];
|
||||
if (leading != null) {
|
||||
row.add(leading);
|
||||
}
|
||||
row.add(
|
||||
Flexible(
|
||||
child: Slider(
|
||||
value: value,
|
||||
min: min,
|
||||
max: max,
|
||||
onChanged: (value) => onChanged(value),
|
||||
onChangeEnd: (value) => onChangeEnd(value),
|
||||
),
|
||||
)
|
||||
);
|
||||
if (closing != null) {
|
||||
row.add(closing);
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(height: Sizes.rowPadding,),
|
||||
Text(
|
||||
"$title",
|
||||
style: TextStyle(fontSize: Sizes.stateFontSize),
|
||||
),
|
||||
Container(height: Sizes.rowPadding,),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: row,
|
||||
),
|
||||
Container(height: Sizes.rowPadding,)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -165,7 +165,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final ClimateEntity entity = entityModel.entity;
|
||||
final ClimateEntity entity = entityModel.entityWrapper.entity;
|
||||
if (_changedHere) {
|
||||
_showPending = (_tmpTemperature != entity.temperature);
|
||||
_changedHere = false;
|
||||
@ -174,7 +174,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
_resetVars(entity);
|
||||
}
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(Entity.leftWidgetPadding, Entity.rowPadding, Entity.rightWidgetPadding, 0.0),
|
||||
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
@ -273,7 +273,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("Target temperature", style: TextStyle(
|
||||
fontSize: Entity.stateFontSize
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
TemperatureControlWidget(
|
||||
value: _tmpTemperature,
|
||||
@ -324,7 +324,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("Target temperature range", style: TextStyle(
|
||||
fontSize: Entity.stateFontSize
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
Row(
|
||||
children: controls,
|
||||
@ -342,7 +342,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
result.addAll(<Widget>[
|
||||
Text(
|
||||
"$_tmpTargetHumidity%",
|
||||
style: TextStyle(fontSize: Entity.largeFontSize),
|
||||
style: TextStyle(fontSize: Sizes.largeFontSize),
|
||||
),
|
||||
Expanded(
|
||||
child: Slider(
|
||||
@ -366,9 +366,9 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, Entity.rowPadding, 0.0, Entity.rowPadding),
|
||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||
child: Text("Target humidity", style: TextStyle(
|
||||
fontSize: Entity.stateFontSize
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
),
|
||||
Row(
|
||||
@ -376,7 +376,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
children: result,
|
||||
),
|
||||
Container(
|
||||
height: Entity.rowPadding,
|
||||
height: Sizes.rowPadding,
|
||||
)
|
||||
],
|
||||
);
|
||||
|
@ -38,15 +38,14 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final CoverEntity entity = entityModel.entity;
|
||||
TheLogger.debug("${entity.state}");
|
||||
final CoverEntity entity = entityModel.entityWrapper.entity;
|
||||
if (_changedHere) {
|
||||
_changedHere = false;
|
||||
} else {
|
||||
_resetVars(entity);
|
||||
}
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(Entity.leftWidgetPadding, Entity.rowPadding, Entity.rightWidgetPadding, 0.0),
|
||||
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
@ -64,9 +63,9 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, Entity.rowPadding, 0.0, Entity.rowPadding),
|
||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||
child: Text("Position", style: TextStyle(
|
||||
fontSize: Entity.stateFontSize
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
),
|
||||
Slider(
|
||||
@ -82,7 +81,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
||||
},
|
||||
onChangeEnd: (double value) => _setNewPosition(entity, value),
|
||||
),
|
||||
Container(height: Entity.rowPadding,)
|
||||
Container(height: Sizes.rowPadding,)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
@ -112,15 +111,15 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
||||
},
|
||||
onChangeEnd: (double value) => _setNewTiltPosition(entity, value),
|
||||
),
|
||||
Container(height: Entity.rowPadding,)
|
||||
Container(height: Sizes.rowPadding,)
|
||||
]);
|
||||
}
|
||||
if (controls.isNotEmpty) {
|
||||
controls.insert(0, Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, Entity.rowPadding, 0.0, Entity.rowPadding),
|
||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||
child: Text("Tilt position", style: TextStyle(
|
||||
fontSize: Entity.stateFontSize
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
));
|
||||
return Column(
|
||||
@ -153,31 +152,31 @@ class CoverTiltControlsWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final CoverEntity entity = entityModel.entity;
|
||||
final CoverEntity entity = entityModel.entityWrapper.entity;
|
||||
List<Widget> buttons = [];
|
||||
if (entity.supportOpenTilt) {
|
||||
buttons.add(IconButton(
|
||||
icon: Icon(
|
||||
MaterialDesignIcons.createIconDataFromIconName(
|
||||
"mdi:arrow-top-right"),
|
||||
size: Entity.iconSize,
|
||||
size: Sizes.iconSize,
|
||||
),
|
||||
onPressed: entity.canTiltBeOpened ? () => _open(entity) : null));
|
||||
} else {
|
||||
buttons.add(Container(
|
||||
width: Entity.iconSize + 20.0,
|
||||
width: Sizes.iconSize + 20.0,
|
||||
));
|
||||
}
|
||||
if (entity.supportStopTilt) {
|
||||
buttons.add(IconButton(
|
||||
icon: Icon(
|
||||
MaterialDesignIcons.createIconDataFromIconName("mdi:stop"),
|
||||
size: Entity.iconSize,
|
||||
size: Sizes.iconSize,
|
||||
),
|
||||
onPressed: () => _stop(entity)));
|
||||
} else {
|
||||
buttons.add(Container(
|
||||
width: Entity.iconSize + 20.0,
|
||||
width: Sizes.iconSize + 20.0,
|
||||
));
|
||||
}
|
||||
if (entity.supportCloseTilt) {
|
||||
@ -185,12 +184,12 @@ class CoverTiltControlsWidget extends StatelessWidget {
|
||||
icon: Icon(
|
||||
MaterialDesignIcons.createIconDataFromIconName(
|
||||
"mdi:arrow-bottom-left"),
|
||||
size: Entity.iconSize,
|
||||
size: Sizes.iconSize,
|
||||
),
|
||||
onPressed: entity.canTiltBeClosed ? () => _close(entity) : null));
|
||||
} else {
|
||||
buttons.add(Container(
|
||||
width: Entity.iconSize + 20.0,
|
||||
width: Sizes.iconSize + 20.0,
|
||||
));
|
||||
}
|
||||
|
||||
|
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -80,7 +80,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final LightEntity entity = entityModel.entity;
|
||||
final LightEntity entity = entityModel.entityWrapper.entity;
|
||||
if (!_changedHere) {
|
||||
_resetState(entity);
|
||||
} else {
|
||||
@ -98,37 +98,20 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
}
|
||||
|
||||
Widget _buildBrightnessControl(LightEntity entity) {
|
||||
if ((entity.supportBrightness) && (_tmpBrightness != null)) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(height: Entity.rowPadding,),
|
||||
Text(
|
||||
"Brightness",
|
||||
style: TextStyle(fontSize: Entity.stateFontSize),
|
||||
),
|
||||
Container(height: Entity.rowPadding,),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Icon(Icons.brightness_5),
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: _tmpBrightness.toDouble(),
|
||||
min: 0.0,
|
||||
max: 255.0,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_tmpBrightness = value.round();
|
||||
});
|
||||
},
|
||||
onChangeEnd: (value) => _setBrightness(entity, value),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Container(height: Entity.rowPadding,)
|
||||
],
|
||||
if ((entity.supportBrightness) && (_tmpBrightness != null) && (entity.state != EntityState.unavailable)) {
|
||||
return UniversalSlider(
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_tmpBrightness = value.round();
|
||||
});
|
||||
},
|
||||
min: 0.0,
|
||||
max: 255.0,
|
||||
onChangeEnd: (value) => _setBrightness(entity, value),
|
||||
value: _tmpBrightness.toDouble(),
|
||||
leading: Icon(Icons.brightness_5),
|
||||
title: "Brightness",
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
@ -137,37 +120,20 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
|
||||
Widget _buildColorTempControl(LightEntity entity) {
|
||||
if ((entity.supportColorTemp) && (_tmpColorTemp != null)) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(height: Entity.rowPadding,),
|
||||
Text(
|
||||
"Color temperature",
|
||||
style: TextStyle(fontSize: Entity.stateFontSize),
|
||||
),
|
||||
Container(height: Entity.rowPadding,),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Text("Cold", style: TextStyle(color: Colors.lightBlue),),
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: _tmpColorTemp.toDouble(),
|
||||
min: entity.minMireds,
|
||||
max: entity.maxMireds,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_tmpColorTemp = value.round();
|
||||
});
|
||||
},
|
||||
onChangeEnd: (value) => _setColorTemp(entity, value),
|
||||
),
|
||||
),
|
||||
Text("Warm", style: TextStyle(color: Colors.amberAccent),),
|
||||
],
|
||||
),
|
||||
Container(height: Entity.rowPadding,)
|
||||
],
|
||||
return UniversalSlider(
|
||||
title: "Color temperature",
|
||||
leading: Text("Cold", style: TextStyle(color: Colors.lightBlue),),
|
||||
value: _tmpColorTemp.toDouble(),
|
||||
onChangeEnd: (value) => _setColorTemp(entity, value),
|
||||
max: entity.maxMireds,
|
||||
min: entity.minMireds,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_tmpColorTemp = value.round();
|
||||
});
|
||||
},
|
||||
closing: Text("Warm", style: TextStyle(color: Colors.amberAccent),),
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
@ -179,7 +145,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Container(height: Entity.rowPadding,),
|
||||
Container(height: Sizes.rowPadding,),
|
||||
RaisedButton(
|
||||
onPressed: () => _showColorPicker(entity),
|
||||
color: _tmpColor ?? Colors.black45,
|
||||
@ -193,7 +159,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(height: 2*Entity.rowPadding,),
|
||||
Container(height: 2*Sizes.rowPadding,),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
|
466
lib/entity_widgets/controls/media_player_widgets.dart
Normal file
466
lib/entity_widgets/controls/media_player_widgets.dart
Normal file
@ -0,0 +1,466 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class MediaPlayerWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final EntityModel entityModel = EntityModel.of(context);
|
||||
final MediaPlayerEntity entity = entityModel.entityWrapper.entity;
|
||||
//TheLogger.debug("stop: ${entity.supportStop}, seek: ${entity.supportSeek}");
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Stack(
|
||||
alignment: AlignmentDirectional.topEnd,
|
||||
children: <Widget>[
|
||||
_buildImage(entity),
|
||||
Positioned(
|
||||
bottom: 0.0,
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
child: Container(
|
||||
color: Colors.black45,
|
||||
child: _buildState(entity),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0.0,
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
child: MediaPlayerProgressWidget()
|
||||
)
|
||||
],
|
||||
),
|
||||
MediaPlayerPlaybackControls()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildState(MediaPlayerEntity entity) {
|
||||
TextStyle style = TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.2
|
||||
);
|
||||
List<Widget> states = [];
|
||||
states.add(Text("${entity.displayName}", style: style));
|
||||
String state = entity.state;
|
||||
if (state == null || state == EntityState.off || state == EntityState.unavailable || state == EntityState.idle) {
|
||||
states.add(Text("${entity.state}", style: style.apply(fontSizeDelta: 4.0),));
|
||||
}
|
||||
if (entity.attributes['media_title'] != null) {
|
||||
states.add(Text(
|
||||
"${entity.attributes['media_title']}",
|
||||
style: style.apply(fontSizeDelta: 6.0, fontWeightDelta: 50),
|
||||
maxLines: 1,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
));
|
||||
}
|
||||
if (entity.attributes['media_content_type'] == "music") {
|
||||
states.add(Text("${entity.attributes['media_artist'] ?? entity.attributes['app_name']}", style: style.apply(fontSizeDelta: 4.0),));
|
||||
} else if (entity.attributes['app_name'] != null) {
|
||||
states.add(Text("${entity.attributes['app_name']}", style: style.apply(fontSizeDelta: 4.0),));
|
||||
}
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: states,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildImage(MediaPlayerEntity entity) {
|
||||
String state = entity.state;
|
||||
if (homeAssistantWebHost != null && entity.entityPicture != null && state != EntityState.off && state != EntityState.unavailable && state != EntityState.idle) {
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: Image(
|
||||
image: CachedNetworkImageProvider("$homeAssistantWebHost${entity.entityPicture}"),
|
||||
height: 240.0,
|
||||
//width: 320.0,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
MaterialDesignIcons.createIconDataFromIconName("mdi:movie"),
|
||||
size: 150.0,
|
||||
color: EntityColor.stateColor("$state"),
|
||||
)
|
||||
],
|
||||
);
|
||||
/*return Container(
|
||||
color: Colors.blue,
|
||||
height: 80.0,
|
||||
);*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MediaPlayerPlaybackControls extends StatelessWidget {
|
||||
|
||||
final bool showMenu;
|
||||
final bool showStop;
|
||||
|
||||
const MediaPlayerPlaybackControls({Key key, this.showMenu: true, this.showStop: false}) : super(key: key);
|
||||
|
||||
|
||||
void _setPower(MediaPlayerEntity entity) {
|
||||
if (entity.state != EntityState.unavailable && entity.state != EntityState.unknown) {
|
||||
if (entity.state == EntityState.off) {
|
||||
TheLogger.debug("${entity.entityId} turn_on");
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_on", entity.entityId,
|
||||
null));
|
||||
} else {
|
||||
TheLogger.debug("${entity.entityId} turn_off");
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_off", entity.entityId,
|
||||
null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _callAction(MediaPlayerEntity entity, String action) {
|
||||
TheLogger.debug("${entity.entityId} $action");
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "$action", entity.entityId,
|
||||
null));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MediaPlayerEntity entity = EntityModel.of(context).entityWrapper.entity;
|
||||
List<Widget> result = [];
|
||||
if (entity.supportTurnOn || entity.supportTurnOff) {
|
||||
result.add(
|
||||
IconButton(
|
||||
icon: Icon(Icons.power_settings_new),
|
||||
onPressed: () => _setPower(entity),
|
||||
iconSize: Sizes.iconSize,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
result.add(
|
||||
Container(
|
||||
width: Sizes.iconSize,
|
||||
)
|
||||
);
|
||||
}
|
||||
List <Widget> centeredControlsChildren = [];
|
||||
if (entity.supportPreviousTrack && entity.state != EntityState.off && entity.state != EntityState.unavailable) {
|
||||
centeredControlsChildren.add(
|
||||
IconButton(
|
||||
icon: Icon(Icons.skip_previous),
|
||||
onPressed: () => _callAction(entity, "media_previous_track"),
|
||||
iconSize: Sizes.iconSize,
|
||||
)
|
||||
);
|
||||
}
|
||||
if (entity.supportPlay || entity.supportPause) {
|
||||
if (entity.state == EntityState.playing) {
|
||||
centeredControlsChildren.add(
|
||||
IconButton(
|
||||
icon: Icon(Icons.pause_circle_filled),
|
||||
color: Colors.blue,
|
||||
onPressed: () => _callAction(entity, "media_pause"),
|
||||
iconSize: Sizes.iconSize*1.8,
|
||||
)
|
||||
);
|
||||
} else if (entity.state == EntityState.paused || entity.state == EntityState.idle) {
|
||||
centeredControlsChildren.add(
|
||||
IconButton(
|
||||
icon: Icon(Icons.play_circle_filled),
|
||||
color: Colors.blue,
|
||||
onPressed: () => _callAction(entity, "media_play"),
|
||||
iconSize: Sizes.iconSize*1.8,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
centeredControlsChildren.add(
|
||||
Container(
|
||||
width: Sizes.iconSize*1.8,
|
||||
height: Sizes.iconSize*2.0,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (entity.supportNextTrack && entity.state != EntityState.off && entity.state != EntityState.unavailable) {
|
||||
centeredControlsChildren.add(
|
||||
IconButton(
|
||||
icon: Icon(Icons.skip_next),
|
||||
onPressed: () => _callAction(entity, "media_next_track"),
|
||||
iconSize: Sizes.iconSize,
|
||||
)
|
||||
);
|
||||
}
|
||||
if (centeredControlsChildren.isNotEmpty) {
|
||||
result.add(
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: showMenu ? MainAxisAlignment.center : MainAxisAlignment.end,
|
||||
children: centeredControlsChildren,
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
result.add(
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 10.0,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
if (showMenu) {
|
||||
result.add(
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
|
||||
"mdi:dots-vertical")),
|
||||
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity))
|
||||
)
|
||||
);
|
||||
} else if (entity.supportStop && entity.state != EntityState.off && entity.state != EntityState.unavailable) {
|
||||
result.add(
|
||||
IconButton(
|
||||
icon: Icon(Icons.stop),
|
||||
onPressed: () => _callAction(entity, "media_stop")
|
||||
)
|
||||
);
|
||||
}
|
||||
return Row(
|
||||
children: result,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MediaPlayerControls extends StatefulWidget {
|
||||
@override
|
||||
_MediaPlayerControlsState createState() => _MediaPlayerControlsState();
|
||||
}
|
||||
|
||||
class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
||||
|
||||
double _newVolumeLevel;
|
||||
bool _changedHere = false;
|
||||
String _newSoundMode;
|
||||
String _newSource;
|
||||
|
||||
void _setVolume(double value, String entityId) {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_newVolumeLevel = value;
|
||||
eventBus.fire(ServiceCallEvent("media_player", "volume_set", entityId, {"volume_level": value}));
|
||||
});
|
||||
}
|
||||
|
||||
void _setVolumeMute(bool isMuted, String entityId) {
|
||||
eventBus.fire(ServiceCallEvent("media_player", "volume_mute", entityId, {"is_volume_muted": isMuted}));
|
||||
}
|
||||
|
||||
void _setVolumeUp(String entityId) {
|
||||
eventBus.fire(ServiceCallEvent("media_player", "volume_up", entityId, null));
|
||||
}
|
||||
|
||||
void _setVolumeDown(String entityId) {
|
||||
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;
|
||||
List<Widget> children = [
|
||||
MediaPlayerPlaybackControls(
|
||||
showMenu: false,
|
||||
)
|
||||
];
|
||||
if (entity.state != EntityState.off && entity.state != EntityState.unknown && entity.state != EntityState.unavailable) {
|
||||
Widget muteWidget;
|
||||
Widget volumeStepWidget;
|
||||
if (entity.supportVolumeMute) {
|
||||
bool isMuted = entity.attributes["is_volume_muted"] ?? false;
|
||||
muteWidget =
|
||||
IconButton(
|
||||
icon: Icon(isMuted ? Icons.volume_off : Icons.volume_up),
|
||||
onPressed: () => _setVolumeMute(!isMuted, entity.entityId)
|
||||
);
|
||||
} else {
|
||||
muteWidget = Container(width: 0.0, height: 0.0,);
|
||||
}
|
||||
if (entity.supportVolumeStep) {
|
||||
volumeStepWidget = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:plus")),
|
||||
onPressed: () => _setVolumeUp(entity.entityId)
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:minus")),
|
||||
onPressed: () => _setVolumeDown(entity.entityId)
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
volumeStepWidget = Container(width: 0.0, height: 0.0,);
|
||||
}
|
||||
if (entity.supportVolumeSet) {
|
||||
if (!_changedHere) {
|
||||
_newVolumeLevel = entity._getDoubleAttributeValue("volume_level");
|
||||
} else {
|
||||
_changedHere = false;
|
||||
}
|
||||
children.add(
|
||||
UniversalSlider(
|
||||
leading: muteWidget,
|
||||
closing: volumeStepWidget,
|
||||
title: "Volume",
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_newVolumeLevel = value;
|
||||
});
|
||||
},
|
||||
value: _newVolumeLevel,
|
||||
onChangeEnd: (value) => _setVolume(value, entity.entityId),
|
||||
max: 1.0,
|
||||
min: 0.0,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
children.add(Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
muteWidget,
|
||||
volumeStepWidget
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MediaPlayerProgressWidget extends StatefulWidget {
|
||||
@override
|
||||
_MediaPlayerProgressWidgetState createState() => _MediaPlayerProgressWidgetState();
|
||||
}
|
||||
|
||||
class _MediaPlayerProgressWidgetState extends State<MediaPlayerProgressWidget> {
|
||||
|
||||
Timer _timer;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final EntityModel entityModel = EntityModel.of(context);
|
||||
final MediaPlayerEntity entity = entityModel.entityWrapper.entity;
|
||||
double progress;
|
||||
try {
|
||||
DateTime lastUpdated = DateTime.parse(
|
||||
entity.attributes["media_position_updated_at"]).toLocal();
|
||||
Duration duration = Duration(seconds: entity._getIntAttributeValue("media_duration") ?? 1);
|
||||
Duration position = Duration(seconds: entity._getIntAttributeValue("media_position") ?? 0);
|
||||
int currentPosition = position.inSeconds;
|
||||
if (entity.state == EntityState.playing) {
|
||||
_timer?.cancel();
|
||||
_timer = Timer(Duration(seconds: 1), () {
|
||||
setState(() {
|
||||
});
|
||||
});
|
||||
int differenceInSeconds = DateTime
|
||||
.now()
|
||||
.difference(lastUpdated)
|
||||
.inSeconds;
|
||||
currentPosition = currentPosition + differenceInSeconds;
|
||||
} else {
|
||||
_timer?.cancel();
|
||||
}
|
||||
progress = currentPosition / duration.inSeconds;
|
||||
return LinearProgressIndicator(
|
||||
value: progress,
|
||||
backgroundColor: Colors.black45,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(EntityColor.stateColor(EntityState.on)),
|
||||
);
|
||||
} catch (e) {
|
||||
_timer?.cancel();
|
||||
progress = 0.0;
|
||||
}
|
||||
return LinearProgressIndicator(
|
||||
value: progress,
|
||||
backgroundColor: Colors.black45,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
70
lib/entity_widgets/controls/slider_controls.dart
Normal file
70
lib/entity_widgets/controls/slider_controls.dart
Normal file
@ -0,0 +1,70 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class SliderControlsWidget extends StatefulWidget {
|
||||
|
||||
SliderControlsWidget({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SliderControlsWidgetState createState() => _SliderControlsWidgetState();
|
||||
}
|
||||
|
||||
class _SliderControlsWidgetState extends State<SliderControlsWidget> {
|
||||
int _multiplier = 1;
|
||||
double _newValue;
|
||||
bool _changedHere = false;
|
||||
|
||||
void setNewState(newValue, domain, entityId) {
|
||||
setState(() {
|
||||
_newValue = newValue;
|
||||
_changedHere = true;
|
||||
});
|
||||
eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId,
|
||||
{"value": "${newValue.toString()}"}));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final SliderEntity entity = entityModel.entityWrapper.entity;
|
||||
if (entity.valueStep < 1) {
|
||||
_multiplier = 10;
|
||||
} else if (entity.valueStep < 0.1) {
|
||||
_multiplier = 100;
|
||||
}
|
||||
if (!_changedHere) {
|
||||
_newValue = entity.doubleState;
|
||||
} else {
|
||||
_changedHere = false;
|
||||
}
|
||||
Widget slider = Slider(
|
||||
min: entity.minValue * _multiplier,
|
||||
max: entity.maxValue * _multiplier,
|
||||
value: (_newValue <= entity.maxValue) &&
|
||||
(_newValue >= entity.minValue)
|
||||
? _newValue * _multiplier
|
||||
: entity.minValue * _multiplier,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_newValue = (value.roundToDouble() / _multiplier);
|
||||
_changedHere = true;
|
||||
});
|
||||
},
|
||||
onChangeEnd: (value) {
|
||||
setNewState(value.roundToDouble() / _multiplier, entity.domain, entity.entityId);
|
||||
},
|
||||
);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"$_newValue",
|
||||
style: TextStyle(
|
||||
fontSize: Sizes.largeFontSize,
|
||||
color: Colors.blue
|
||||
),
|
||||
),
|
||||
slider
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -3,21 +3,33 @@ part of '../main.dart';
|
||||
class DefaultEntityContainer extends StatelessWidget {
|
||||
DefaultEntityContainer({
|
||||
Key key,
|
||||
@required this.state,
|
||||
@required this.height
|
||||
@required this.state
|
||||
}) : super(key: key);
|
||||
|
||||
final Widget state;
|
||||
final double height;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: height,
|
||||
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(),
|
||||
Expanded(
|
||||
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
flex: 3,
|
||||
child: EntityName(),
|
||||
),
|
||||
state
|
||||
|
@ -1,22 +1,28 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class EntityColors {
|
||||
class EntityColor {
|
||||
|
||||
static const badgeColors = {
|
||||
"default": Color.fromRGBO(223, 76, 30, 1.0),
|
||||
"binary_sensor": Color.fromRGBO(3, 155, 229, 1.0)
|
||||
};
|
||||
|
||||
static const _stateColors = {
|
||||
"on": Colors.amber,
|
||||
EntityState.on: Colors.amber,
|
||||
"auto": Colors.amber,
|
||||
"idle": Colors.amber,
|
||||
"playing": Colors.amber,
|
||||
EntityState.idle: Colors.amber,
|
||||
EntityState.playing: Colors.amber,
|
||||
"above_horizon": Colors.amber,
|
||||
"home": Colors.amber,
|
||||
"open": Colors.amber,
|
||||
"off": Color.fromRGBO(68, 115, 158, 1.0),
|
||||
"closed": Color.fromRGBO(68, 115, 158, 1.0),
|
||||
EntityState.home: Colors.amber,
|
||||
EntityState.open: Colors.amber,
|
||||
EntityState.off: Color.fromRGBO(68, 115, 158, 1.0),
|
||||
EntityState.closed: Color.fromRGBO(68, 115, 158, 1.0),
|
||||
"below_horizon": Color.fromRGBO(68, 115, 158, 1.0),
|
||||
"default": Color.fromRGBO(68, 115, 158, 1.0),
|
||||
"heat": Colors.redAccent,
|
||||
"cool": Colors.lightBlue,
|
||||
"unavailable": Colors.black26,
|
||||
"unknown": Colors.black26,
|
||||
EntityState.unavailable: Colors.black26,
|
||||
EntityState.unknown: Colors.black26,
|
||||
};
|
||||
|
||||
static Color stateColor(String state) {
|
||||
@ -33,7 +39,8 @@ class EntityColors {
|
||||
a: c.alpha
|
||||
);
|
||||
} else {
|
||||
return charts.MaterialPalette.getOrderedPalettes(id+1)[id].shadeDefault;
|
||||
double r = id.toDouble() % 10;
|
||||
return charts.MaterialPalette.getOrderedPalettes(10)[r.round()].shadeDefault;
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,10 +50,11 @@ class EntityColors {
|
||||
return c;
|
||||
} else {
|
||||
if (id > -1) {
|
||||
charts.Color c1 = charts.MaterialPalette.getOrderedPalettes(id + 1)[id].shadeDefault;
|
||||
double r = id.toDouble() % 10;
|
||||
charts.Color c1 = charts.MaterialPalette.getOrderedPalettes(10)[r.round()].shadeDefault;
|
||||
return Color.fromARGB(c1.a, c1.r, c1.g, c1.b);
|
||||
} else {
|
||||
return _stateColors["on"];
|
||||
return _stateColors[EntityState.on];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,23 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class EntityIcon extends StatelessWidget {
|
||||
|
||||
final EdgeInsetsGeometry padding;
|
||||
final double iconSize;
|
||||
|
||||
const EntityIcon({Key key, this.iconSize: Sizes.iconSize, this.padding: const EdgeInsets.fromLTRB(
|
||||
Sizes.leftWidgetPadding, 0.0, 12.0, 0.0)}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
return GestureDetector(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
Entity.leftWidgetPadding, 0.0, 12.0, 0.0),
|
||||
child: MaterialDesignIcons.createIconWidgetFromEntityData(
|
||||
entityModel.entity,
|
||||
Entity.iconSize,
|
||||
EntityColors.stateColor(entityModel.entity.state)
|
||||
),
|
||||
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: MaterialDesignIcons.createIconWidgetFromEntityData(
|
||||
entityWrapper,
|
||||
iconSize,
|
||||
EntityColor.stateColor(entityWrapper.entity.state)
|
||||
),
|
||||
onTap: () => entityModel.handleTap
|
||||
? eventBus.fire(new ShowEntityPageEvent(entityModel.entity))
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,23 +1,27 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class EntityName extends StatelessWidget {
|
||||
|
||||
final EdgeInsetsGeometry padding;
|
||||
final TextOverflow textOverflow;
|
||||
final bool wordsWrap;
|
||||
final double fontSize;
|
||||
final TextAlign textAlign;
|
||||
|
||||
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);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
return GestureDetector(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: 10.0),
|
||||
child: Text(
|
||||
"${entityModel.entity.displayName}",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: false,
|
||||
style: TextStyle(fontSize: Entity.nameFontSize),
|
||||
),
|
||||
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: Text(
|
||||
"${entityWrapper.displayName}",
|
||||
overflow: textOverflow,
|
||||
softWrap: wordsWrap,
|
||||
style: TextStyle(fontSize: fontSize),
|
||||
textAlign: textAlign,
|
||||
),
|
||||
onTap: () =>
|
||||
entityModel.handleTap
|
||||
? eventBus.fire(new ShowEntityPageEvent(entityModel.entity))
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
54
lib/entity_widgets/glance_entity_container.dart
Normal file
54
lib/entity_widgets/glance_entity_container.dart
Normal file
@ -0,0 +1,54 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class GlanceEntityContainer extends StatelessWidget {
|
||||
|
||||
final bool showName;
|
||||
final bool showState;
|
||||
|
||||
GlanceEntityContainer({
|
||||
Key key, @required this.showName, @required this.showState,
|
||||
}) : 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,
|
||||
));
|
||||
}
|
||||
result.add(
|
||||
EntityIcon(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
iconSize: Sizes.iconSize,
|
||||
)
|
||||
);
|
||||
if (showState) {
|
||||
result.add(SimpleEntityState(
|
||||
textAlign: TextAlign.center,
|
||||
expanded: false,
|
||||
padding: EdgeInsets.only(top: Sizes.rowPadding),
|
||||
));
|
||||
}
|
||||
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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -69,7 +69,7 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
|
||||
selectionModels: [
|
||||
new charts.SelectionModelConfig(
|
||||
type: charts.SelectionModelType.info,
|
||||
listener: (model) => _onSelectionChanged(model),
|
||||
changedListener: (model) => _onSelectionChanged(model),
|
||||
)
|
||||
],
|
||||
customSeriesRenderers: [
|
||||
@ -156,7 +156,7 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
|
||||
result.add(
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: "value",
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor("_", historyMoment.colorId),
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor("_", historyMoment.colorId),
|
||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) {
|
||||
if (historyMoment.hiddenDot) {
|
||||
return 0.0;
|
||||
@ -179,7 +179,7 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'state',
|
||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 4.0,
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
domainLowerBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
domainUpperBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.endTime ?? DateTime.now(),
|
||||
|
@ -31,6 +31,7 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
||||
|
||||
List _history;
|
||||
bool _needToUpdateHistory;
|
||||
DateTime _historyLastUpdated;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -39,25 +40,32 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
||||
}
|
||||
|
||||
void _loadHistory(HomeAssistant ha, String entityId) {
|
||||
ha.getHistory(entityId).then((history){
|
||||
setState(() {
|
||||
_history = history.isNotEmpty ? history[0] : [];
|
||||
_needToUpdateHistory = false;
|
||||
DateTime now = DateTime.now();
|
||||
if (_historyLastUpdated != null) {
|
||||
TheLogger.debug("History was updated ${now.difference(_historyLastUpdated).inSeconds} seconds ago");
|
||||
}
|
||||
if (_historyLastUpdated == null || now.difference(_historyLastUpdated).inSeconds > 30) {
|
||||
_historyLastUpdated = now;
|
||||
ha.getHistory(entityId).then((history){
|
||||
setState(() {
|
||||
_history = history.isNotEmpty ? history[0] : [];
|
||||
_needToUpdateHistory = false;
|
||||
});
|
||||
}).catchError((e) {
|
||||
TheLogger.error("Error loading $entityId history: $e");
|
||||
setState(() {
|
||||
_history = [];
|
||||
_needToUpdateHistory = false;
|
||||
});
|
||||
});
|
||||
}).catchError((e) {
|
||||
TheLogger.error("Error loading $entityId history: $e");
|
||||
setState(() {
|
||||
_history = [];
|
||||
_needToUpdateHistory = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final HomeAssistantModel homeAssistantModel = HomeAssistantModel.of(context);
|
||||
final EntityModel entityModel = EntityModel.of(context);
|
||||
final Entity entity = entityModel.entity;
|
||||
final Entity entity = entityModel.entityWrapper.entity;
|
||||
if (!_needToUpdateHistory) {
|
||||
_needToUpdateHistory = true;
|
||||
} else {
|
||||
@ -83,7 +91,7 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
||||
}
|
||||
children.add(Divider());
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, Entity.rowPadding),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, Sizes.rowPadding),
|
||||
child: Column(
|
||||
children: children,
|
||||
),
|
||||
@ -91,18 +99,15 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
||||
}
|
||||
|
||||
Widget _selectChartWidget() {
|
||||
TheLogger.debug(" selecting history widget (${widget.config.chartType})");
|
||||
switch (widget.config.chartType) {
|
||||
|
||||
case EntityHistoryWidgetType.simple: {
|
||||
TheLogger.debug(" Simple selected");
|
||||
return SimpleStateHistoryChartWidget(
|
||||
rawHistory: _history,
|
||||
);
|
||||
}
|
||||
|
||||
case EntityHistoryWidgetType.numericState: {
|
||||
TheLogger.debug(" EntityHistory selected");
|
||||
return NumericStateHistoryChartWidget(
|
||||
rawHistory: _history,
|
||||
config: widget.config,
|
||||
@ -110,7 +115,6 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
||||
}
|
||||
|
||||
case EntityHistoryWidgetType.numericAttributes: {
|
||||
TheLogger.debug(" NumericAttributes selected");
|
||||
return CombinedHistoryChartWidget(
|
||||
rawHistory: _history,
|
||||
config: widget.config,
|
||||
|
@ -55,7 +55,7 @@ class HistoryControlWidget extends StatelessWidget {
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: EntityColors.historyStateColor(selectedStates[i], colorIndexes[i]),
|
||||
color: EntityColor.historyStateColor(selectedStates[i], colorIndexes[i]),
|
||||
fontSize: 22.0
|
||||
),
|
||||
)
|
||||
|
@ -57,7 +57,7 @@ class _NumericStateHistoryChartWidgetState extends State<NumericStateHistoryChar
|
||||
selectionModels: [
|
||||
new charts.SelectionModelConfig(
|
||||
type: charts.SelectionModelType.info,
|
||||
listener: (model) => _onSelectionChanged(model),
|
||||
changedListener: (model) => _onSelectionChanged(model),
|
||||
)
|
||||
],
|
||||
),
|
||||
@ -108,7 +108,7 @@ class _NumericStateHistoryChartWidgetState extends State<NumericStateHistoryChar
|
||||
return [
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'State',
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor("on", -1),
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(EntityState.on, -1),
|
||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
measureFn: (EntityHistoryMoment historyMoment, _) => historyMoment.value ?? historyMoment.previousValue,
|
||||
data: data,
|
||||
|
@ -53,7 +53,7 @@ class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartW
|
||||
selectionModels: [
|
||||
new charts.SelectionModelConfig(
|
||||
type: charts.SelectionModelType.info,
|
||||
listener: (model) => _onSelectionChanged(model),
|
||||
changedListener: (model) => _onSelectionChanged(model),
|
||||
)
|
||||
],
|
||||
customSeriesRenderers: [
|
||||
@ -107,7 +107,7 @@ class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartW
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'State',
|
||||
strokeWidthPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 6.0 : 3.0,
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
|
||||
data: data,
|
||||
@ -115,7 +115,7 @@ class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartW
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'State',
|
||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 3.0,
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
|
||||
data: data,
|
||||
@ -123,7 +123,7 @@ class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartW
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'State',
|
||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 3.0,
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColors.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.endTime ?? DateTime.now(),
|
||||
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
|
||||
data: data,
|
||||
|
@ -3,12 +3,12 @@ part of '../main.dart';
|
||||
class EntityModel extends InheritedWidget {
|
||||
const EntityModel({
|
||||
Key key,
|
||||
@required this.entity,
|
||||
@required this.entityWrapper,
|
||||
@required this.handleTap,
|
||||
@required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
final Entity entity;
|
||||
final EntityWrapper entityWrapper;
|
||||
final bool handleTap;
|
||||
|
||||
static EntityModel of(BuildContext context) {
|
||||
|
@ -9,16 +9,19 @@ class ButtonStateWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
return FlatButton(
|
||||
onPressed: (() {
|
||||
_setNewState(entityModel.entity);
|
||||
}),
|
||||
child: Text(
|
||||
"EXECUTE",
|
||||
textAlign: TextAlign.right,
|
||||
style:
|
||||
new TextStyle(fontSize: Entity.stateFontSize, color: Colors.blue),
|
||||
),
|
||||
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),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ class ClimateStateWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final ClimateEntity entity = entityModel.entity;
|
||||
final ClimateEntity entity = entityModel.entityWrapper.entity;
|
||||
String targetTemp = "-";
|
||||
if ((entity.supportTargetTemperature) && (entity.temperature != null)) {
|
||||
targetTemp = "${entity.temperature}";
|
||||
@ -18,40 +18,35 @@ class ClimateStateWidget extends StatelessWidget {
|
||||
}
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, 0.0, Entity.rightWidgetPadding, 0.0),
|
||||
child: GestureDetector(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Text("${entity.state}",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: Entity.stateFontSize,
|
||||
)),
|
||||
Text(" $targetTemp",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontSize: Entity.stateFontSize,
|
||||
))
|
||||
],
|
||||
),
|
||||
entity.attributes["current_temperature"] != null ?
|
||||
Text("Currently: ${entity.attributes["current_temperature"]}",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontSize: Entity.stateFontSize,
|
||||
color: Colors.black45)
|
||||
) :
|
||||
Container(height: 0.0,)
|
||||
],
|
||||
),
|
||||
onTap: () => entityModel.handleTap
|
||||
? eventBus.fire(new ShowEntityPageEvent(entity))
|
||||
: null,
|
||||
0.0, 0.0, Sizes.rightWidgetPadding, 0.0),
|
||||
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(
|
||||
fontSize: Sizes.stateFontSize,
|
||||
color: Colors.black45)
|
||||
) :
|
||||
Container(height: 0.0,)
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
@ -19,42 +19,42 @@ class CoverStateWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final CoverEntity entity = entityModel.entity;
|
||||
final CoverEntity entity = entityModel.entityWrapper.entity;
|
||||
List<Widget> buttons = [];
|
||||
if (entity.supportOpen) {
|
||||
buttons.add(IconButton(
|
||||
icon: Icon(
|
||||
MaterialDesignIcons.createIconDataFromIconName("mdi:arrow-up"),
|
||||
size: Entity.iconSize,
|
||||
size: Sizes.iconSize,
|
||||
),
|
||||
onPressed: entity.canBeOpened ? () => _open(entity) : null));
|
||||
} else {
|
||||
buttons.add(Container(
|
||||
width: Entity.iconSize + 20.0,
|
||||
width: Sizes.iconSize + 20.0,
|
||||
));
|
||||
}
|
||||
if (entity.supportStop) {
|
||||
buttons.add(IconButton(
|
||||
icon: Icon(
|
||||
MaterialDesignIcons.createIconDataFromIconName("mdi:stop"),
|
||||
size: Entity.iconSize,
|
||||
size: Sizes.iconSize,
|
||||
),
|
||||
onPressed: () => _stop(entity)));
|
||||
} else {
|
||||
buttons.add(Container(
|
||||
width: Entity.iconSize + 20.0,
|
||||
width: Sizes.iconSize + 20.0,
|
||||
));
|
||||
}
|
||||
if (entity.supportClose) {
|
||||
buttons.add(IconButton(
|
||||
icon: Icon(
|
||||
MaterialDesignIcons.createIconDataFromIconName("mdi:arrow-down"),
|
||||
size: Entity.iconSize,
|
||||
size: Sizes.iconSize,
|
||||
),
|
||||
onPressed: entity.canBeClosed ? () => _close(entity) : null));
|
||||
} else {
|
||||
buttons.add(Container(
|
||||
width: Entity.iconSize + 20.0,
|
||||
width: Sizes.iconSize + 20.0,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -4,14 +4,14 @@ class DateTimeStateWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final DateTimeEntity entity = entityModel.entity;
|
||||
final DateTimeEntity entity = entityModel.entityWrapper.entity;
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(0.0, 0.0, Entity.rightWidgetPadding, 0.0),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0),
|
||||
child: GestureDetector(
|
||||
child: Text("${entity.formattedState}",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontSize: Entity.stateFontSize,
|
||||
fontSize: Sizes.stateFontSize,
|
||||
)),
|
||||
onTap: () => _handleStateTap(context, entity),
|
||||
));
|
||||
|
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),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -18,11 +18,12 @@ class _SelectStateWidgetState extends State<SelectStateWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final SelectEntity entity = entityModel.entity;
|
||||
final SelectEntity entity = entityModel.entityWrapper.entity;
|
||||
Widget ctrl;
|
||||
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,
|
||||
);
|
||||
|
@ -1,22 +1,37 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class SimpleEntityState extends StatelessWidget {
|
||||
|
||||
final bool expanded;
|
||||
final TextAlign textAlign;
|
||||
final EdgeInsetsGeometry padding;
|
||||
|
||||
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);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, 0.0, Entity.rightWidgetPadding, 0.0),
|
||||
child: GestureDetector(
|
||||
child: Text(
|
||||
"${entityModel.entity.state}${entityModel.entity.unitOfMeasurement}",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontSize: Entity.stateFontSize,
|
||||
)),
|
||||
onTap: () => entityModel.handleTap
|
||||
? eventBus.fire(new ShowEntityPageEvent(entityModel.entity))
|
||||
: null,
|
||||
));
|
||||
Widget result = Padding(
|
||||
padding: padding,
|
||||
child: Text(
|
||||
"${entityModel.entityWrapper.entity.state} ${entityModel.entityWrapper.entity.unitOfMeasurement}",
|
||||
textAlign: textAlign,
|
||||
maxLines: 10,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: new TextStyle(
|
||||
fontSize: Sizes.stateFontSize,
|
||||
)
|
||||
)
|
||||
);
|
||||
if (expanded) {
|
||||
return Flexible(
|
||||
fit: FlexFit.tight,
|
||||
flex: 2,
|
||||
child: result,
|
||||
);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,58 +0,0 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class SliderStateWidget extends StatefulWidget {
|
||||
|
||||
final bool expanded;
|
||||
|
||||
SliderStateWidget({Key key, @required this.expanded}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SliderStateWidgetState createState() => _SliderStateWidgetState();
|
||||
}
|
||||
|
||||
class _SliderStateWidgetState extends State<SliderStateWidget> {
|
||||
int _multiplier = 1;
|
||||
|
||||
void setNewState(newValue, domain, entityId) {
|
||||
eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId,
|
||||
{"value": "${newValue.toString()}"}));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final SliderEntity entity = entityModel.entity;
|
||||
if (entity.valueStep < 1) {
|
||||
_multiplier = 10;
|
||||
} else if (entity.valueStep < 0.1) {
|
||||
_multiplier = 100;
|
||||
}
|
||||
Widget slider = Slider(
|
||||
min: entity.minValue * _multiplier,
|
||||
max: entity.maxValue * _multiplier,
|
||||
value: (entity.doubleState <= entity.maxValue) &&
|
||||
(entity.doubleState >= entity.minValue)
|
||||
? entity.doubleState * _multiplier
|
||||
: entity.minValue * _multiplier,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
entity.state =
|
||||
(value.roundToDouble() / _multiplier).toString();
|
||||
});
|
||||
eventBus.fire(new StateChangedEvent(entity.entityId,
|
||||
(value.roundToDouble() / _multiplier).toString(), true));
|
||||
|
||||
},
|
||||
onChangeEnd: (value) {
|
||||
setNewState(value.roundToDouble() / _multiplier, entity.domain, entity.entityId);
|
||||
},
|
||||
);
|
||||
if (widget.expanded) {
|
||||
return Expanded(
|
||||
child: slider,
|
||||
);
|
||||
} else {
|
||||
return slider;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,20 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class SwitchStateWidget extends StatefulWidget {
|
||||
|
||||
final String domainForService;
|
||||
|
||||
const SwitchStateWidget({Key key, this.domainForService}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SwitchStateWidgetState createState() => _SwitchStateWidgetState();
|
||||
}
|
||||
|
||||
class _SwitchStateWidgetState extends State<SwitchStateWidget> {
|
||||
|
||||
String newState;
|
||||
bool updatedHere = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -14,47 +22,68 @@ class _SwitchStateWidgetState extends State<SwitchStateWidget> {
|
||||
|
||||
void _setNewState(newValue, Entity entity) {
|
||||
setState(() {
|
||||
entity.assumedState = newValue ? 'on' : 'off';
|
||||
newState = newValue ? EntityState.on : EntityState.off;
|
||||
updatedHere = true;
|
||||
});
|
||||
Timer(Duration(seconds: 2), (){
|
||||
setState(() {
|
||||
entity.assumedState = entity.state;
|
||||
newState = entity.state;
|
||||
updatedHere = true;
|
||||
//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
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final entity = entityModel.entity;
|
||||
if ((entity.attributes["assumed_state"] == null) || (entity.attributes["assumed_state"] == false)) {
|
||||
return Switch(
|
||||
value: entity.assumedState == 'on',
|
||||
onChanged: ((switchState) {
|
||||
_setNewState(switchState, entity);
|
||||
}),
|
||||
final entity = entityModel.entityWrapper.entity;
|
||||
if (!updatedHere) {
|
||||
newState = entity.state;
|
||||
} else {
|
||||
updatedHere = false;
|
||||
}
|
||||
if (entity.state == EntityState.unavailable || entity.state == EntityState.unknown) {
|
||||
return SimpleEntityState();
|
||||
} else if ((entity.attributes["assumed_state"] == null) || (entity.attributes["assumed_state"] == false)) {
|
||||
return SizedBox(
|
||||
height: 32.0,
|
||||
child: Switch(
|
||||
value: newState == EntityState.on,
|
||||
onChanged: ((switchState) {
|
||||
_setNewState(switchState, entity);
|
||||
}),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
onPressed: () => _setNewState(false, entity),
|
||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:flash-off")),
|
||||
color: entity.assumedState == 'on' ? Colors.black : Colors.blue,
|
||||
iconSize: Entity.iconSize,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => _setNewState(true, entity),
|
||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:flash")),
|
||||
color: entity.assumedState == 'on' ? Colors.blue : Colors.black,
|
||||
iconSize: Entity.iconSize
|
||||
)
|
||||
],
|
||||
return SizedBox(
|
||||
height: 32.0,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
onPressed: () => _setNewState(false, entity),
|
||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:flash-off")),
|
||||
color: newState == EntityState.on ? Colors.black : Colors.blue,
|
||||
iconSize: Sizes.iconSize,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => _setNewState(true, entity),
|
||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:flash")),
|
||||
color: newState == EntityState.on ? Colors.blue : Colors.black,
|
||||
iconSize: Sizes.iconSize
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -55,7 +55,7 @@ class _TextInputStateWidgetState extends State<TextInputStateWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final TextEntity entity = entityModel.entity;
|
||||
final TextEntity entity = entityModel.entityWrapper.entity;
|
||||
_entityState = entity.state;
|
||||
_entityDomain = entity.domain;
|
||||
_entityId = entity.entityId;
|
||||
@ -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,
|
||||
|
@ -4,7 +4,7 @@ class HomeAssistant {
|
||||
String _webSocketAPIEndpoint;
|
||||
String _password;
|
||||
String _authType;
|
||||
bool _useLovelace;
|
||||
bool _useLovelace = false;
|
||||
|
||||
IOWebSocketChannel _hassioChannel;
|
||||
SendMessageQueue _messageQueue;
|
||||
@ -40,7 +40,13 @@ class HomeAssistant {
|
||||
Duration fetchTimeout = Duration(seconds: 30);
|
||||
Duration connectTimeout = Duration(seconds: 15);
|
||||
|
||||
String get locationName => _instanceConfig["location_name"] ?? "";
|
||||
String get locationName {
|
||||
if (_useLovelace) {
|
||||
return ui?.title ?? "";
|
||||
} else {
|
||||
return _instanceConfig["location_name"] ?? "";
|
||||
}
|
||||
}
|
||||
String get userName => _userName ?? locationName;
|
||||
String get userAvatarText => userName.length > 0 ? userName[0] : "";
|
||||
//int get viewsCount => entities.views.length ?? 0;
|
||||
@ -195,7 +201,12 @@ class HomeAssistant {
|
||||
_connectionCompleter.complete();
|
||||
}
|
||||
} else if (error != null) {
|
||||
eventBus.fire(ShowErrorEvent(error["errorMessage"], error["errorCode"]));
|
||||
if (error is Error) {
|
||||
eventBus.fire(ShowErrorEvent(error.toString(), 12));
|
||||
} else {
|
||||
eventBus.fire(ShowErrorEvent(error["errorMessage"], error["errorCode"]));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,6 +220,7 @@ class HomeAssistant {
|
||||
} 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'}");
|
||||
if (data["id"] == _configMessageId) {
|
||||
_parseConfig(data);
|
||||
} else if (data["id"] == _statesMessageId) {
|
||||
@ -219,12 +231,10 @@ class HomeAssistant {
|
||||
_parseServices(data);
|
||||
} else if (data["id"] == _userInfoMessageId) {
|
||||
_parseUserInfo(data);
|
||||
} else if (data["id"] == _currentMessageId) {
|
||||
TheLogger.debug("[Received] => Request id:$_currentMessageId was successful");
|
||||
}
|
||||
} 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"]}");
|
||||
TheLogger.debug("[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"]}");
|
||||
@ -317,17 +327,40 @@ class HomeAssistant {
|
||||
|
||||
Future callService(String domain, String service, String entityId, Map<String, dynamic> additionalParams) {
|
||||
_incrementMessageId();
|
||||
String message = '{"id": $_currentMessageId, "type": "call_service", "domain": "$domain", "service": "$service", "service_data": {"entity_id": "$entityId"';
|
||||
if (additionalParams != null) {
|
||||
additionalParams.forEach((name, value){
|
||||
if ((value is double) || (value is int) || (value is List)) {
|
||||
message += ', "$name" : $value';
|
||||
} else {
|
||||
message += ', "$name" : "$value"';
|
||||
}
|
||||
});
|
||||
String message = "";
|
||||
if (entityId != null) {
|
||||
message = '{"id": $_currentMessageId, "type": "call_service", "domain": "$domain", "service": "$service", "service_data": {"entity_id": "$entityId"';
|
||||
if (additionalParams != null) {
|
||||
additionalParams.forEach((name, value) {
|
||||
if ((value is double) || (value is int) || (value is List)) {
|
||||
message += ', "$name" : $value';
|
||||
} else {
|
||||
message += ', "$name" : "$value"';
|
||||
}
|
||||
});
|
||||
}
|
||||
message += '}}';
|
||||
} else {
|
||||
message = '{"id": $_currentMessageId, "type": "call_service", "domain": "$domain", "service": "$service"';
|
||||
if (additionalParams != null && additionalParams.isNotEmpty) {
|
||||
message += ', "service_data": {';
|
||||
bool first = true;
|
||||
additionalParams.forEach((name, value) {
|
||||
if (!first) {
|
||||
message += ', ';
|
||||
}
|
||||
if ((value is double) || (value is int) || (value is List)) {
|
||||
message += '"$name" : $value';
|
||||
} else {
|
||||
message += '"$name" : "$value"';
|
||||
}
|
||||
first = false;
|
||||
});
|
||||
|
||||
message += '}';
|
||||
}
|
||||
message += '}';
|
||||
}
|
||||
message += '}}';
|
||||
return _sendMessageRaw(message, true);
|
||||
}
|
||||
|
||||
@ -335,7 +368,7 @@ class HomeAssistant {
|
||||
//TheLogger.debug( "New state for ${eventData['entity_id']}");
|
||||
Map data = Map.from(eventData);
|
||||
entities.updateState(data);
|
||||
eventBus.fire(new StateChangedEvent(data["entity_id"], null, false));
|
||||
eventBus.fire(new StateChangedEvent(data["entity_id"], null));
|
||||
}
|
||||
|
||||
void _parseConfig(Map data) {
|
||||
@ -352,6 +385,7 @@ class HomeAssistant {
|
||||
_userName = data["result"]["name"];
|
||||
} else {
|
||||
_userName = null;
|
||||
TheLogger.warning("There was an error getting current user: $data");
|
||||
}
|
||||
_userInfoCompleter.complete();
|
||||
}
|
||||
@ -364,22 +398,22 @@ class HomeAssistant {
|
||||
if (response["success"] == true) {
|
||||
_rawLovelaceData = response["result"];
|
||||
} else {
|
||||
TheLogger.error("There was an error getting Lovelace config: $response");
|
||||
_rawLovelaceData = null;
|
||||
}
|
||||
_lovelaceCompleter.complete();
|
||||
}
|
||||
|
||||
void _parseLovelace() {
|
||||
ui = HomeAssistantUI();
|
||||
TheLogger.debug("Parsing lovelace config");
|
||||
TheLogger.debug("--Title: ${_rawLovelaceData["title"]}");
|
||||
ui.title = _rawLovelaceData["title"];
|
||||
int viewCounter = 0;
|
||||
TheLogger.debug("--Views count: ${_rawLovelaceData['views'].length}");
|
||||
_rawLovelaceData["views"].forEach((rawView){
|
||||
TheLogger.debug("----view id: ${rawView['id']}");
|
||||
HAView view = HAView(
|
||||
count: viewCounter,
|
||||
id: rawView['id'],
|
||||
id: "${rawView['id']}",
|
||||
name: rawView['title'],
|
||||
iconName: rawView['icon']
|
||||
);
|
||||
@ -395,28 +429,60 @@ class HomeAssistant {
|
||||
List<HACard> result = [];
|
||||
rawCards.forEach((rawCard){
|
||||
if (rawCard["cards"] != null) {
|
||||
TheLogger.debug("------card: ${rawCard['type']} has child cards");
|
||||
result.addAll(_createLovelaceCards(rawCard["cards"]));
|
||||
} else {
|
||||
TheLogger.debug("------card: ${rawCard['type']}");
|
||||
HACard card = HACard(
|
||||
id: "card",
|
||||
name: rawCard["title"],
|
||||
type: rawCard['type']
|
||||
type: rawCard['type'],
|
||||
columnsCount: rawCard['columns'] ?? 4,
|
||||
showName: rawCard['show_name'] ?? true,
|
||||
showState: rawCard['show_state'] ?? true,
|
||||
);
|
||||
rawCard["entities"]?.forEach((rawEntity) {
|
||||
if (rawEntity is String) {
|
||||
if (entities.isExist(rawEntity)) {
|
||||
card.entities.add(entities.get(rawEntity));
|
||||
card.entities.add(EntityWrapper(entity: entities.get(rawEntity)));
|
||||
}
|
||||
} else {
|
||||
if (entities.isExist(rawEntity["entity"])) {
|
||||
card.entities.add(entities.get(rawEntity["entity"]));
|
||||
Entity e = entities.get(rawEntity["entity"]);
|
||||
String tapAction = EntityTapAction.moreInfo;
|
||||
String holdAction = EntityTapAction.none;
|
||||
if (card.type == CardType.glance) {
|
||||
tapAction = rawEntity["tap_action"] ?? EntityTapAction.moreInfo;
|
||||
holdAction = rawEntity["hold_action"] ?? EntityTapAction.none;
|
||||
}
|
||||
card.entities.add(
|
||||
EntityWrapper(
|
||||
entity: e,
|
||||
displayName: rawEntity["name"],
|
||||
icon: rawEntity["icon"],
|
||||
tapAction: tapAction,
|
||||
holdAction: holdAction,
|
||||
tapActionService: rawEntity["service"],
|
||||
tapActionServiceData: rawEntity["service_data"] ?? {"entity_id": e.entityId}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (rawCard["entity"] != null) {
|
||||
card.linkedEntity = entities.get(rawCard["entity"]);
|
||||
var en = rawCard["entity"];
|
||||
if (en is String) {
|
||||
if (entities.isExist(en)) {
|
||||
card.linkedEntity = EntityWrapper(entity: entities.get(en));
|
||||
}
|
||||
} else {
|
||||
if (entities.isExist(en["entity"])) {
|
||||
card.linkedEntity = EntityWrapper(
|
||||
entity: entities.get(en["entity"]),
|
||||
icon: en["icon"],
|
||||
displayName: en["name"]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
result.add(card);
|
||||
}
|
||||
@ -434,13 +500,14 @@ class HomeAssistant {
|
||||
}
|
||||
|
||||
void _createUI() {
|
||||
ui = HomeAssistantUI();
|
||||
if ((_useLovelace) && (_rawLovelaceData != null)) {
|
||||
TheLogger.debug("Creating Lovelace UI");
|
||||
_parseLovelace();
|
||||
} else {
|
||||
ui = HomeAssistantUI();
|
||||
TheLogger.debug("Creating group-based UI");
|
||||
int viewCounter = 0;
|
||||
if (!entities.hasDefaultView) {
|
||||
TheLogger.debug( "--Default view");
|
||||
HAView view = HAView(
|
||||
count: viewCounter,
|
||||
id: "group.default_view",
|
||||
@ -453,7 +520,6 @@ class HomeAssistant {
|
||||
viewCounter += 1;
|
||||
}
|
||||
entities.viewEntities.forEach((viewEntity) {
|
||||
TheLogger.debug( "--View: ${viewEntity.entityId}");
|
||||
HAView view = HAView(
|
||||
count: viewCounter,
|
||||
id: viewEntity.entityId,
|
||||
@ -478,7 +544,7 @@ 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( "$url");
|
||||
TheLogger.debug("[Sending] ==> $url");
|
||||
http.Response historyResponse;
|
||||
if (_authType == "access_token") {
|
||||
historyResponse = await http.get(url, headers: {
|
||||
@ -493,7 +559,7 @@ class HomeAssistant {
|
||||
}
|
||||
var history = json.decode(historyResponse.body);
|
||||
if (history is List) {
|
||||
TheLogger.debug( "Got ${history.first.length} history recors");
|
||||
TheLogger.debug( "[Received] <== ${history.first.length} history recors");
|
||||
return history;
|
||||
} else {
|
||||
return [];
|
||||
|
@ -38,15 +38,7 @@ class _LogViewPageState extends State<LogViewPage> {
|
||||
onPressed: () {
|
||||
Clipboard.setData(new ClipboardData(text: _logData));
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:github-circle")),
|
||||
onPressed: () {
|
||||
String body = "```\n$_logData```";
|
||||
String encodedBody = "${Uri.encodeFull(body)}";
|
||||
HAUtils.launchURL("https://github.com/estevez-dev/ha_client_pub/issues/new?body=$encodedBody");
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: TextField(
|
||||
|
394
lib/main.dart
394
lib/main.dart
@ -4,7 +4,6 @@ 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,8 +13,11 @@ 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';
|
||||
|
||||
part 'entity_class/const.dart';
|
||||
part 'entity_class/entity.class.dart';
|
||||
part 'entity_class/entity_wrapper.class.dart';
|
||||
part 'entity_class/switch_entity.class.dart';
|
||||
part 'entity_class/button_entity.class.dart';
|
||||
part 'entity_class/text_entity.class.dart';
|
||||
@ -26,15 +28,21 @@ part 'entity_class/light_entity.class.dart';
|
||||
part 'entity_class/select_entity.class.dart';
|
||||
part 'entity_class/other_entity.class.dart';
|
||||
part 'entity_class/slider_entity.dart';
|
||||
part 'entity_widgets/badge.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_widgets/common/badge.dart';
|
||||
part 'entity_widgets/model_widgets.dart';
|
||||
part 'entity_widgets/default_entity_container.dart';
|
||||
part 'entity_widgets/entity_attributes_list.dart';
|
||||
part 'entity_widgets/glance_entity_container.dart';
|
||||
part 'entity_widgets/common/entity_attributes_list.dart';
|
||||
part 'entity_widgets/entity_icon.dart';
|
||||
part 'entity_widgets/entity_name.dart';
|
||||
part 'entity_widgets/last_updated.dart';
|
||||
part 'entity_widgets/mode_swicth.dart';
|
||||
part 'entity_widgets/mode_selector.dart';
|
||||
part 'entity_widgets/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/entity_colors.class.dart';
|
||||
part 'entity_widgets/entity_page_container.dart';
|
||||
part 'entity_widgets/history_chart/entity_history.dart';
|
||||
@ -44,7 +52,7 @@ part 'entity_widgets/history_chart/combined_history_chart.dart';
|
||||
part 'entity_widgets/history_chart/history_control_widget.dart';
|
||||
part 'entity_widgets/history_chart/entity_history_moment.dart';
|
||||
part 'entity_widgets/state/switch_state.dart';
|
||||
part 'entity_widgets/state/slider_state.dart';
|
||||
part 'entity_widgets/controls/slider_controls.dart';
|
||||
part 'entity_widgets/state/text_input_state.dart';
|
||||
part 'entity_widgets/state/select_state.dart';
|
||||
part 'entity_widgets/state/simple_state.dart';
|
||||
@ -52,9 +60,12 @@ 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 'settings.page.dart';
|
||||
part 'home_assistant.class.dart';
|
||||
part 'log.page.dart';
|
||||
@ -65,8 +76,10 @@ part 'entity_collection.class.dart';
|
||||
part 'ui_class/ui.dart';
|
||||
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_header_widget.dart';
|
||||
@ -74,7 +87,7 @@ part 'ui_widgets/card_header_widget.dart';
|
||||
|
||||
EventBus eventBus = new EventBus();
|
||||
const String appName = "HA Client";
|
||||
const appVersion = "0.3.4";
|
||||
const appVersion = "0.3.10";
|
||||
|
||||
String homeAssistantWebHost;
|
||||
|
||||
@ -127,7 +140,6 @@ class MainPage extends StatefulWidget {
|
||||
|
||||
class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
HomeAssistant _homeAssistant;
|
||||
EntityCollection _entities;
|
||||
//Map _instanceConfig;
|
||||
String _webSocketApiEndpoint;
|
||||
String _password;
|
||||
@ -140,7 +152,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;
|
||||
@ -151,6 +162,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
_settingsLoaded = false;
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
TheLogger.debug("<!!!> Creating new HomeAssistant instance");
|
||||
_homeAssistant = HomeAssistant();
|
||||
|
||||
_settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) {
|
||||
@ -169,10 +181,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
_subscribe();
|
||||
_refreshData();
|
||||
}, onError: (_) {
|
||||
setState(() {
|
||||
_isLoading = 2;
|
||||
});
|
||||
_showErrorSnackBar(message: _, errorCode: 5);
|
||||
_showErrorBottomBar(message: _, errorCode: 5);
|
||||
});
|
||||
}
|
||||
|
||||
@ -204,15 +213,8 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
|
||||
_subscribe() {
|
||||
if (_stateSubscription == null) {
|
||||
//TODO Move to homeAssistant or remove
|
||||
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
|
||||
setState(() {
|
||||
if (event.localChange) {
|
||||
_entities
|
||||
.get(event.entityId)
|
||||
.state = event.newState;
|
||||
}
|
||||
});
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
if (_serviceCallSubscription == null) {
|
||||
@ -226,7 +228,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,25 +240,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;
|
||||
});
|
||||
_hideBottomBar();
|
||||
_showInfoBottomBar(progress: true,);
|
||||
await _homeAssistant.fetch().then((result) {
|
||||
setState(() {
|
||||
//_instanceConfig = _homeAssistant.instanceConfig;
|
||||
_entities = _homeAssistant.entities;
|
||||
//_uiViewsCount = _homeAssistant.viewsCount;
|
||||
//TheLogger.debug("_uiViewsCount=$_uiViewsCount");
|
||||
_isLoading = 0;
|
||||
});
|
||||
_hideBottomBar();
|
||||
}).catchError((e) {
|
||||
_setErrorState(e);
|
||||
});
|
||||
@ -264,24 +258,34 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
_setErrorState(e) {
|
||||
setState(() {
|
||||
_isLoading = 2;
|
||||
});
|
||||
_showErrorSnackBar(
|
||||
message: e != null ? e["errorMessage"] ?? "$e" : "Unknown error",
|
||||
errorCode: e["errorCode"] != null ? e["errorCode"] : 99
|
||||
);
|
||||
if (e is Error) {
|
||||
TheLogger.error(e.toString());
|
||||
TheLogger.error("${e.stackTrace}");
|
||||
_showErrorBottomBar(
|
||||
message: "There was some error",
|
||||
errorCode: 13
|
||||
);
|
||||
} else {
|
||||
_showErrorBottomBar(
|
||||
message: e != null ? e["errorMessage"] ?? "$e" : "Unknown error",
|
||||
errorCode: e["errorCode"] != null ? e["errorCode"] : 99
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _callService(String domain, String service, String entityId, Map<String, dynamic> additionalParams) {
|
||||
_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),
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -291,61 +295,13 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
|
||||
if (_homeAssistant.ui.views.isNotEmpty) {
|
||||
_homeAssistant.ui.views.forEach((HAView view) {
|
||||
if (view.linkedEntity == null) {
|
||||
result.add(
|
||||
Tab(
|
||||
icon:
|
||||
Icon(
|
||||
MaterialDesignIcons.createIconDataFromIconName(
|
||||
view.iconName ?? "mdi:home-assistant"),
|
||||
size: 24.0,
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
result.add(
|
||||
Tab(
|
||||
icon: MaterialDesignIcons.createIconWidgetFromEntityData(
|
||||
view.linkedEntity, 24.0, null) ??
|
||||
Icon(
|
||||
MaterialDesignIcons.createIconDataFromIconName(
|
||||
"mdi:home-assistant"),
|
||||
size: 24.0,
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
result.add(view.buildTab());
|
||||
});
|
||||
}
|
||||
|
||||
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(
|
||||
@ -394,14 +350,29 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
title: Text("Report an issue"),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
HAUtils.launchURL("https://github.com/estevez-dev/ha_client_pub/issues/new");
|
||||
HAUtils.launchURL("https://github.com/estevez-dev/ha_client/issues/new");
|
||||
},
|
||||
),
|
||||
Divider(),
|
||||
new AboutListTile(
|
||||
aboutBoxChildren: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
HAUtils.launchURL("http://www.keyboardcrumbs.io/");
|
||||
},
|
||||
child: Text(
|
||||
"www.keyboardcrumbs.io",
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
applicationName: appName,
|
||||
applicationVersion: appVersion,
|
||||
applicationLegalese: "Keyboard Crumbs | www.keyboardcrumbs.io",
|
||||
applicationLegalese: "Keyboard Crumbs",
|
||||
)
|
||||
]);
|
||||
}
|
||||
@ -412,21 +383,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();
|
||||
},
|
||||
);
|
||||
@ -435,10 +436,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');
|
||||
},
|
||||
);
|
||||
@ -446,10 +447,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');
|
||||
},
|
||||
);
|
||||
@ -457,10 +458,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();
|
||||
},
|
||||
);
|
||||
@ -468,76 +469,157 @@ 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();
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
_bottomBarAction = FlatButton(
|
||||
child: Text("Reload", style: textStyle),
|
||||
onPressed: () {
|
||||
//_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>();
|
||||
|
||||
Scaffold _buildScaffold(bool empty) {
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: AppBar(
|
||||
title: _buildAppTitle(),
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.menu),
|
||||
onPressed: () {
|
||||
_scaffoldKey.currentState.openDrawer();
|
||||
setState(() {
|
||||
_accountMenuExpanded = false;
|
||||
});
|
||||
},
|
||||
),
|
||||
primary: true,
|
||||
bottom: empty ? null : TabBar(
|
||||
tabs: buildUIViewTabs(),
|
||||
isScrollable: true,
|
||||
),
|
||||
),
|
||||
drawer: _buildAppDrawer(),
|
||||
body: empty ?
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
MaterialDesignIcons.createIconDataFromIconName("mdi:home-assistant"),
|
||||
size: 100.0,
|
||||
color: _isLoading == 2 ? Colors.redAccent : Colors.blue,
|
||||
),
|
||||
]
|
||||
Widget _buildScaffoldBody(bool empty) {
|
||||
return NestedScrollView(
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
return <Widget>[
|
||||
SliverAppBar(
|
||||
floating: true,
|
||||
pinned: true,
|
||||
primary: true,
|
||||
title: Text(_homeAssistant != null ? _homeAssistant.locationName : ""),
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.menu),
|
||||
onPressed: () {
|
||||
_scaffoldKey.currentState.openDrawer();
|
||||
setState(() {
|
||||
_accountMenuExpanded = false;
|
||||
});
|
||||
},
|
||||
),
|
||||
bottom: empty ? null : TabBar(
|
||||
tabs: buildUIViewTabs(),
|
||||
isScrollable: true,
|
||||
),
|
||||
),
|
||||
)
|
||||
:
|
||||
_homeAssistant.buildViews(context, _useLovelaceUI)
|
||||
|
||||
];
|
||||
},
|
||||
body: empty ?
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
MaterialDesignIcons.createIconDataFromIconName("mdi:home-assistant"),
|
||||
size: 100.0,
|
||||
color: Colors.blue,
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
:
|
||||
_homeAssistant.buildViews(context, _useLovelaceUI),
|
||||
);
|
||||
}
|
||||
|
||||
@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.entities.isEmpty) {
|
||||
return _buildScaffold(true);
|
||||
if (_homeAssistant.ui == null || _homeAssistant.ui.views == null) {
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
primary: false,
|
||||
drawer: _buildAppDrawer(),
|
||||
bottomNavigationBar: bottomBar,
|
||||
body: _buildScaffoldBody(true)
|
||||
);
|
||||
} else {
|
||||
return DefaultTabController(
|
||||
length: _homeAssistant.ui.views.length,
|
||||
child: _buildScaffold(false)
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
drawer: _buildAppDrawer(),
|
||||
primary: false,
|
||||
bottomNavigationBar: bottomBar,
|
||||
body: DefaultTabController(
|
||||
length: _homeAssistant.ui?.views?.length ?? 0,
|
||||
child: _buildScaffoldBody(false),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,9 @@ 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"
|
||||
};
|
||||
|
||||
static Map _defaultIconsByDeviceClass = {
|
||||
@ -2880,17 +2883,17 @@ class MaterialDesignIcons {
|
||||
"mdi:blank": 0xf68c
|
||||
};
|
||||
|
||||
static Widget createIconWidgetFromEntityData(Entity data, double size, Color color) {
|
||||
static Widget createIconWidgetFromEntityData(EntityWrapper data, double size, Color color) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
if (data.entityPicture != null) {
|
||||
if (data.entity.entityPicture != null) {
|
||||
if (homeAssistantWebHost != null) {
|
||||
return CircleAvatar(
|
||||
radius: size/2,
|
||||
backgroundColor: Colors.white,
|
||||
backgroundImage: CachedNetworkImageProvider(
|
||||
"$homeAssistantWebHost${data.entityPicture}",
|
||||
"$homeAssistantWebHost${data.entity.entityPicture}",
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@ -2902,8 +2905,8 @@ class MaterialDesignIcons {
|
||||
if (iconName.length > 0) {
|
||||
iconCode = getIconCodeByIconName(iconName);
|
||||
} else {
|
||||
iconCode = getDefaultIconByEntityId(data.entityId,
|
||||
data.deviceClass, data.state); //
|
||||
iconCode = getDefaultIconByEntityId(data.entity.entityId,
|
||||
data.entity.deviceClass, data.entity.state); //
|
||||
}
|
||||
return Icon(
|
||||
IconData(iconCode, fontFamily: 'Material Design Icons'),
|
||||
|
@ -22,20 +22,10 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
String _newAuthType = "access_token";
|
||||
bool _useLovelace = false;
|
||||
bool _newUseLovelace = false;
|
||||
bool _edited = false;
|
||||
FocusNode _domainFocusNode;
|
||||
FocusNode _portFocusNode;
|
||||
FocusNode _passwordFocusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_domainFocusNode = FocusNode();
|
||||
_portFocusNode = FocusNode();
|
||||
_passwordFocusNode = FocusNode();
|
||||
_domainFocusNode.addListener(_checkConfigChanged);
|
||||
_portFocusNode.addListener(_checkConfigChanged);
|
||||
_passwordFocusNode.addListener(_checkConfigChanged);
|
||||
_loadSettings();
|
||||
}
|
||||
|
||||
@ -56,15 +46,14 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
});
|
||||
}
|
||||
|
||||
void _checkConfigChanged() {
|
||||
setState(() {
|
||||
_edited = ((_newHassioPassword != _hassioPassword) ||
|
||||
(_newHassioPort != _hassioPort) ||
|
||||
(_newHassioDomain != _hassioDomain) ||
|
||||
(_newSocketProtocol != _socketProtocol) ||
|
||||
(_newAuthType != _authType) ||
|
||||
(_newUseLovelace != _useLovelace));
|
||||
});
|
||||
bool _checkConfigChanged() {
|
||||
return ((_newHassioPassword != _hassioPassword) ||
|
||||
(_newHassioPort != _hassioPort) ||
|
||||
(_newHassioDomain != _hassioDomain) ||
|
||||
(_newSocketProtocol != _socketProtocol) ||
|
||||
(_newAuthType != _authType) ||
|
||||
(_newUseLovelace != _useLovelace));
|
||||
|
||||
}
|
||||
|
||||
_saveSettings() async {
|
||||
@ -92,16 +81,23 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.check),
|
||||
onPressed: _edited ? (){
|
||||
_saveSettings().then((r){
|
||||
onPressed: (){
|
||||
if (_checkConfigChanged()) {
|
||||
TheLogger.debug("Settings changed. Saving...");
|
||||
_saveSettings().then((r) {
|
||||
Navigator.pop(context);
|
||||
eventBus.fire(SettingsChangedEvent(true));
|
||||
});
|
||||
} : null
|
||||
} else {
|
||||
TheLogger.debug("Settings was not changed");
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
)
|
||||
],
|
||||
),
|
||||
body: ListView(
|
||||
scrollDirection: Axis.vertical,
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
children: <Widget>[
|
||||
Text(
|
||||
@ -117,8 +113,9 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
Switch(
|
||||
value: (_newSocketProtocol == "wss"),
|
||||
onChanged: (value) {
|
||||
_newSocketProtocol = value ? "wss" : "ws";
|
||||
_checkConfigChanged();
|
||||
setState(() {
|
||||
_newSocketProtocol = value ? "wss" : "ws";
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
@ -136,9 +133,7 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
),
|
||||
onChanged: (value) {
|
||||
_newHassioDomain = value;
|
||||
},
|
||||
focusNode: _domainFocusNode,
|
||||
onEditingComplete: _checkConfigChanged,
|
||||
}
|
||||
),
|
||||
new TextField(
|
||||
decoration: InputDecoration(
|
||||
@ -153,27 +148,31 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
),
|
||||
onChanged: (value) {
|
||||
_newHassioPort = value;
|
||||
//_saveSettings();
|
||||
},
|
||||
focusNode: _portFocusNode,
|
||||
onEditingComplete: _checkConfigChanged,
|
||||
}
|
||||
),
|
||||
new Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text("Login with access token (HA >= 0.78.0)"),
|
||||
Flexible(
|
||||
child: Text(
|
||||
"Login with access token (HA >= 0.78.0)",
|
||||
softWrap: true,
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: (_newAuthType == "access_token"),
|
||||
onChanged: (value) {
|
||||
_newAuthType = value ? "access_token" : "api_password";
|
||||
_checkConfigChanged();
|
||||
//_saveSettings();
|
||||
setState(() {
|
||||
_newAuthType = value ? "access_token" : "api_password";
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
new TextField(
|
||||
decoration: InputDecoration(
|
||||
labelText: _authType == "access_token" ? "Access token" : "API password"
|
||||
labelText: _newAuthType == "access_token" ? "Access token" : "API password"
|
||||
),
|
||||
controller: new TextEditingController.fromValue(
|
||||
new TextEditingValue(
|
||||
@ -184,10 +183,7 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
),
|
||||
onChanged: (value) {
|
||||
_newHassioPassword = value;
|
||||
//_saveSettings();
|
||||
},
|
||||
focusNode: _passwordFocusNode,
|
||||
onEditingComplete: _checkConfigChanged,
|
||||
}
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20.0),
|
||||
@ -205,8 +201,9 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
Switch(
|
||||
value: _newUseLovelace,
|
||||
onChanged: (value) {
|
||||
_newUseLovelace = value;
|
||||
_checkConfigChanged();
|
||||
setState(() {
|
||||
_newUseLovelace = value;
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
@ -218,12 +215,6 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_domainFocusNode.removeListener(_checkConfigChanged);
|
||||
_portFocusNode.removeListener(_checkConfigChanged);
|
||||
_passwordFocusNode.removeListener(_checkConfigChanged);
|
||||
_domainFocusNode.dispose();
|
||||
_portFocusNode.dispose();
|
||||
_passwordFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,42 +1,59 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class HACard {
|
||||
List<Entity> entities = [];
|
||||
Entity linkedEntity;
|
||||
List<EntityWrapper> entities = [];
|
||||
EntityWrapper linkedEntity;
|
||||
String name;
|
||||
String id;
|
||||
String type;
|
||||
bool showName;
|
||||
bool showState;
|
||||
int columnsCount;
|
||||
|
||||
HACard({
|
||||
this.name,
|
||||
this.id,
|
||||
this.linkedEntity,
|
||||
this.columnsCount: 4,
|
||||
this.showName: true,
|
||||
this.showState: true,
|
||||
@required this.type
|
||||
});
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
switch (type) {
|
||||
|
||||
case "entities": {
|
||||
case CardType.entities: {
|
||||
return EntitiesCardWidget(
|
||||
card: this,
|
||||
);
|
||||
}
|
||||
|
||||
case "weather-forecast":
|
||||
case "thermostat":
|
||||
case "sensor":
|
||||
case "plant-status":
|
||||
case "picture-entity":
|
||||
case "picture-elements":
|
||||
case "picture":
|
||||
case "map":
|
||||
case "iframe":
|
||||
case "gauge":
|
||||
case "entity-button":
|
||||
case "conditional":
|
||||
case "alarm-panel":
|
||||
case "media-control": {
|
||||
case CardType.glance: {
|
||||
return GlanceCardWidget(
|
||||
card: this,
|
||||
);
|
||||
}
|
||||
|
||||
case CardType.mediaControl: {
|
||||
return MediaControlCardWidget(
|
||||
card: this,
|
||||
);
|
||||
}
|
||||
|
||||
case CardType.weatherForecast:
|
||||
case CardType.thermostat:
|
||||
case CardType.sensor:
|
||||
case CardType.plantStatus:
|
||||
case CardType.pictureEntity:
|
||||
case CardType.pictureElements:
|
||||
case CardType.picture:
|
||||
case CardType.map:
|
||||
case CardType.iframe:
|
||||
case CardType.gauge:
|
||||
case CardType.entityButton:
|
||||
case CardType.conditional:
|
||||
case CardType.alarmPanel: {
|
||||
return UnsupportedCardWidget(
|
||||
card: this,
|
||||
);
|
||||
|
15
lib/ui_class/sizes_class.dart
Normal file
15
lib/ui_class/sizes_class.dart
Normal file
@ -0,0 +1,15 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class Sizes {
|
||||
static const rightWidgetPadding = 14.0;
|
||||
static const leftWidgetPadding = 8.0;
|
||||
static const extendedWidgetHeight = 50.0;
|
||||
static const iconSize = 28.0;
|
||||
static const largeIconSize = 34.0;
|
||||
static const stateFontSize = 15.0;
|
||||
static const nameFontSize = 15.0;
|
||||
static const smallFontSize = 14.0;
|
||||
static const largeFontSize = 24.0;
|
||||
static const inputWidth = 160.0;
|
||||
static const rowPadding = 10.0;
|
||||
}
|
@ -2,6 +2,7 @@ part of '../main.dart';
|
||||
|
||||
class HomeAssistantUI {
|
||||
List<HAView> views;
|
||||
String title;
|
||||
|
||||
HomeAssistantUI() {
|
||||
views = [];
|
||||
@ -9,7 +10,7 @@ class HomeAssistantUI {
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return TabBarView(
|
||||
children: _buildViews(context)
|
||||
children: _buildViews(context)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -23,41 +23,89 @@ class HAView {
|
||||
|
||||
void _fillView(List<Entity> childEntities) {
|
||||
List<HACard> autoGeneratedCards = [];
|
||||
childEntities.forEach((entity) {
|
||||
if (entity.isBadge) {
|
||||
badges.add(entity);
|
||||
TheLogger.debug("----Badge: ${entity.entityId}");
|
||||
} else {
|
||||
if (!entity.isGroup) {
|
||||
String groupIdToAdd = "${entity.domain}.${entity.domain}$count";
|
||||
if (autoGeneratedCards.every((HACard card) => card.id != groupIdToAdd )) {
|
||||
HACard card = HACard(
|
||||
id: groupIdToAdd,
|
||||
name: entity.domain,
|
||||
type: "entities"
|
||||
);
|
||||
TheLogger.debug("----Creating card: $groupIdToAdd");
|
||||
card.entities.add(entity);
|
||||
autoGeneratedCards.add(card);
|
||||
} else {
|
||||
autoGeneratedCards.firstWhere((card) => card.id == groupIdToAdd).entities.add(entity);
|
||||
}
|
||||
} else {
|
||||
TheLogger.debug("----Card: ${entity.entityId}");
|
||||
badges.addAll(childEntities.where((entity){ return entity.isBadge;}));
|
||||
childEntities.where((entity){ return entity.domain == "media_player";}).forEach((e){
|
||||
HACard card = HACard(
|
||||
name: e.displayName,
|
||||
id: e.entityId,
|
||||
linkedEntity: EntityWrapper(entity: e),
|
||||
type: "media-control"
|
||||
);
|
||||
cards.add(card);
|
||||
});
|
||||
childEntities.where((e){return (!e.isBadge && e.domain != "media_player");}).forEach((entity) {
|
||||
if (!entity.isGroup) {
|
||||
String groupIdToAdd = "${entity.domain}.${entity.domain}$count";
|
||||
if (autoGeneratedCards.every((HACard card) => card.id != groupIdToAdd )) {
|
||||
HACard card = HACard(
|
||||
name: entity.displayName,
|
||||
id: entity.entityId,
|
||||
linkedEntity: entity,
|
||||
id: groupIdToAdd,
|
||||
name: entity.domain,
|
||||
type: "entities"
|
||||
);
|
||||
card.entities.addAll(entity.childEntities);
|
||||
cards.add(card);
|
||||
card.entities.add(EntityWrapper(entity: entity));
|
||||
autoGeneratedCards.add(card);
|
||||
} else {
|
||||
autoGeneratedCards.firstWhere((card) => card.id == groupIdToAdd).entities.add(EntityWrapper(entity: entity));
|
||||
}
|
||||
} else {
|
||||
HACard card = HACard(
|
||||
name: entity.displayName,
|
||||
id: entity.entityId,
|
||||
linkedEntity: EntityWrapper(entity: entity),
|
||||
type: "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"
|
||||
);
|
||||
cards.add(mediaCard);
|
||||
});
|
||||
cards.add(card);
|
||||
}
|
||||
});
|
||||
cards.addAll(autoGeneratedCards);
|
||||
}
|
||||
|
||||
Widget buildTab() {
|
||||
if (linkedEntity == null) {
|
||||
if (iconName != null) {
|
||||
return
|
||||
Tab(
|
||||
icon:
|
||||
Icon(
|
||||
MaterialDesignIcons.createIconDataFromIconName(
|
||||
iconName ?? "mdi:home-assistant"),
|
||||
size: 24.0,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return
|
||||
Tab(
|
||||
text: name.toUpperCase(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (linkedEntity.icon != null && linkedEntity.icon.length > 0) {
|
||||
return Tab(
|
||||
icon: Icon(
|
||||
MaterialDesignIcons.createIconDataFromIconName(
|
||||
linkedEntity.icon),
|
||||
size: 24.0,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return Tab(
|
||||
text: linkedEntity.displayName.toUpperCase(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return ViewWidget(
|
||||
view: this,
|
||||
|
@ -14,7 +14,7 @@ class CardHeaderWidget extends StatelessWidget {
|
||||
title: Text("$name",
|
||||
textAlign: TextAlign.left,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: 25.0)),
|
||||
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: Sizes.largeFontSize)),
|
||||
);
|
||||
} else {
|
||||
result = new Container(width: 0.0, height: 0.0);
|
||||
|
@ -11,7 +11,7 @@ class EntitiesCardWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if ((card.linkedEntity!= null) && (card.linkedEntity.isHidden)) {
|
||||
if ((card.linkedEntity!= null) && (card.linkedEntity.entity.isHidden)) {
|
||||
return Container(width: 0.0, height: 0.0,);
|
||||
}
|
||||
List<Widget> body = [];
|
||||
@ -24,12 +24,16 @@ class EntitiesCardWidget extends StatelessWidget {
|
||||
|
||||
List<Widget> _buildCardBody(BuildContext context) {
|
||||
List<Widget> result = [];
|
||||
card.entities.forEach((Entity entity) {
|
||||
if (!entity.isHidden) {
|
||||
card.entities.forEach((EntityWrapper entity) {
|
||||
if (!entity.entity.isHidden) {
|
||||
result.add(
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0),
|
||||
child: entity.buildDefaultWidget(context),
|
||||
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||
child: EntityModel(
|
||||
entityWrapper: entity,
|
||||
handleTap: true,
|
||||
child: entity.entity.buildDefaultWidget(context)
|
||||
),
|
||||
));
|
||||
}
|
||||
});
|
||||
|
52
lib/ui_widgets/glance_card.dart
Normal file
52
lib/ui_widgets/glance_card.dart
Normal file
@ -0,0 +1,52 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class GlanceCardWidget extends StatelessWidget {
|
||||
|
||||
final HACard card;
|
||||
|
||||
const GlanceCardWidget({
|
||||
Key key,
|
||||
this.card
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if ((card.linkedEntity!= null) && (card.linkedEntity.entity.isHidden)) {
|
||||
return Container(width: 0.0, height: 0.0,);
|
||||
}
|
||||
List<Widget> rows = [];
|
||||
rows.add(CardHeaderWidget(name: card.name));
|
||||
rows.add(_buildRows(context));
|
||||
return Card(
|
||||
child: new Column(mainAxisSize: MainAxisSize.min, children: rows)
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRows(BuildContext context) {
|
||||
List<Widget> result = [];
|
||||
List<EntityWrapper> toShow = card.entities.where((entity) {return !entity.entity.isHidden;}).toList();
|
||||
int columnsCount = toShow.length >= card.columnsCount ? card.columnsCount : toShow.length;
|
||||
|
||||
toShow.forEach((EntityWrapper entity) {
|
||||
result.add(
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1/columnsCount,
|
||||
child: EntityModel(
|
||||
entityWrapper: entity,
|
||||
child: entity.entity.buildGlanceWidget(context, card.showName, card.showState),
|
||||
handleTap: true
|
||||
),
|
||||
)
|
||||
);
|
||||
});
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, 2*Sizes.rowPadding),
|
||||
child: Wrap(
|
||||
//alignment: WrapAlignment.spaceAround,
|
||||
runSpacing: Sizes.rowPadding*2,
|
||||
children: result,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -11,17 +11,19 @@ class MediaControlCardWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if ((card.linkedEntity!= null) && (card.linkedEntity.isHidden)) {
|
||||
if ((card.linkedEntity == null) || (card.linkedEntity.entity.isHidden)) {
|
||||
return Container(width: 0.0, height: 0.0,);
|
||||
}
|
||||
List<Widget> body = [];
|
||||
|
||||
return Card(
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: body
|
||||
child: EntityModel(
|
||||
entityWrapper: card.linkedEntity,
|
||||
handleTap: null,
|
||||
child: MediaPlayerWidget()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -11,7 +11,7 @@ class UnsupportedCardWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if ((card.linkedEntity!= null) && (card.linkedEntity.isHidden)) {
|
||||
if ((card.linkedEntity!= null) && (card.linkedEntity.entity.isHidden)) {
|
||||
return Container(width: 0.0, height: 0.0,);
|
||||
}
|
||||
List<Widget> body = [];
|
||||
@ -31,14 +31,18 @@ class UnsupportedCardWidget extends StatelessWidget {
|
||||
if (card.linkedEntity != null) {
|
||||
result.addAll(<Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(0.0, Entity.rowPadding, 0.0, Entity.rowPadding),
|
||||
child: card.linkedEntity.buildDefaultWidget(context),
|
||||
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(Entity.leftWidgetPadding, Entity.rowPadding, Entity.rightWidgetPadding, Entity.rowPadding),
|
||||
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
|
||||
child: Text("'${card.type}' card is not supported yet"),
|
||||
),
|
||||
]);
|
||||
|
@ -35,6 +35,7 @@ class ViewWidgetState extends State<ViewWidget> {
|
||||
return RefreshIndicator(
|
||||
color: Colors.amber,
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: _buildChildren(context),
|
||||
),
|
||||
|
@ -58,9 +58,8 @@ class HAUtils {
|
||||
class StateChangedEvent {
|
||||
String entityId;
|
||||
String newState;
|
||||
bool localChange;
|
||||
|
||||
StateChangedEvent(this.entityId, this.newState, this.localChange);
|
||||
StateChangedEvent(this.entityId, this.newState);
|
||||
}
|
||||
|
||||
class SettingsChangedEvent {
|
||||
|
217
pubspec.lock
217
pubspec.lock
@ -1,13 +1,6 @@
|
||||
# Generated by pub
|
||||
# See https://www.dartlang.org/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.32.4"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -21,7 +14,7 @@ packages:
|
||||
name: args
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
version: "1.5.1"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -42,7 +35,7 @@ packages:
|
||||
name: cached_network_image
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
version: "0.5.0+1"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -56,14 +49,14 @@ packages:
|
||||
name: charts_common
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.0"
|
||||
version: "0.5.0"
|
||||
charts_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: charts_flutter
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.0"
|
||||
version: "0.5.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -85,13 +78,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.6"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: csslib
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.14.5"
|
||||
dart_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -126,7 +112,7 @@ packages:
|
||||
name: flutter_cache_manager
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
version: "0.2.0+1"
|
||||
flutter_colorpicker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -146,41 +132,13 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
front_end:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: front_end
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.7"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: html
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.13.3+3"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.11.3+17"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
version: "0.12.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -202,34 +160,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.15.7"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.3"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.1+1"
|
||||
json_rpc_2:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_rpc_2
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.9"
|
||||
kernel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: kernel
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.4"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -251,41 +181,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.6"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.6+2"
|
||||
multi_server_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: multi_server_socket
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
node_preamble:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_preamble
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.4"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
package_resolver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_resolver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -307,20 +202,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
plugin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0+3"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.6"
|
||||
progress_indicators:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -328,20 +209,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.2"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quiver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0+1"
|
||||
version: "2.0.1"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -349,53 +223,11 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.3"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.7.3+3"
|
||||
shelf_packages_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_packages_handler
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
shelf_static:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_static
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.8"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.2+4"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_map_stack_trace
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.5"
|
||||
source_maps:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_maps
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.10.7"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -438,13 +270,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
test:
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "0.2.1"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -458,14 +290,7 @@ packages:
|
||||
name: url_launcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
utf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: utf
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.0+5"
|
||||
version: "4.0.1"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -480,22 +305,8 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.8"
|
||||
vm_service_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service_client
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.6"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.7+10"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: web_socket_channel
|
||||
url: "https://pub.dartlang.org"
|
||||
@ -516,5 +327,5 @@ packages:
|
||||
source: hosted
|
||||
version: "2.1.15"
|
||||
sdks:
|
||||
dart: ">=2.0.0 <=2.1.0-dev.5.0.flutter-a2eb050044"
|
||||
flutter: ">=0.1.4 <2.0.0"
|
||||
dart: ">=2.0.0 <=2.1.0-dev.9.3.flutter-9c07fb64c4"
|
||||
flutter: ">=0.5.6 <2.0.0"
|
||||
|
19
pubspec.yaml
19
pubspec.yaml
@ -1,7 +1,7 @@
|
||||
name: hass_client
|
||||
description: Home Assistant Android Client
|
||||
|
||||
version: 0.3.4+52
|
||||
version: 0.3.10+74
|
||||
|
||||
environment:
|
||||
sdk: ">=2.0.0-dev.68.0 <3.0.0"
|
||||
@ -9,14 +9,15 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
web_socket_channel: any
|
||||
shared_preferences: any
|
||||
progress_indicators: ^0.1.2
|
||||
event_bus: ^1.0.1
|
||||
cached_network_image: ^0.4.1
|
||||
url_launcher: ^3.0.3
|
||||
date_format: ^1.0.5
|
||||
flutter_colorpicker: ^0.1.0
|
||||
charts_flutter: ^0.4.0
|
||||
progress_indicators: any
|
||||
event_bus: any
|
||||
cached_network_image: any
|
||||
url_launcher: any
|
||||
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.
|
||||
@ -25,7 +26,7 @@ dependencies:
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_launcher_icons: ^0.6.1
|
||||
flutter_launcher_icons: any
|
||||
|
||||
flutter_icons:
|
||||
android: true
|
||||
|
Reference in New Issue
Block a user