Compare commits
156 Commits
Author | SHA1 | Date | |
---|---|---|---|
5211d1ff46 | |||
d1c9fddba6 | |||
14cc55a2c7 | |||
277c67fc6f | |||
2a01ff8a03 | |||
b246b7bc1d | |||
e1868b9a14 | |||
125f3ac16c | |||
be502b5668 | |||
6f33fdca9f | |||
db77cc43aa | |||
b2269cc96d | |||
8b28bb2e9e | |||
fb456878bc | |||
8b961ebd69 | |||
9bd3a41cf5 | |||
491ae55a2a | |||
e1d2981782 | |||
74572168ae | |||
92d0b5c055 | |||
3504d3276c | |||
736b38b64c | |||
cb118b599a | |||
a08a056cff | |||
0ef2ebfe31 | |||
4f4ac3b574 | |||
7064cb0e30 | |||
91a99e17e0 | |||
2e9b7d20b9 | |||
b8aa808de4 | |||
2cfa92a42b | |||
146efef72d | |||
8c9804e16f | |||
a4736bfb5a | |||
15c54df629 | |||
32ffef21e9 | |||
848d3cb510 | |||
8a4caeebba | |||
aa923f0fba | |||
4d8f50ddd5 | |||
fe06b21a6c | |||
efed7fb1b5 | |||
df2cbb7d13 | |||
03edaa9ca2 | |||
1a7457abf9 | |||
00889b13e0 | |||
0615073ec4 | |||
eb7d17d147 | |||
24f80feeee | |||
4b6dda5a9c | |||
4099fa0c83 | |||
76057e8797 | |||
538d3603dc | |||
bc0e72ca52 | |||
f25a47beb2 | |||
cc3c6b0087 | |||
6cf80c0bfd | |||
8ce9bdb7a5 | |||
31e50150b1 | |||
e359150d97 | |||
93680c981c | |||
e06b66c523 | |||
3dea844e1e | |||
62b1af30e0 | |||
e006c4e403 | |||
983573388e | |||
bdd1dc7e17 | |||
9c1970ee14 | |||
d0e0bf3571 | |||
b399357517 | |||
0290cd3a32 | |||
d8a1d03179 | |||
216fad3cb9 | |||
fead6ea348 | |||
8814687be6 | |||
71c0e2caa0 | |||
1531c41542 | |||
bc90d013e8 | |||
2adfaca0c4 | |||
6cc1a37d9d | |||
4bb616b327 | |||
38219618ba | |||
6774b53758 | |||
29a94c882f | |||
5897fa3a99 | |||
7af92c2dc9 | |||
1094177a42 | |||
5e814e8109 | |||
24c7675fa4 | |||
dc3ca38c78 | |||
96b528e055 | |||
3858036631 | |||
19d42ceeb3 | |||
a2836a3603 | |||
2a45758a6d | |||
dc1bf4d878 | |||
e82ba60c4e | |||
09199d30e8 | |||
724d32dbe2 | |||
949c8ee44e | |||
1a446d34c7 | |||
22a5847285 | |||
1c8f770f10 | |||
be5ea55f6b | |||
c65ade9827 | |||
d3c1422b9e | |||
b6ac9f985f | |||
a59de4b6dc | |||
f507d5df0c | |||
f77e46de37 | |||
cda17b1217 | |||
be560769ef | |||
3815800e32 | |||
a3226311a2 | |||
79669243c2 | |||
fdc81f6ea4 | |||
7fe44459e7 | |||
a8500d44e1 | |||
b4d4c5abec | |||
c19a3f272a | |||
b264534858 | |||
ab53f77f9e | |||
c73956720c | |||
051041e794 | |||
5c83be9fee | |||
4bece42693 | |||
4ae107fe4c | |||
9523ed2562 | |||
9c403480e2 | |||
20b1b90e39 | |||
5633e30448 | |||
4492fb9f0c | |||
36410752e4 | |||
0219f7bfbb | |||
5f3c77f4b9 | |||
a36c7a9ca3 | |||
56ce6dfeeb | |||
67c214454f | |||
73398378c4 | |||
215871ce9e | |||
fd8ea6befd | |||
809a1a1c8c | |||
fc8f2f200f | |||
f41c9f9197 | |||
cdf55ce68b | |||
12088d9516 | |||
a0235ee385 | |||
67fbdb13c6 | |||
c5960de0be | |||
da15e880ec | |||
efbe33f4e3 | |||
af84c99a2d | |||
438449cad8 | |||
d9ca55c3b7 | |||
f248268984 | |||
8ee096595c |
76
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||||
|
level of experience, education, socio-economic status, nationality, personal
|
||||||
|
appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the project team at vyalov.egor@gmail.com. All
|
||||||
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
|
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see
|
||||||
|
https://www.contributor-covenant.org/faq
|
@ -1,13 +1,12 @@
|
|||||||
|
[](https://somegeeky.website/badges/flutter) [](https://somegeeky.website/badges/dart)
|
||||||
# HA Client
|
# HA Client
|
||||||
## Native Android client for Home Assistant
|
## Native Android client for Home Assistant
|
||||||
### With Lovelace UI support
|
### With Lovelace UI support
|
||||||
|
|
||||||
Home Assistant Android client on Dart with Flutter.
|
Visit [homemade.systems](http://ha-client.homemade.systems/) for more info.
|
||||||
|
|
||||||
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
|
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
|
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)
|
Discuss it in [Home Assistant community](https://community.home-assistant.io/t/alpha-testing-ha-client-native-android-client-for-home-assistant/69912)
|
||||||
|
@ -29,7 +29,12 @@ def keystoreProperties = new Properties()
|
|||||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 27
|
compileSdkVersion 28
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
disable 'InvalidPackage'
|
disable 'InvalidPackage'
|
||||||
@ -38,7 +43,7 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.keyboardcrumbs.haclient"
|
applicationId "com.keyboardcrumbs.haclient"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 27
|
targetSdkVersion 28
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
@ -36,4 +36,4 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 11 KiB |
@ -1 +1,5 @@
|
|||||||
org.gradle.jvmargs=-Xmx1536M
|
org.gradle.jvmargs=-Xmx2g
|
||||||
|
org.gradle.daemon=true
|
||||||
|
org.gradle.caching=true
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=false
|
0
android/gradlew
vendored
Normal file → Executable file
1
docs/empty
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
BIN
docs/ha_access_tokens.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
docs/ha_profile-300x247.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
docs/settings-869x1024.png
Normal file
After Width: | Height: | Size: 102 KiB |
BIN
fonts/materialdesignicons-webfont-3-5-95.ttf
Normal file
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 24 KiB |
@ -1,9 +1,9 @@
|
|||||||
part of 'main.dart';
|
part of 'main.dart';
|
||||||
|
|
||||||
class EntityViewPage extends StatefulWidget {
|
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;
|
final HomeAssistant homeAssistant;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -12,30 +12,26 @@ class EntityViewPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _EntityViewPageState extends State<EntityViewPage> {
|
class _EntityViewPageState extends State<EntityViewPage> {
|
||||||
String _title;
|
String _title;
|
||||||
|
StreamSubscription _refreshDataSubscription;
|
||||||
StreamSubscription _stateSubscription;
|
StreamSubscription _stateSubscription;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
|
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
|
||||||
if (event.entityId == widget.entity.entityId) {
|
if (event.entityId == widget.entityId) {
|
||||||
|
Logger.d("State change event handled by entity page: ${event.entityId}");
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
_prepareData();
|
_prepareData();
|
||||||
_getHistory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _prepareData() async {
|
void _prepareData() async {
|
||||||
_title = widget.entity.displayName;
|
_title = widget.homeAssistant.entities.get(widget.entityId).displayName;
|
||||||
}
|
|
||||||
|
|
||||||
void _getHistory() {
|
|
||||||
/* widget.homeAssistant.getHistory(widget.entity.entityId).then((List history) {
|
|
||||||
if (history != null) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -50,12 +46,9 @@ class _EntityViewPageState extends State<EntityViewPage> {
|
|||||||
// the App.build method, and use it to set our appbar title.
|
// the App.build method, and use it to set our appbar title.
|
||||||
title: new Text(_title),
|
title: new Text(_title),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: HomeAssistantModel(
|
||||||
padding: EdgeInsets.all(10.0),
|
homeAssistant: widget.homeAssistant,
|
||||||
child: HomeAssistantModel(
|
child: widget.homeAssistant.entities.get(widget.entityId).buildEntityPageWidget(context)
|
||||||
homeAssistant: widget.homeAssistant,
|
|
||||||
child: widget.entity.buildEntityPageWidget(context)
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -63,6 +56,7 @@ class _EntityViewPageState extends State<EntityViewPage> {
|
|||||||
@override
|
@override
|
||||||
void dispose(){
|
void dispose(){
|
||||||
if (_stateSubscription != null) _stateSubscription.cancel();
|
if (_stateSubscription != null) _stateSubscription.cancel();
|
||||||
|
if (_refreshDataSubscription != null) _refreshDataSubscription.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
12
lib/entity_class/alarm_control_panel.class.dart
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class AlarmControlPanelEntity extends Entity {
|
||||||
|
AlarmControlPanelEntity(Map rawData) : super(rawData);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||||
|
return AlarmControlPanelControlsWidget(
|
||||||
|
extended: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
26
lib/entity_class/automation_entity.dart
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class AutomationEntity extends Entity {
|
||||||
|
AutomationEntity(Map rawData) : super(rawData);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget _buildStatePart(BuildContext context) {
|
||||||
|
return SwitchStateWidget();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: <Widget>[
|
||||||
|
FlatServiceButton(
|
||||||
|
serviceDomain: domain,
|
||||||
|
entityId: entityId,
|
||||||
|
text: "TRIGGER",
|
||||||
|
serviceName: "trigger",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,11 @@ class ButtonEntity extends Entity {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget _buildStatePart(BuildContext context) {
|
Widget _buildStatePart(BuildContext context) {
|
||||||
return ButtonStateWidget();
|
return FlatServiceButton(
|
||||||
|
entityId: entityId,
|
||||||
|
serviceDomain: domain,
|
||||||
|
serviceName: 'turn_on',
|
||||||
|
text: domain == "scene" ? "ACTIVATE" : "EXECUTE",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
17
lib/entity_class/camera_entity.class.dart
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class CameraEntity extends Entity {
|
||||||
|
|
||||||
|
static const SUPPORT_ON_OFF = 1;
|
||||||
|
|
||||||
|
CameraEntity(Map rawData) : super(rawData);
|
||||||
|
|
||||||
|
bool get supportOnOff => ((supportedFeatures &
|
||||||
|
CameraEntity.SUPPORT_ON_OFF) ==
|
||||||
|
CameraEntity.SUPPORT_ON_OFF);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||||
|
return CameraStreamView();
|
||||||
|
}
|
||||||
|
}
|
@ -23,44 +23,44 @@ class ClimateEntity extends Entity {
|
|||||||
static const SUPPORT_AUX_HEAT = 2048;
|
static const SUPPORT_AUX_HEAT = 2048;
|
||||||
static const SUPPORT_ON_OFF = 4096;
|
static const SUPPORT_ON_OFF = 4096;
|
||||||
|
|
||||||
bool get supportTargetTemperature => ((attributes["supported_features"] &
|
bool get supportTargetTemperature => ((supportedFeatures &
|
||||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE) ==
|
ClimateEntity.SUPPORT_TARGET_TEMPERATURE) ==
|
||||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE);
|
ClimateEntity.SUPPORT_TARGET_TEMPERATURE);
|
||||||
bool get supportTargetTemperatureHigh => ((attributes["supported_features"] &
|
bool get supportTargetTemperatureHigh => ((supportedFeatures &
|
||||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH) ==
|
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH) ==
|
||||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH);
|
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH);
|
||||||
bool get supportTargetTemperatureLow => ((attributes["supported_features"] &
|
bool get supportTargetTemperatureLow => ((supportedFeatures &
|
||||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW) ==
|
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW) ==
|
||||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW);
|
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW);
|
||||||
bool get supportTargetHumidity => ((attributes["supported_features"] &
|
bool get supportTargetHumidity => ((supportedFeatures &
|
||||||
ClimateEntity.SUPPORT_TARGET_HUMIDITY) ==
|
ClimateEntity.SUPPORT_TARGET_HUMIDITY) ==
|
||||||
ClimateEntity.SUPPORT_TARGET_HUMIDITY);
|
ClimateEntity.SUPPORT_TARGET_HUMIDITY);
|
||||||
bool get supportTargetHumidityHigh => ((attributes["supported_features"] &
|
bool get supportTargetHumidityHigh => ((supportedFeatures &
|
||||||
ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH) ==
|
ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH) ==
|
||||||
ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH);
|
ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH);
|
||||||
bool get supportTargetHumidityLow => ((attributes["supported_features"] &
|
bool get supportTargetHumidityLow => ((supportedFeatures &
|
||||||
ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW) ==
|
ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW) ==
|
||||||
ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW);
|
ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW);
|
||||||
bool get supportFanMode =>
|
bool get supportFanMode =>
|
||||||
((attributes["supported_features"] & ClimateEntity.SUPPORT_FAN_MODE) ==
|
((supportedFeatures & ClimateEntity.SUPPORT_FAN_MODE) ==
|
||||||
ClimateEntity.SUPPORT_FAN_MODE);
|
ClimateEntity.SUPPORT_FAN_MODE);
|
||||||
bool get supportOperationMode => ((attributes["supported_features"] &
|
bool get supportOperationMode => ((supportedFeatures &
|
||||||
ClimateEntity.SUPPORT_OPERATION_MODE) ==
|
ClimateEntity.SUPPORT_OPERATION_MODE) ==
|
||||||
ClimateEntity.SUPPORT_OPERATION_MODE);
|
ClimateEntity.SUPPORT_OPERATION_MODE);
|
||||||
bool get supportHoldMode =>
|
bool get supportHoldMode =>
|
||||||
((attributes["supported_features"] & ClimateEntity.SUPPORT_HOLD_MODE) ==
|
((supportedFeatures & ClimateEntity.SUPPORT_HOLD_MODE) ==
|
||||||
ClimateEntity.SUPPORT_HOLD_MODE);
|
ClimateEntity.SUPPORT_HOLD_MODE);
|
||||||
bool get supportSwingMode =>
|
bool get supportSwingMode =>
|
||||||
((attributes["supported_features"] & ClimateEntity.SUPPORT_SWING_MODE) ==
|
((supportedFeatures & ClimateEntity.SUPPORT_SWING_MODE) ==
|
||||||
ClimateEntity.SUPPORT_SWING_MODE);
|
ClimateEntity.SUPPORT_SWING_MODE);
|
||||||
bool get supportAwayMode =>
|
bool get supportAwayMode =>
|
||||||
((attributes["supported_features"] & ClimateEntity.SUPPORT_AWAY_MODE) ==
|
((supportedFeatures & ClimateEntity.SUPPORT_AWAY_MODE) ==
|
||||||
ClimateEntity.SUPPORT_AWAY_MODE);
|
ClimateEntity.SUPPORT_AWAY_MODE);
|
||||||
bool get supportAuxHeat =>
|
bool get supportAuxHeat =>
|
||||||
((attributes["supported_features"] & ClimateEntity.SUPPORT_AUX_HEAT) ==
|
((supportedFeatures & ClimateEntity.SUPPORT_AUX_HEAT) ==
|
||||||
ClimateEntity.SUPPORT_AUX_HEAT);
|
ClimateEntity.SUPPORT_AUX_HEAT);
|
||||||
bool get supportOnOff =>
|
bool get supportOnOff =>
|
||||||
((attributes["supported_features"] & ClimateEntity.SUPPORT_ON_OFF) ==
|
((supportedFeatures & ClimateEntity.SUPPORT_ON_OFF) ==
|
||||||
ClimateEntity.SUPPORT_ON_OFF);
|
ClimateEntity.SUPPORT_ON_OFF);
|
||||||
|
|
||||||
List<String> get operationList => attributes["operation_list"] != null
|
List<String> get operationList => attributes["operation_list"] != null
|
||||||
@ -80,6 +80,7 @@ class ClimateEntity extends Entity {
|
|||||||
double get targetHumidity => _getDoubleAttributeValue('humidity');
|
double get targetHumidity => _getDoubleAttributeValue('humidity');
|
||||||
double get maxHumidity => _getDoubleAttributeValue('max_humidity');
|
double get maxHumidity => _getDoubleAttributeValue('max_humidity');
|
||||||
double get minHumidity => _getDoubleAttributeValue('min_humidity');
|
double get minHumidity => _getDoubleAttributeValue('min_humidity');
|
||||||
|
double get temperatureStep => _getDoubleAttributeValue('target_temp_step') ?? 0.5;
|
||||||
String get operationMode => attributes['operation_mode'];
|
String get operationMode => attributes['operation_mode'];
|
||||||
String get fanMode => attributes['fan_mode'];
|
String get fanMode => attributes['fan_mode'];
|
||||||
String get swingMode => attributes['swing_mode'];
|
String get swingMode => attributes['swing_mode'];
|
||||||
|
@ -28,10 +28,72 @@ class EntityState {
|
|||||||
static const unavailable = 'unavailable';
|
static const unavailable = 'unavailable';
|
||||||
static const ok = 'ok';
|
static const ok = 'ok';
|
||||||
static const problem = 'problem';
|
static const problem = 'problem';
|
||||||
|
static const active = 'active';
|
||||||
}
|
}
|
||||||
|
|
||||||
class EntityTapAction {
|
class EntityUIAction {
|
||||||
static const moreInfo = 'more-info';
|
static const moreInfo = 'more-info';
|
||||||
static const toggle = 'toggle';
|
static const toggle = 'toggle';
|
||||||
static const callService = 'call-service';
|
static const callService = 'call-service';
|
||||||
|
static const navigate = 'navigate';
|
||||||
|
static const none = 'none';
|
||||||
|
|
||||||
|
String tapAction = EntityUIAction.moreInfo;
|
||||||
|
String tapNavigationPath;
|
||||||
|
String tapService;
|
||||||
|
Map<String, dynamic> tapServiceData;
|
||||||
|
String holdAction = EntityUIAction.none;
|
||||||
|
String holdNavigationPath;
|
||||||
|
String holdService;
|
||||||
|
Map<String, dynamic> holdServiceData;
|
||||||
|
|
||||||
|
EntityUIAction({rawEntityData}) {
|
||||||
|
if (rawEntityData != null) {
|
||||||
|
if (rawEntityData["tap_action"] != null) {
|
||||||
|
if (rawEntityData["tap_action"] is String) {
|
||||||
|
tapAction = rawEntityData["tap_action"];
|
||||||
|
} else {
|
||||||
|
tapAction =
|
||||||
|
rawEntityData["tap_action"]["action"] ?? EntityUIAction.moreInfo;
|
||||||
|
tapNavigationPath = rawEntityData["tap_action"]["navigation_path"];
|
||||||
|
tapService = rawEntityData["tap_action"]["service"];
|
||||||
|
tapServiceData = rawEntityData["tap_action"]["service_data"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rawEntityData["hold_action"] != null) {
|
||||||
|
if (rawEntityData["hold_action"] is String) {
|
||||||
|
holdAction = rawEntityData["hold_action"];
|
||||||
|
} else {
|
||||||
|
holdAction =
|
||||||
|
rawEntityData["hold_action"]["action"] ?? EntityUIAction.none;
|
||||||
|
holdNavigationPath = rawEntityData["hold_action"]["navigation_path"];
|
||||||
|
holdService = rawEntityData["hold_action"]["service"];
|
||||||
|
holdServiceData = rawEntityData["hold_action"]["service_data"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class CardType {
|
||||||
|
static const horizontalStack = "horizontal-stack";
|
||||||
|
static const verticalStack = "vertical-stack";
|
||||||
|
static const entities = "entities";
|
||||||
|
static const glance = "glance";
|
||||||
|
static const mediaControl = "media-control";
|
||||||
|
static const weatherForecast = "weather-forecast";
|
||||||
|
static const thermostat = "thermostat";
|
||||||
|
static const sensor = "sensor";
|
||||||
|
static const plantStatus = "plant-status";
|
||||||
|
static const pictureEntity = "picture-entity";
|
||||||
|
static const pictureElements = "picture-elements";
|
||||||
|
static const picture = "picture";
|
||||||
|
static const map = "map";
|
||||||
|
static const iframe = "iframe";
|
||||||
|
static const gauge = "gauge";
|
||||||
|
static const entityButton = "entity-button";
|
||||||
|
static const conditional = "conditional";
|
||||||
|
static const alarmPanel = "alarm-panel";
|
||||||
|
static const markdown = "markdown";
|
||||||
}
|
}
|
@ -11,29 +11,29 @@ class CoverEntity extends Entity {
|
|||||||
static const SUPPORT_STOP_TILT = 64;
|
static const SUPPORT_STOP_TILT = 64;
|
||||||
static const SUPPORT_SET_TILT_POSITION = 128;
|
static const SUPPORT_SET_TILT_POSITION = 128;
|
||||||
|
|
||||||
bool get supportOpen => ((attributes["supported_features"] &
|
bool get supportOpen => ((supportedFeatures &
|
||||||
CoverEntity.SUPPORT_OPEN) ==
|
CoverEntity.SUPPORT_OPEN) ==
|
||||||
CoverEntity.SUPPORT_OPEN);
|
CoverEntity.SUPPORT_OPEN);
|
||||||
bool get supportClose => ((attributes["supported_features"] &
|
bool get supportClose => ((supportedFeatures &
|
||||||
CoverEntity.SUPPORT_CLOSE) ==
|
CoverEntity.SUPPORT_CLOSE) ==
|
||||||
CoverEntity.SUPPORT_CLOSE);
|
CoverEntity.SUPPORT_CLOSE);
|
||||||
bool get supportSetPosition => ((attributes["supported_features"] &
|
bool get supportSetPosition => ((supportedFeatures &
|
||||||
CoverEntity.SUPPORT_SET_POSITION) ==
|
CoverEntity.SUPPORT_SET_POSITION) ==
|
||||||
CoverEntity.SUPPORT_SET_POSITION);
|
CoverEntity.SUPPORT_SET_POSITION);
|
||||||
bool get supportStop => ((attributes["supported_features"] &
|
bool get supportStop => ((supportedFeatures &
|
||||||
CoverEntity.SUPPORT_STOP) ==
|
CoverEntity.SUPPORT_STOP) ==
|
||||||
CoverEntity.SUPPORT_STOP);
|
CoverEntity.SUPPORT_STOP);
|
||||||
|
|
||||||
bool get supportOpenTilt => ((attributes["supported_features"] &
|
bool get supportOpenTilt => ((supportedFeatures &
|
||||||
CoverEntity.SUPPORT_OPEN_TILT) ==
|
CoverEntity.SUPPORT_OPEN_TILT) ==
|
||||||
CoverEntity.SUPPORT_OPEN_TILT);
|
CoverEntity.SUPPORT_OPEN_TILT);
|
||||||
bool get supportCloseTilt => ((attributes["supported_features"] &
|
bool get supportCloseTilt => ((supportedFeatures &
|
||||||
CoverEntity.SUPPORT_CLOSE_TILT) ==
|
CoverEntity.SUPPORT_CLOSE_TILT) ==
|
||||||
CoverEntity.SUPPORT_CLOSE_TILT);
|
CoverEntity.SUPPORT_CLOSE_TILT);
|
||||||
bool get supportStopTilt => ((attributes["supported_features"] &
|
bool get supportStopTilt => ((supportedFeatures &
|
||||||
CoverEntity.SUPPORT_STOP_TILT) ==
|
CoverEntity.SUPPORT_STOP_TILT) ==
|
||||||
CoverEntity.SUPPORT_STOP_TILT);
|
CoverEntity.SUPPORT_STOP_TILT);
|
||||||
bool get supportSetTiltPosition => ((attributes["supported_features"] &
|
bool get supportSetTiltPosition => ((supportedFeatures &
|
||||||
CoverEntity.SUPPORT_SET_TILT_POSITION) ==
|
CoverEntity.SUPPORT_SET_TILT_POSITION) ==
|
||||||
CoverEntity.SUPPORT_SET_TILT_POSITION);
|
CoverEntity.SUPPORT_SET_TILT_POSITION);
|
||||||
|
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
part of '../main.dart';
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class StatelessEntityType {
|
||||||
|
static const NONE = 0;
|
||||||
|
static const MISSED = 1;
|
||||||
|
static const DIVIDER = 2;
|
||||||
|
static const SECTION = 3;
|
||||||
|
static const CALL_SERVICE = 4;
|
||||||
|
static const WEBLINK = 5;
|
||||||
|
}
|
||||||
|
|
||||||
class Entity {
|
class Entity {
|
||||||
|
|
||||||
static List badgeDomains = [
|
static List badgeDomains = [
|
||||||
@ -12,14 +21,65 @@ class Entity {
|
|||||||
"sensor"
|
"sensor"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static Map StateByDeviceClass = {
|
||||||
|
"battery.on": "Low",
|
||||||
|
"battery.off": "Normal",
|
||||||
|
"cold.on": "Cold",
|
||||||
|
"cold.off": "Normal",
|
||||||
|
"connectivity.on": "Connected",
|
||||||
|
"connectivity.off": "Diconnected",
|
||||||
|
"door.on": "Open",
|
||||||
|
"door.off": "Closed",
|
||||||
|
"garage_door.on": "Open",
|
||||||
|
"garage_door.off": "Closed",
|
||||||
|
"gas.on": "Detected",
|
||||||
|
"gas.off": "Clear",
|
||||||
|
"heat.on": "Hot",
|
||||||
|
"heat.off": "Normal",
|
||||||
|
"light.on": "Detected",
|
||||||
|
"lignt.off": "No light",
|
||||||
|
"lock.on": "Unlocked",
|
||||||
|
"lock.off": "Locked",
|
||||||
|
"moisture.on": "Wet",
|
||||||
|
"moisture.off": "Dry",
|
||||||
|
"motion.on": "Detected",
|
||||||
|
"motion.off": "Clear",
|
||||||
|
"moving.on": "Moving",
|
||||||
|
"moving.off": "Stopped",
|
||||||
|
"occupancy.on": "Occupied",
|
||||||
|
"occupancy.off": "Clear",
|
||||||
|
"opening.on": "Open",
|
||||||
|
"opening.off": "Closed",
|
||||||
|
"plug.on": "Plugged in",
|
||||||
|
"plug.off": "Unplugged",
|
||||||
|
"power.on": "Powered",
|
||||||
|
"power.off": "No power",
|
||||||
|
"presence.on": "Home",
|
||||||
|
"presence.off": "Away",
|
||||||
|
"problem.on": "Problem",
|
||||||
|
"problem.off": "OK",
|
||||||
|
"safety.on": "Unsafe",
|
||||||
|
"safety.off": "Safe",
|
||||||
|
"smoke.on": "Detected",
|
||||||
|
"smoke.off": "Clear",
|
||||||
|
"sound.on": "Detected",
|
||||||
|
"sound.off": "Clear",
|
||||||
|
"vibration.on": "Detected",
|
||||||
|
"vibration.off": "Clear",
|
||||||
|
"window.on": "Open",
|
||||||
|
"window.off": "Closed"
|
||||||
|
};
|
||||||
|
|
||||||
Map attributes;
|
Map attributes;
|
||||||
String domain;
|
String domain;
|
||||||
String entityId;
|
String entityId;
|
||||||
String state;
|
String state;
|
||||||
|
String displayState;
|
||||||
DateTime _lastUpdated;
|
DateTime _lastUpdated;
|
||||||
|
int statelessType = 0;
|
||||||
|
|
||||||
List<Entity> childEntities = [];
|
List<Entity> childEntities = [];
|
||||||
List<String> attributesToShow = ["all"];
|
String deviceClass;
|
||||||
EntityHistoryConfig historyConfig = EntityHistoryConfig(
|
EntityHistoryConfig historyConfig = EntityHistoryConfig(
|
||||||
chartType: EntityHistoryWidgetType.simple
|
chartType: EntityHistoryWidgetType.simple
|
||||||
);
|
);
|
||||||
@ -27,7 +87,6 @@ class Entity {
|
|||||||
String get displayName =>
|
String get displayName =>
|
||||||
attributes["friendly_name"] ?? (attributes["name"] ?? entityId.split(".")[1].replaceAll("_", " "));
|
attributes["friendly_name"] ?? (attributes["name"] ?? entityId.split(".")[1].replaceAll("_", " "));
|
||||||
|
|
||||||
String get deviceClass => attributes["device_class"] ?? null;
|
|
||||||
bool get isView =>
|
bool get isView =>
|
||||||
(domain == "group") &&
|
(domain == "group") &&
|
||||||
(attributes != null ? attributes["view"] ?? false : false);
|
(attributes != null ? attributes["view"] ?? false : false);
|
||||||
@ -35,22 +94,67 @@ class Entity {
|
|||||||
bool get isBadge => Entity.badgeDomains.contains(domain);
|
bool get isBadge => Entity.badgeDomains.contains(domain);
|
||||||
String get icon => attributes["icon"] ?? "";
|
String get icon => attributes["icon"] ?? "";
|
||||||
bool get isOn => state == EntityState.on;
|
bool get isOn => state == EntityState.on;
|
||||||
String get entityPicture => attributes["entity_picture"];
|
String get entityPicture => _getEntityPictureUrl();
|
||||||
String get unitOfMeasurement => attributes["unit_of_measurement"] ?? "";
|
String get unitOfMeasurement => attributes["unit_of_measurement"] ?? "";
|
||||||
List get childEntityIds => attributes["entity_id"] ?? [];
|
List get childEntityIds => attributes["entity_id"] ?? [];
|
||||||
String get lastUpdated => _getLastUpdatedFormatted();
|
String get lastUpdated => _getLastUpdatedFormatted();
|
||||||
bool get isHidden => attributes["hidden"] ?? false;
|
bool get isHidden => attributes["hidden"] ?? false;
|
||||||
double get doubleState => double.tryParse(state) ?? 0.0;
|
double get doubleState => double.tryParse(state) ?? 0.0;
|
||||||
|
int get supportedFeatures => attributes["supported_features"] ?? 0;
|
||||||
|
|
||||||
|
String _getEntityPictureUrl() {
|
||||||
|
String result = attributes["entity_picture"];
|
||||||
|
if (result == null) return result;
|
||||||
|
if (!result.startsWith("http")) {
|
||||||
|
if (result.startsWith("/")) {
|
||||||
|
result = "$homeAssistantWebHost$result";
|
||||||
|
} else {
|
||||||
|
result = "$homeAssistantWebHost/$result";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Entity(Map rawData) {
|
Entity(Map rawData) {
|
||||||
update(rawData);
|
update(rawData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Entity.missed(String entityId) {
|
||||||
|
statelessType = StatelessEntityType.MISSED;
|
||||||
|
attributes = {"hidden": false};
|
||||||
|
this.entityId = entityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity.divider() {
|
||||||
|
statelessType = StatelessEntityType.DIVIDER;
|
||||||
|
attributes = {"hidden": false};
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity.section(String label) {
|
||||||
|
statelessType = StatelessEntityType.SECTION;
|
||||||
|
attributes = {"hidden": false, "friendly_name": "$label"};
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity.callService({String icon, String name, String service, String actionName}) {
|
||||||
|
statelessType = StatelessEntityType.CALL_SERVICE;
|
||||||
|
entityId = service;
|
||||||
|
displayState = actionName?.toUpperCase() ?? "RUN";
|
||||||
|
attributes = {"hidden": false, "friendly_name": "$name", "icon": "$icon"};
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity.weblink({String url, String name, String icon}) {
|
||||||
|
statelessType = StatelessEntityType.WEBLINK;
|
||||||
|
entityId = "custom.custom"; //TODO wtf??
|
||||||
|
attributes = {"hidden": false, "friendly_name": "${name ?? url}", "icon": "${icon ?? 'mdi:link'}"};
|
||||||
|
}
|
||||||
|
|
||||||
void update(Map rawData) {
|
void update(Map rawData) {
|
||||||
attributes = rawData["attributes"] ?? {};
|
attributes = rawData["attributes"] ?? {};
|
||||||
domain = rawData["entity_id"].split(".")[0];
|
domain = rawData["entity_id"].split(".")[0];
|
||||||
entityId = rawData["entity_id"];
|
entityId = rawData["entity_id"];
|
||||||
|
deviceClass = attributes["device_class"];
|
||||||
state = rawData["state"];
|
state = rawData["state"];
|
||||||
|
displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? state;
|
||||||
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
|
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,19 +180,21 @@ class Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> getStringListAttributeValue(String attribute) {
|
||||||
|
if (attributes["$attribute"] != null) {
|
||||||
|
List<String> result = (attributes["$attribute"] as List).cast<String>();
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildDefaultWidget(BuildContext context) {
|
Widget buildDefaultWidget(BuildContext context) {
|
||||||
return DefaultEntityContainer(
|
return DefaultEntityContainer(
|
||||||
state: _buildStatePart(context)
|
state: _buildStatePart(context)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildGlanceWidget(BuildContext context, bool showName, bool showState) {
|
|
||||||
return GlanceEntityContainer(
|
|
||||||
showName: showName,
|
|
||||||
showState: showState,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildStatePart(BuildContext context) {
|
Widget _buildStatePart(BuildContext context) {
|
||||||
return SimpleEntityState();
|
return SimpleEntityState();
|
||||||
}
|
}
|
||||||
@ -108,7 +214,10 @@ class Entity {
|
|||||||
return EntityModel(
|
return EntityModel(
|
||||||
entityWrapper: EntityWrapper(entity: this),
|
entityWrapper: EntityWrapper(entity: this),
|
||||||
child: EntityPageContainer(children: <Widget>[
|
child: EntityPageContainer(children: <Widget>[
|
||||||
DefaultEntityContainer(state: _buildStatePartForPage(context)),
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: Sizes.rowPadding),
|
||||||
|
child: DefaultEntityContainer(state: _buildStatePartForPage(context)),
|
||||||
|
),
|
||||||
LastUpdatedWidget(),
|
LastUpdatedWidget(),
|
||||||
Divider(),
|
Divider(),
|
||||||
_buildAdditionalControlsForPage(context),
|
_buildAdditionalControlsForPage(context),
|
||||||
|
@ -4,10 +4,8 @@ class EntityWrapper {
|
|||||||
|
|
||||||
String displayName;
|
String displayName;
|
||||||
String icon;
|
String icon;
|
||||||
String tapAction;
|
String entityPicture;
|
||||||
String holdAction;
|
EntityUIAction uiAction;
|
||||||
String actionService;
|
|
||||||
Map<String, dynamic> actionServiceData;
|
|
||||||
Entity entity;
|
Entity entity;
|
||||||
|
|
||||||
|
|
||||||
@ -15,13 +13,102 @@ class EntityWrapper {
|
|||||||
this.entity,
|
this.entity,
|
||||||
String icon,
|
String icon,
|
||||||
String displayName,
|
String displayName,
|
||||||
this.tapAction: EntityTapAction.moreInfo,
|
this.uiAction
|
||||||
this.holdAction,
|
|
||||||
this.actionService,
|
|
||||||
this.actionServiceData
|
|
||||||
}) {
|
}) {
|
||||||
this.icon = icon ?? entity.icon;
|
if (entity.statelessType == StatelessEntityType.NONE || entity.statelessType == StatelessEntityType.CALL_SERVICE || entity.statelessType == StatelessEntityType.WEBLINK) {
|
||||||
this.displayName = displayName ?? entity.displayName;
|
this.icon = icon ?? entity.icon;
|
||||||
|
if (icon == null) {
|
||||||
|
entityPicture = entity.entityPicture;
|
||||||
|
}
|
||||||
|
this.displayName = displayName ?? entity.displayName;
|
||||||
|
if (uiAction == null) {
|
||||||
|
uiAction = EntityUIAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleTap() {
|
||||||
|
switch (uiAction.tapAction) {
|
||||||
|
case EntityUIAction.toggle: {
|
||||||
|
eventBus.fire(
|
||||||
|
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.callService: {
|
||||||
|
if (uiAction.tapService != null) {
|
||||||
|
eventBus.fire(
|
||||||
|
ServiceCallEvent(uiAction.tapService.split(".")[0],
|
||||||
|
uiAction.tapService.split(".")[1], null,
|
||||||
|
uiAction.tapServiceData));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.none: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.moreInfo: {
|
||||||
|
eventBus.fire(
|
||||||
|
new ShowEntityPageEvent(entity));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.navigate: {
|
||||||
|
if (uiAction.tapService.startsWith("/")) {
|
||||||
|
//TODO handle local urls
|
||||||
|
Logger.w("Local urls is not supported yet");
|
||||||
|
} else {
|
||||||
|
HAUtils.launchURL(uiAction.tapService);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleHold() {
|
||||||
|
switch (uiAction.holdAction) {
|
||||||
|
case EntityUIAction.toggle: {
|
||||||
|
eventBus.fire(
|
||||||
|
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.callService: {
|
||||||
|
if (uiAction.holdService != null) {
|
||||||
|
eventBus.fire(
|
||||||
|
ServiceCallEvent(uiAction.holdService.split(".")[0],
|
||||||
|
uiAction.holdService.split(".")[1], null,
|
||||||
|
uiAction.holdServiceData));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.moreInfo: {
|
||||||
|
eventBus.fire(
|
||||||
|
new ShowEntityPageEvent(entity));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.navigate: {
|
||||||
|
if (uiAction.holdService.startsWith("/")) {
|
||||||
|
//TODO handle local urls
|
||||||
|
Logger.w("Local urls is not supported yet");
|
||||||
|
} else {
|
||||||
|
HAUtils.launchURL(uiAction.holdService);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
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 => ((supportedFeatures &
|
||||||
|
FanEntity.SUPPORT_SET_SPEED) ==
|
||||||
|
FanEntity.SUPPORT_SET_SPEED);
|
||||||
|
bool get supportOscillate => ((supportedFeatures &
|
||||||
|
FanEntity.SUPPORT_OSCILLATE) ==
|
||||||
|
FanEntity.SUPPORT_OSCILLATE);
|
||||||
|
bool get supportDirection => ((supportedFeatures &
|
||||||
|
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
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -10,43 +10,50 @@ class LightEntity extends Entity {
|
|||||||
static const SUPPORT_TRANSITION = 32;
|
static const SUPPORT_TRANSITION = 32;
|
||||||
static const SUPPORT_WHITE_VALUE = 128;
|
static const SUPPORT_WHITE_VALUE = 128;
|
||||||
|
|
||||||
bool get supportBrightness => ((attributes["supported_features"] &
|
bool get supportBrightness => ((supportedFeatures &
|
||||||
LightEntity.SUPPORT_BRIGHTNESS) ==
|
LightEntity.SUPPORT_BRIGHTNESS) ==
|
||||||
LightEntity.SUPPORT_BRIGHTNESS);
|
LightEntity.SUPPORT_BRIGHTNESS);
|
||||||
bool get supportColorTemp => ((attributes["supported_features"] &
|
bool get supportColorTemp => ((supportedFeatures &
|
||||||
LightEntity.SUPPORT_COLOR_TEMP) ==
|
LightEntity.SUPPORT_COLOR_TEMP) ==
|
||||||
LightEntity.SUPPORT_COLOR_TEMP);
|
LightEntity.SUPPORT_COLOR_TEMP);
|
||||||
bool get supportEffect => ((attributes["supported_features"] &
|
bool get supportEffect => ((supportedFeatures &
|
||||||
LightEntity.SUPPORT_EFFECT) ==
|
LightEntity.SUPPORT_EFFECT) ==
|
||||||
LightEntity.SUPPORT_EFFECT);
|
LightEntity.SUPPORT_EFFECT);
|
||||||
bool get supportFlash => ((attributes["supported_features"] &
|
bool get supportFlash => ((supportedFeatures &
|
||||||
LightEntity.SUPPORT_FLASH) ==
|
LightEntity.SUPPORT_FLASH) ==
|
||||||
LightEntity.SUPPORT_FLASH);
|
LightEntity.SUPPORT_FLASH);
|
||||||
bool get supportColor => ((attributes["supported_features"] &
|
bool get supportColor => ((supportedFeatures &
|
||||||
LightEntity.SUPPORT_COLOR) ==
|
LightEntity.SUPPORT_COLOR) ==
|
||||||
LightEntity.SUPPORT_COLOR);
|
LightEntity.SUPPORT_COLOR);
|
||||||
bool get supportTransition => ((attributes["supported_features"] &
|
bool get supportTransition => ((supportedFeatures &
|
||||||
LightEntity.SUPPORT_TRANSITION) ==
|
LightEntity.SUPPORT_TRANSITION) ==
|
||||||
LightEntity.SUPPORT_TRANSITION);
|
LightEntity.SUPPORT_TRANSITION);
|
||||||
bool get supportWhiteValue => ((attributes["supported_features"] &
|
bool get supportWhiteValue => ((supportedFeatures &
|
||||||
LightEntity.SUPPORT_WHITE_VALUE) ==
|
LightEntity.SUPPORT_WHITE_VALUE) ==
|
||||||
LightEntity.SUPPORT_WHITE_VALUE);
|
LightEntity.SUPPORT_WHITE_VALUE);
|
||||||
|
|
||||||
int get brightness => _getIntAttributeValue("brightness");
|
int get brightness => _getIntAttributeValue("brightness");
|
||||||
|
int get whiteValue => _getIntAttributeValue("white_value");
|
||||||
|
String get effect => attributes["effect"];
|
||||||
int get colorTemp => _getIntAttributeValue("color_temp");
|
int get colorTemp => _getIntAttributeValue("color_temp");
|
||||||
double get maxMireds => _getDoubleAttributeValue("max_mireds");
|
double get maxMireds => _getDoubleAttributeValue("max_mireds");
|
||||||
double get minMireds => _getDoubleAttributeValue("min_mireds");
|
double get minMireds => _getDoubleAttributeValue("min_mireds");
|
||||||
Color get color => _getColor();
|
HSVColor get color => _getColor();
|
||||||
bool get isAdditionalControls => ((attributes["supported_features"] != null) && (attributes["supported_features"] != 0));
|
bool get isAdditionalControls => ((supportedFeatures != null) && (supportedFeatures != 0));
|
||||||
List<String> get effectList => _getEffectList();
|
List<String> get effectList => getStringListAttributeValue("effect_list");
|
||||||
|
|
||||||
LightEntity(Map rawData) : super(rawData);
|
LightEntity(Map rawData) : super(rawData);
|
||||||
|
|
||||||
Color _getColor() {
|
HSVColor _getColor() {
|
||||||
|
List hs = attributes["hs_color"];
|
||||||
List rgb = attributes["rgb_color"];
|
List rgb = attributes["rgb_color"];
|
||||||
try {
|
try {
|
||||||
if ((rgb != null) && (rgb.length > 0)) {
|
if (hs != null && hs.isNotEmpty) {
|
||||||
return Color.fromARGB(255, rgb[0], rgb[1], rgb[2]);
|
double sat = hs[1]/100;
|
||||||
|
String ssat = sat.toStringAsFixed(2);
|
||||||
|
return HSVColor.fromAHSV(1.0, hs[0], double.parse(ssat), 1.0);
|
||||||
|
} else if (rgb != null && rgb.isNotEmpty) {
|
||||||
|
return HSVColor.fromColor(Color.fromARGB(255, rgb[0], rgb[1], rgb[2]));
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -55,15 +62,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
|
@override
|
||||||
Widget _buildStatePart(BuildContext context) {
|
Widget _buildStatePart(BuildContext context) {
|
||||||
return SwitchStateWidget();
|
return SwitchStateWidget();
|
||||||
@ -71,7 +69,7 @@ class LightEntity extends Entity {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||||
if (!isAdditionalControls) {
|
if (!isAdditionalControls || state == EntityState.unavailable) {
|
||||||
return Container(height: 0.0, width: 0.0);
|
return Container(height: 0.0, width: 0.0);
|
||||||
} else {
|
} else {
|
||||||
return LightControlsWidget();
|
return LightControlsWidget();
|
||||||
|
21
lib/entity_class/lock_entity.class.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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(
|
||||||
|
assumedState: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget _buildStatePartForPage(BuildContext context) {
|
||||||
|
return LockStateWidget(
|
||||||
|
assumedState: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -22,56 +22,59 @@ class MediaPlayerEntity extends Entity {
|
|||||||
|
|
||||||
MediaPlayerEntity(Map rawData) : super(rawData);
|
MediaPlayerEntity(Map rawData) : super(rawData);
|
||||||
|
|
||||||
bool get supportPause => ((attributes["supported_features"] &
|
bool get supportPause => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_PAUSE) ==
|
MediaPlayerEntity.SUPPORT_PAUSE) ==
|
||||||
MediaPlayerEntity.SUPPORT_PAUSE);
|
MediaPlayerEntity.SUPPORT_PAUSE);
|
||||||
bool get supportSeek => ((attributes["supported_features"] &
|
bool get supportSeek => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_SEEK) ==
|
MediaPlayerEntity.SUPPORT_SEEK) ==
|
||||||
MediaPlayerEntity.SUPPORT_SEEK);
|
MediaPlayerEntity.SUPPORT_SEEK);
|
||||||
bool get supportVolumeSet => ((attributes["supported_features"] &
|
bool get supportVolumeSet => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_VOLUME_SET) ==
|
MediaPlayerEntity.SUPPORT_VOLUME_SET) ==
|
||||||
MediaPlayerEntity.SUPPORT_VOLUME_SET);
|
MediaPlayerEntity.SUPPORT_VOLUME_SET);
|
||||||
bool get supportVolumeMute => ((attributes["supported_features"] &
|
bool get supportVolumeMute => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_VOLUME_MUTE) ==
|
MediaPlayerEntity.SUPPORT_VOLUME_MUTE) ==
|
||||||
MediaPlayerEntity.SUPPORT_VOLUME_MUTE);
|
MediaPlayerEntity.SUPPORT_VOLUME_MUTE);
|
||||||
bool get supportPreviousTrack => ((attributes["supported_features"] &
|
bool get supportPreviousTrack => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK) ==
|
MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK) ==
|
||||||
MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK);
|
MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK);
|
||||||
bool get supportNextTrack => ((attributes["supported_features"] &
|
bool get supportNextTrack => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_NEXT_TRACK) ==
|
MediaPlayerEntity.SUPPORT_NEXT_TRACK) ==
|
||||||
MediaPlayerEntity.SUPPORT_NEXT_TRACK);
|
MediaPlayerEntity.SUPPORT_NEXT_TRACK);
|
||||||
|
|
||||||
bool get supportTurnOn => ((attributes["supported_features"] &
|
bool get supportTurnOn => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_TURN_ON) ==
|
MediaPlayerEntity.SUPPORT_TURN_ON) ==
|
||||||
MediaPlayerEntity.SUPPORT_TURN_ON);
|
MediaPlayerEntity.SUPPORT_TURN_ON);
|
||||||
bool get supportTurnOff => ((attributes["supported_features"] &
|
bool get supportTurnOff => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_TURN_OFF) ==
|
MediaPlayerEntity.SUPPORT_TURN_OFF) ==
|
||||||
MediaPlayerEntity.SUPPORT_TURN_OFF);
|
MediaPlayerEntity.SUPPORT_TURN_OFF);
|
||||||
bool get supportPlayMedia => ((attributes["supported_features"] &
|
bool get supportPlayMedia => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_PLAY_MEDIA) ==
|
MediaPlayerEntity.SUPPORT_PLAY_MEDIA) ==
|
||||||
MediaPlayerEntity.SUPPORT_PLAY_MEDIA);
|
MediaPlayerEntity.SUPPORT_PLAY_MEDIA);
|
||||||
bool get supportVolumeStep => ((attributes["supported_features"] &
|
bool get supportVolumeStep => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_VOLUME_STEP) ==
|
MediaPlayerEntity.SUPPORT_VOLUME_STEP) ==
|
||||||
MediaPlayerEntity.SUPPORT_VOLUME_STEP);
|
MediaPlayerEntity.SUPPORT_VOLUME_STEP);
|
||||||
bool get supportSelectSource => ((attributes["supported_features"] &
|
bool get supportSelectSource => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_SELECT_SOURCE) ==
|
MediaPlayerEntity.SUPPORT_SELECT_SOURCE) ==
|
||||||
MediaPlayerEntity.SUPPORT_SELECT_SOURCE);
|
MediaPlayerEntity.SUPPORT_SELECT_SOURCE);
|
||||||
bool get supportStop => ((attributes["supported_features"] &
|
bool get supportStop => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_STOP) ==
|
MediaPlayerEntity.SUPPORT_STOP) ==
|
||||||
MediaPlayerEntity.SUPPORT_STOP);
|
MediaPlayerEntity.SUPPORT_STOP);
|
||||||
bool get supportClearPlaylist => ((attributes["supported_features"] &
|
bool get supportClearPlaylist => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST) ==
|
MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST) ==
|
||||||
MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST);
|
MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST);
|
||||||
bool get supportPlay => ((attributes["supported_features"] &
|
bool get supportPlay => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_PLAY) ==
|
MediaPlayerEntity.SUPPORT_PLAY) ==
|
||||||
MediaPlayerEntity.SUPPORT_PLAY);
|
MediaPlayerEntity.SUPPORT_PLAY);
|
||||||
bool get supportShuffleSet => ((attributes["supported_features"] &
|
bool get supportShuffleSet => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_SHUFFLE_SET) ==
|
MediaPlayerEntity.SUPPORT_SHUFFLE_SET) ==
|
||||||
MediaPlayerEntity.SUPPORT_SHUFFLE_SET);
|
MediaPlayerEntity.SUPPORT_SHUFFLE_SET);
|
||||||
bool get supportSelectSoundMode => ((attributes["supported_features"] &
|
bool get supportSelectSoundMode => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE) ==
|
MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE) ==
|
||||||
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
|
@override
|
||||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||||
return MediaPlayerControls();
|
return MediaPlayerControls();
|
||||||
|
45
lib/entity_class/timer_entity.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class TimerEntity extends Entity {
|
||||||
|
TimerEntity(Map rawData) : super(rawData);
|
||||||
|
|
||||||
|
Duration duration;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(Map rawData) {
|
||||||
|
super.update(rawData);
|
||||||
|
String durationSource = "${attributes["duration"]}";
|
||||||
|
if (durationSource != null && durationSource.isNotEmpty) {
|
||||||
|
try {
|
||||||
|
List<String> durationList = durationSource.split(":");
|
||||||
|
if (durationList.length == 1) {
|
||||||
|
duration = Duration(seconds: int.tryParse(durationList[0] ?? 0));
|
||||||
|
} else if (durationList.length == 2) {
|
||||||
|
duration = Duration(
|
||||||
|
hours: int.tryParse(durationList[0]) ?? 0,
|
||||||
|
minutes: int.tryParse(durationList[1]) ?? 0
|
||||||
|
);
|
||||||
|
} else if (durationList.length == 3) {
|
||||||
|
duration = Duration(
|
||||||
|
hours: int.tryParse(durationList[0]) ?? 0,
|
||||||
|
minutes: int.tryParse(durationList[1]) ?? 0,
|
||||||
|
seconds: int.tryParse(durationList[2]) ?? 0
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Logger.e("Strange $entityId duration format: $durationSource");
|
||||||
|
duration = Duration(seconds: 0);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Logger.e("Error parsing duration for $entityId: ${e.toString()}");
|
||||||
|
duration = Duration(seconds: 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
duration = Duration(seconds: 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget _buildStatePart(BuildContext context) {
|
||||||
|
return TimerState();
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,7 @@ class EntityCollection {
|
|||||||
_allEntities.clear();
|
_allEntities.clear();
|
||||||
//views.clear();
|
//views.clear();
|
||||||
|
|
||||||
TheLogger.debug("Parsing ${rawData.length} Home Assistant entities");
|
Logger.d("Parsing ${rawData.length} Home Assistant entities");
|
||||||
rawData.forEach((rawEntityData) {
|
rawData.forEach((rawEntityData) {
|
||||||
addFromRaw(rawEntityData);
|
addFromRaw(rawEntityData);
|
||||||
});
|
});
|
||||||
@ -44,7 +44,13 @@ class EntityCollection {
|
|||||||
case 'sensor': {
|
case 'sensor': {
|
||||||
return SensorEntity(rawEntityData);
|
return SensorEntity(rawEntityData);
|
||||||
}
|
}
|
||||||
case "automation":
|
case 'lock': {
|
||||||
|
return LockEntity(rawEntityData);
|
||||||
|
}
|
||||||
|
case "automation": {
|
||||||
|
return AutomationEntity(rawEntityData);
|
||||||
|
}
|
||||||
|
|
||||||
case "input_boolean":
|
case "input_boolean":
|
||||||
case "switch": {
|
case "switch": {
|
||||||
return SwitchEntity(rawEntityData);
|
return SwitchEntity(rawEntityData);
|
||||||
@ -52,6 +58,9 @@ class EntityCollection {
|
|||||||
case "light": {
|
case "light": {
|
||||||
return LightEntity(rawEntityData);
|
return LightEntity(rawEntityData);
|
||||||
}
|
}
|
||||||
|
case "group": {
|
||||||
|
return GroupEntity(rawEntityData);
|
||||||
|
}
|
||||||
case "script":
|
case "script":
|
||||||
case "scene": {
|
case "scene": {
|
||||||
return ButtonEntity(rawEntityData);
|
return ButtonEntity(rawEntityData);
|
||||||
@ -74,17 +83,31 @@ class EntityCollection {
|
|||||||
case "cover": {
|
case "cover": {
|
||||||
return CoverEntity(rawEntityData);
|
return CoverEntity(rawEntityData);
|
||||||
}
|
}
|
||||||
|
case "fan": {
|
||||||
|
return FanEntity(rawEntityData);
|
||||||
|
}
|
||||||
|
case "camera": {
|
||||||
|
return CameraEntity(rawEntityData);
|
||||||
|
}
|
||||||
|
case "alarm_control_panel": {
|
||||||
|
return AlarmControlPanelEntity(rawEntityData);
|
||||||
|
}
|
||||||
|
case "timer": {
|
||||||
|
return TimerEntity(rawEntityData);
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return Entity(rawEntityData);
|
return Entity(rawEntityData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateState(Map rawStateData) {
|
bool updateState(Map rawStateData) {
|
||||||
if (isExist(rawStateData["entity_id"])) {
|
if (isExist(rawStateData["entity_id"])) {
|
||||||
updateFromRaw(rawStateData["new_state"] ?? rawStateData["old_state"]);
|
updateFromRaw(rawStateData["new_state"] ?? rawStateData["old_state"]);
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
addFromRaw(rawStateData["new_state"] ?? rawStateData["old_state"]);
|
addFromRaw(rawStateData["new_state"] ?? rawStateData["old_state"]);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,10 +115,9 @@ class EntityCollection {
|
|||||||
_allEntities[entity.entityId] = entity;
|
_allEntities[entity.entityId] = entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity addFromRaw(Map rawEntityData) {
|
void addFromRaw(Map rawEntityData) {
|
||||||
Entity entity = _createEntityInstance(rawEntityData);
|
Entity entity = _createEntityInstance(rawEntityData);
|
||||||
_allEntities[entity.entityId] = entity;
|
_allEntities[entity.entityId] = entity;
|
||||||
return entity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateFromRaw(Map rawEntityData) {
|
void updateFromRaw(Map rawEntityData) {
|
||||||
@ -126,7 +148,7 @@ class EntityCollection {
|
|||||||
List<Entity> groups = [];
|
List<Entity> groups = [];
|
||||||
List<Entity> nonGroupEntities = [];
|
List<Entity> nonGroupEntities = [];
|
||||||
_allEntities.forEach((id, entity){
|
_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);
|
groups.add(entity);
|
||||||
}
|
}
|
||||||
if (!entity.isGroup) {
|
if (!entity.isGroup) {
|
||||||
|
50
lib/entity_widgets/button_entity_container.dart
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class ButtonEntityContainer extends StatelessWidget {
|
||||||
|
|
||||||
|
ButtonEntityContainer({
|
||||||
|
Key key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||||
|
if (entityWrapper.entity.statelessType == StatelessEntityType.MISSED) {
|
||||||
|
return MissedEntityWidget();
|
||||||
|
}
|
||||||
|
if (entityWrapper.entity.statelessType > StatelessEntityType.MISSED) {
|
||||||
|
return Container(width: 0.0, height: 0.0,);
|
||||||
|
}
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => entityWrapper.handleTap(),
|
||||||
|
onLongPress: () => entityWrapper.handleHold(),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: 0.4,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.fitHeight,
|
||||||
|
child: EntityIcon(
|
||||||
|
padding: EdgeInsets.fromLTRB(2.0, 6.0, 2.0, 2.0),
|
||||||
|
size: Sizes.iconSize,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildName()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildName() {
|
||||||
|
return EntityName(
|
||||||
|
padding: EdgeInsets.fromLTRB(Sizes.buttonPadding, 0.0, Sizes.buttonPadding, Sizes.rowPadding),
|
||||||
|
textOverflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 3,
|
||||||
|
wordsWrap: true,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
fontSize: Sizes.nameFontSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -14,16 +14,37 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
{
|
{
|
||||||
badgeIcon = entityModel.entityWrapper.entity.state == "below_horizon"
|
badgeIcon = entityModel.entityWrapper.entity.state == "below_horizon"
|
||||||
? Icon(
|
? Icon(
|
||||||
MaterialDesignIcons.createIconDataFromIconCode(0xf0dc),
|
MaterialDesignIcons.getIconDataFromIconCode(0xf0dc),
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
)
|
)
|
||||||
: Icon(
|
: Icon(
|
||||||
MaterialDesignIcons.createIconDataFromIconCode(0xf5a8),
|
MaterialDesignIcons.getIconDataFromIconCode(0xf5a8),
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "sensor":
|
case "camera":
|
||||||
|
case "media_player":
|
||||||
|
case "binary_sensor":
|
||||||
|
{
|
||||||
|
badgeIcon = EntityIcon(
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
size: iconSize,
|
||||||
|
color: Colors.black
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "device_tracker":
|
||||||
|
{
|
||||||
|
badgeIcon = EntityIcon(
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
size: iconSize,
|
||||||
|
color: Colors.black
|
||||||
|
);
|
||||||
|
onBadgeTextValue = entityModel.entityWrapper.entity.state;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
{
|
{
|
||||||
onBadgeTextValue = entityModel.entityWrapper.entity.unitOfMeasurement;
|
onBadgeTextValue = entityModel.entityWrapper.entity.unitOfMeasurement;
|
||||||
badgeIcon = Center(
|
badgeIcon = Center(
|
||||||
@ -37,18 +58,6 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "device_tracker":
|
|
||||||
{
|
|
||||||
badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData(
|
|
||||||
entityModel.entityWrapper, iconSize, Colors.black);
|
|
||||||
onBadgeTextValue = entityModel.entityWrapper.entity.state;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
badgeIcon = MaterialDesignIcons.createIconWidgetFromEntityData(
|
|
||||||
entityModel.entityWrapper, iconSize, Colors.black);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Widget onBadgeText;
|
Widget onBadgeText;
|
||||||
if (onBadgeTextValue == null || onBadgeTextValue.length == 0) {
|
if (onBadgeTextValue == null || onBadgeTextValue.length == 0) {
|
||||||
|
175
lib/entity_widgets/common/camera_stream_view.dart
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class CameraStreamView extends StatefulWidget {
|
||||||
|
|
||||||
|
CameraStreamView({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CameraStreamViewState createState() => _CameraStreamViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CameraStreamViewState extends State<CameraStreamView> {
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraEntity _entity;
|
||||||
|
|
||||||
|
http.Client client;
|
||||||
|
http.StreamedResponse response;
|
||||||
|
List<int> binaryImage = [];
|
||||||
|
bool timeToStop = false;
|
||||||
|
Completer streamCompleter;
|
||||||
|
bool started = false;
|
||||||
|
bool useSVG = false;
|
||||||
|
|
||||||
|
void _connect() async {
|
||||||
|
started = true;
|
||||||
|
timeToStop = false;
|
||||||
|
String streamUrl = '$homeAssistantWebHost/api/camera_proxy_stream/${_entity.entityId}?token=${_entity.attributes['access_token']}';
|
||||||
|
client = new http.Client(); // create a client to make api calls
|
||||||
|
http.Request request = new http.Request("GET", Uri.parse(streamUrl)); // create get request
|
||||||
|
Logger.d("[Sending] ==> $streamUrl");
|
||||||
|
response = await client.send(request);
|
||||||
|
Logger.d("[Received] <== ${response.headers}");
|
||||||
|
String frameBoundary = response.headers['content-type'].split('boundary=')[1];
|
||||||
|
final int frameBoundarySize = frameBoundary.length;
|
||||||
|
List<int> primaryBuffer=[];
|
||||||
|
int imageSizeStart = 59;
|
||||||
|
int imageSizeEnd = 0;
|
||||||
|
int imageStart = 0;
|
||||||
|
int imageSize = 0;
|
||||||
|
String strBuffer = "";
|
||||||
|
String contentType = "";
|
||||||
|
streamCompleter = Completer();
|
||||||
|
response.stream.transform(
|
||||||
|
StreamTransformer.fromHandlers(
|
||||||
|
handleData: (data, sink) {
|
||||||
|
primaryBuffer.addAll(data);
|
||||||
|
imageStart = 0;
|
||||||
|
imageSizeEnd = 0;
|
||||||
|
if (primaryBuffer.length >= imageSizeStart + 10) {
|
||||||
|
contentType = utf8.decode(
|
||||||
|
primaryBuffer.sublist(frameBoundarySize+16, imageSizeStart + 10), allowMalformed: true).split("\r\n")[0];
|
||||||
|
useSVG = contentType == "image/svg+xml";
|
||||||
|
imageSizeStart = frameBoundarySize + 16 + contentType.length + 18;
|
||||||
|
for (int i = imageSizeStart; i < primaryBuffer.length - 4; i++) {
|
||||||
|
strBuffer = utf8.decode(
|
||||||
|
primaryBuffer.sublist(i, i + 4), allowMalformed: true);
|
||||||
|
if (strBuffer == "\r\n\r\n") {
|
||||||
|
imageSizeEnd = i;
|
||||||
|
imageStart = i + 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (imageSizeEnd > 0) {
|
||||||
|
imageSize = int.tryParse(utf8.decode(
|
||||||
|
primaryBuffer.sublist(imageSizeStart, imageSizeEnd),
|
||||||
|
allowMalformed: true));
|
||||||
|
//Logger.d("content-length: $imageSize");
|
||||||
|
if (imageSize != null &&
|
||||||
|
primaryBuffer.length >= imageStart + imageSize + 2) {
|
||||||
|
sink.add(
|
||||||
|
primaryBuffer.sublist(
|
||||||
|
imageStart, imageStart + imageSize));
|
||||||
|
primaryBuffer.removeRange(0, imageStart + imageSize + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (timeToStop) {
|
||||||
|
sink?.close();
|
||||||
|
streamCompleter.complete();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleError: (error, stack, sink) {
|
||||||
|
Logger.e("Error parsing MJPEG stream: $error");
|
||||||
|
},
|
||||||
|
handleDone: (sink) {
|
||||||
|
Logger.d("Camera stream finished. Reconnecting...");
|
||||||
|
sink?.close();
|
||||||
|
streamCompleter?.complete();
|
||||||
|
_reconnect();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
).listen((d) {
|
||||||
|
if (!timeToStop) {
|
||||||
|
setState(() {
|
||||||
|
binaryImage = d;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reconnect() {
|
||||||
|
disconnect().then((_){
|
||||||
|
_connect();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future disconnect() {
|
||||||
|
Completer disconF = Completer();
|
||||||
|
timeToStop = true;
|
||||||
|
if (streamCompleter != null && !streamCompleter.isCompleted) {
|
||||||
|
streamCompleter.future.then((_) {
|
||||||
|
client?.close();
|
||||||
|
disconF.complete();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
client?.close();
|
||||||
|
disconF.complete();
|
||||||
|
}
|
||||||
|
return disconF.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (!started) {
|
||||||
|
_entity = EntityModel
|
||||||
|
.of(context)
|
||||||
|
.entityWrapper
|
||||||
|
.entity;
|
||||||
|
_connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binaryImage.isEmpty) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
child: const CircularProgressIndicator()
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (useSVG) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
SvgPicture.memory(
|
||||||
|
Uint8List.fromList(binaryImage),
|
||||||
|
placeholderBuilder: (BuildContext context) =>
|
||||||
|
new Container(
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
child: const CircularProgressIndicator()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Image.memory(
|
||||||
|
Uint8List.fromList(binaryImage), gaplessPlayback: true),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
disconnect();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -7,24 +7,16 @@ class EntityAttributesList extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final entityModel = EntityModel.of(context);
|
final entityModel = EntityModel.of(context);
|
||||||
List<Widget> attrs = [];
|
List<Widget> attrs = [];
|
||||||
if ((entityModel.entityWrapper.entity.attributesToShow == null) ||
|
entityModel.entityWrapper.entity.attributes.forEach((name, value) {
|
||||||
(entityModel.entityWrapper.entity.attributesToShow.contains("all"))) {
|
attrs.add(_buildSingleAttribute("$name", "${value ?? '-'}"));
|
||||||
entityModel.entityWrapper.entity.attributes.forEach((name, value) {
|
});
|
||||||
attrs.add(_buildSingleAttribute("$name", "$value"));
|
return Padding(
|
||||||
});
|
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
|
||||||
} else {
|
child: Column(
|
||||||
entityModel.entityWrapper.entity.attributesToShow.forEach((String attr) {
|
children: attrs,
|
||||||
String attrValue = entityModel.entityWrapper.entity.getAttribute("$attr");
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
if (attrValue != null) {
|
mainAxisSize: MainAxisSize.min,
|
||||||
attrs.add(
|
)
|
||||||
_buildSingleAttribute("$attr", "$attrValue"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Column(
|
|
||||||
children: attrs,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +38,7 @@ class EntityAttributesList extends StatelessWidget {
|
|||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
0.0, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
|
0.0, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
"$value",
|
"${value}",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
41
lib/entity_widgets/common/flat_service_button.dart
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class FlatServiceButton extends StatelessWidget {
|
||||||
|
|
||||||
|
final String serviceDomain;
|
||||||
|
final String serviceName;
|
||||||
|
final String entityId;
|
||||||
|
final String text;
|
||||||
|
final double fontSize;
|
||||||
|
|
||||||
|
FlatServiceButton({
|
||||||
|
Key key,
|
||||||
|
@required this.serviceDomain,
|
||||||
|
@required this.serviceName,
|
||||||
|
@required this.entityId,
|
||||||
|
@required this.text,
|
||||||
|
this.fontSize: Sizes.stateFontSize
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
void _setNewState() {
|
||||||
|
eventBus.fire(new ServiceCallEvent(serviceDomain, serviceName, entityId, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: fontSize*2.5,
|
||||||
|
child: FlatButton(
|
||||||
|
onPressed: (() {
|
||||||
|
_setNewState();
|
||||||
|
}),
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style:
|
||||||
|
new TextStyle(fontSize: fontSize, color: Colors.blue),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@ class LastUpdatedWidget extends StatelessWidget {
|
|||||||
final entityModel = EntityModel.of(context);
|
final entityModel = EntityModel.of(context);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
Sizes.leftWidgetPadding, 0.0, 0.0, 0.0),
|
Sizes.leftWidgetPadding, Sizes.rowPadding, 0.0, 0.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${entityModel.entityWrapper.entity.lastUpdated}',
|
'${entityModel.entityWrapper.entity.lastUpdated}',
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
|
101
lib/entity_widgets/common/light_color_picker.dart
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class LightColorPicker extends StatefulWidget {
|
||||||
|
|
||||||
|
final HSVColor color;
|
||||||
|
final onColorSelected;
|
||||||
|
final double hueStep;
|
||||||
|
final double saturationStep;
|
||||||
|
final EdgeInsets padding;
|
||||||
|
|
||||||
|
LightColorPicker({this.color, this.onColorSelected, this.hueStep: 15.0, this.saturationStep: 0.2, this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0)});
|
||||||
|
|
||||||
|
@override
|
||||||
|
LightColorPickerState createState() => new LightColorPickerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class LightColorPickerState extends State<LightColorPicker> {
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
List<Widget> colorRows = [];
|
||||||
|
Border border;
|
||||||
|
bool isSomethingSelected = false;
|
||||||
|
Logger.d("Current colotfor picker: [${widget.color.hue}, ${widget.color.saturation}]");
|
||||||
|
for (double saturation = 1.0; saturation >= (0.0 + widget.saturationStep); saturation = double.parse((saturation - widget.saturationStep).toStringAsFixed(2))) {
|
||||||
|
List<Widget> rowChildren = [];
|
||||||
|
//Logger.d("$saturation");
|
||||||
|
double roundedSaturation = double.parse(widget.color.saturation.toStringAsFixed(1));
|
||||||
|
//Logger.d("Rounded saturation=$roundedSaturation");
|
||||||
|
for (double hue = 0; hue <= (365 - widget.hueStep);
|
||||||
|
hue += widget.hueStep) {
|
||||||
|
bool isExactHue = widget.color.hue.round() == hue;
|
||||||
|
bool isHueInRange = widget.color.hue.round() > hue && widget.color.hue.round() < (hue+widget.hueStep);
|
||||||
|
bool isExactSaturation = roundedSaturation == saturation;
|
||||||
|
bool isSaturationInRange = roundedSaturation > saturation && roundedSaturation < double.parse((saturation+widget.saturationStep).toStringAsFixed(1));
|
||||||
|
if ((isExactHue || isHueInRange) && (isExactSaturation || isSaturationInRange)) {
|
||||||
|
//Logger.d("$isExactHue $isHueInRange $isExactSaturation $isSaturationInRange (${saturation+widget.saturationStep})");
|
||||||
|
border = Border.all(
|
||||||
|
width: 2.0,
|
||||||
|
color: Colors.white,
|
||||||
|
);
|
||||||
|
isSomethingSelected = true;
|
||||||
|
} else {
|
||||||
|
border = null;
|
||||||
|
}
|
||||||
|
HSVColor currentColor = HSVColor.fromAHSV(1.0, hue, double.parse(saturation.toStringAsFixed(2)), 1.0);
|
||||||
|
rowChildren.add(
|
||||||
|
Flexible(
|
||||||
|
child: GestureDetector(
|
||||||
|
child: Container(
|
||||||
|
height: 40.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: currentColor.toColor(),
|
||||||
|
border: border,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () => widget.onColorSelected(currentColor),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
colorRows.add(
|
||||||
|
Row(
|
||||||
|
children: rowChildren,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
colorRows.add(
|
||||||
|
Flexible(
|
||||||
|
child: GestureDetector(
|
||||||
|
child: Container(
|
||||||
|
height: 40.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
border: isSomethingSelected ? null : Border.all(
|
||||||
|
width: 2.0,
|
||||||
|
color: Colors.amber[200],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () => widget.onColorSelected(HSVColor.fromAHSV(1.0, 30.0, 0.0, 1.0)),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return Padding(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: colorRows,
|
||||||
|
),
|
||||||
|
padding: widget.padding,
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -7,56 +7,58 @@ class ModeSelectorWidget extends StatelessWidget {
|
|||||||
final String value;
|
final String value;
|
||||||
final double captionFontSize;
|
final double captionFontSize;
|
||||||
final double valueFontSize;
|
final double valueFontSize;
|
||||||
final double bottomPadding;
|
|
||||||
final onChange;
|
final onChange;
|
||||||
|
final EdgeInsets padding;
|
||||||
|
|
||||||
ModeSelectorWidget({
|
ModeSelectorWidget({
|
||||||
Key key,
|
Key key,
|
||||||
this.caption,
|
@required this.caption,
|
||||||
@required this.options,
|
@required this.options,
|
||||||
this.value,
|
this.value,
|
||||||
@required this.onChange,
|
@required this.onChange,
|
||||||
this.captionFontSize,
|
this.captionFontSize,
|
||||||
this.valueFontSize,
|
this.valueFontSize,
|
||||||
this.bottomPadding
|
this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Padding(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
padding: padding,
|
||||||
children: <Widget>[
|
child: Column(
|
||||||
Text("$caption", style: TextStyle(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
fontSize: captionFontSize ?? Sizes.stateFontSize
|
children: <Widget>[
|
||||||
)),
|
Text("$caption", style: TextStyle(
|
||||||
Row(
|
fontSize: captionFontSize ?? Sizes.stateFontSize
|
||||||
children: <Widget>[
|
)),
|
||||||
Expanded(
|
Row(
|
||||||
child: ButtonTheme(
|
children: <Widget>[
|
||||||
alignedDropdown: true,
|
Expanded(
|
||||||
child: DropdownButton<String>(
|
child: ButtonTheme(
|
||||||
value: value,
|
alignedDropdown: true,
|
||||||
iconSize: 30.0,
|
child: DropdownButton<String>(
|
||||||
isExpanded: true,
|
value: value,
|
||||||
style: TextStyle(
|
iconSize: 30.0,
|
||||||
fontSize: valueFontSize ?? Sizes.largeFontSize,
|
isExpanded: true,
|
||||||
color: Colors.black,
|
style: TextStyle(
|
||||||
|
fontSize: valueFontSize ?? Sizes.largeFontSize,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
hint: Text("Select ${caption.toLowerCase()}"),
|
||||||
|
items: options.map((String value) {
|
||||||
|
return new DropdownMenuItem<String>(
|
||||||
|
value: value,
|
||||||
|
child: Text(value),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (mode) => onChange(mode),
|
||||||
),
|
),
|
||||||
hint: Text("Select ${caption.toLowerCase()}"),
|
|
||||||
items: options.map((String value) {
|
|
||||||
return new DropdownMenuItem<String>(
|
|
||||||
value: value,
|
|
||||||
child: Text(value),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: (mode) => onChange(mode),
|
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
],
|
||||||
],
|
)
|
||||||
),
|
],
|
||||||
Container(height: bottomPadding ?? Sizes.rowPadding,)
|
),
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,33 +6,48 @@ class ModeSwitchWidget extends StatelessWidget {
|
|||||||
final onChange;
|
final onChange;
|
||||||
final double captionFontSize;
|
final double captionFontSize;
|
||||||
final bool value;
|
final bool value;
|
||||||
|
final bool expanded;
|
||||||
|
final EdgeInsets padding;
|
||||||
|
|
||||||
ModeSwitchWidget({
|
ModeSwitchWidget({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.caption,
|
@required this.caption,
|
||||||
@required this.onChange,
|
@required this.onChange,
|
||||||
this.captionFontSize,
|
this.captionFontSize,
|
||||||
this.value
|
this.value,
|
||||||
|
this.expanded: true,
|
||||||
|
this.padding: const EdgeInsets.only(left: Sizes.leftWidgetPadding, right: Sizes.rightWidgetPadding)
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Padding(
|
||||||
children: <Widget>[
|
padding: this.padding,
|
||||||
Expanded(
|
child: Row(
|
||||||
child: Text(
|
children: <Widget>[
|
||||||
"$caption",
|
_buildCaption(),
|
||||||
style: TextStyle(
|
Switch(
|
||||||
fontSize: captionFontSize ?? Sizes.stateFontSize
|
onChanged: (value) => onChange(value),
|
||||||
),
|
value: value ?? false,
|
||||||
),
|
)
|
||||||
),
|
],
|
||||||
Switch(
|
)
|
||||||
onChanged: (value) => onChange(value),
|
|
||||||
value: value ?? false,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildCaption() {
|
||||||
|
Widget captionWidget = Text(
|
||||||
|
"$caption",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: captionFontSize ?? Sizes.stateFontSize
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (expanded) {
|
||||||
|
return Expanded(
|
||||||
|
child: captionWidget,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return captionWidget;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -10,8 +10,9 @@ class UniversalSlider extends StatelessWidget {
|
|||||||
final double min;
|
final double min;
|
||||||
final double max;
|
final double max;
|
||||||
final double value;
|
final double value;
|
||||||
|
final EdgeInsets padding;
|
||||||
|
|
||||||
const UniversalSlider({Key key, this.onChanged, this.onChangeEnd, this.leading, this.closing, this.title, this.min, this.max, this.value}) : super(key: key);
|
const UniversalSlider({Key key, this.onChanged, this.onChangeEnd, this.leading, this.closing, this.title, this.min, this.max, this.value, this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0)}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -33,21 +34,24 @@ class UniversalSlider extends StatelessWidget {
|
|||||||
if (closing != null) {
|
if (closing != null) {
|
||||||
row.add(closing);
|
row.add(closing);
|
||||||
}
|
}
|
||||||
return Column(
|
return Padding(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
padding: padding,
|
||||||
children: <Widget>[
|
child: Column(
|
||||||
Container(height: Sizes.rowPadding,),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
children: <Widget>[
|
||||||
"$title",
|
Container(height: Sizes.rowPadding,),
|
||||||
style: TextStyle(fontSize: Sizes.stateFontSize),
|
Text(
|
||||||
),
|
"$title",
|
||||||
Container(height: Sizes.rowPadding,),
|
style: TextStyle(fontSize: Sizes.stateFontSize),
|
||||||
Row(
|
),
|
||||||
mainAxisSize: MainAxisSize.min,
|
Container(height: Sizes.rowPadding,),
|
||||||
children: row,
|
Row(
|
||||||
),
|
mainAxisSize: MainAxisSize.min,
|
||||||
Container(height: Sizes.rowPadding,)
|
children: row,
|
||||||
],
|
),
|
||||||
|
Container(height: Sizes.rowPadding,)
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
262
lib/entity_widgets/controls/alarm_control_panel_controls.dart
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class AlarmControlPanelControlsWidget extends StatefulWidget {
|
||||||
|
|
||||||
|
final bool extended;
|
||||||
|
final List states;
|
||||||
|
|
||||||
|
const AlarmControlPanelControlsWidget({Key key, @required this.extended, this.states}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_AlarmControlPanelControlsWidgetWidgetState createState() => _AlarmControlPanelControlsWidgetWidgetState();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPanelControlsWidget> {
|
||||||
|
|
||||||
|
String code = "";
|
||||||
|
List supportedStates;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
supportedStates = widget.states ?? ["arm_home", "arm_away"];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void _callService(AlarmControlPanelEntity entity, String service) {
|
||||||
|
eventBus.fire(new ServiceCallEvent(
|
||||||
|
entity.domain, service, entity.entityId,
|
||||||
|
{"code": "$code"}));
|
||||||
|
setState(() {
|
||||||
|
code = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _pinPadHandler(value) {
|
||||||
|
setState(() {
|
||||||
|
code += "$value";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _pinPadClear() {
|
||||||
|
setState(() {
|
||||||
|
code = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _askToTrigger(AlarmControlPanelEntity entity) {
|
||||||
|
// flutter defined function
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
// return object of type Dialog
|
||||||
|
return AlertDialog(
|
||||||
|
title: new Text("Are you sure?"),
|
||||||
|
content: new Text("Are you sure want to trigger alarm ${entity.displayName}?"),
|
||||||
|
actions: <Widget>[
|
||||||
|
FlatButton(
|
||||||
|
child: new Text("Yes"),
|
||||||
|
onPressed: () {
|
||||||
|
eventBus.fire(new ServiceCallEvent(entity.domain, "alarm_trigger", entity.entityId, null));
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FlatButton(
|
||||||
|
child: new Text("No"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
final AlarmControlPanelEntity entity = entityModel.entityWrapper.entity;
|
||||||
|
List<Widget> buttons = [];
|
||||||
|
if (entity.state == EntityState.alarm_disarmed) {
|
||||||
|
if (supportedStates.contains("arm_home")) {
|
||||||
|
buttons.add(
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () => _callService(entity, "alarm_arm_home"),
|
||||||
|
child: Text("ARM HOME"),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (supportedStates.contains("arm_away")) {
|
||||||
|
buttons.add(
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () => _callService(entity, "alarm_arm_away"),
|
||||||
|
child: Text("ARM AWAY"),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (widget.extended) {
|
||||||
|
if (supportedStates.contains("arm_night")) {
|
||||||
|
buttons.add(
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () => _callService(entity, "alarm_arm_night"),
|
||||||
|
child: Text("ARM NIGHT"),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (supportedStates.contains("arm_custom_bypass")) {
|
||||||
|
buttons.add(
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () =>
|
||||||
|
_callService(entity, "alarm_arm_custom_bypass"),
|
||||||
|
child: Text("ARM CUSTOM BYPASS"),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buttons.add(
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () => _callService(entity, "alarm_disarm"),
|
||||||
|
child: Text("DISARM"),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Widget pinPad;
|
||||||
|
if (entity.attributes["code_format"] == null) {
|
||||||
|
pinPad = Container(width: 0.0, height: 0.0,);
|
||||||
|
} else {
|
||||||
|
pinPad = Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Wrap(
|
||||||
|
spacing: 5.0,
|
||||||
|
children: <Widget>[
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () => _pinPadHandler("1"),
|
||||||
|
child: Text("1"),
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () => _pinPadHandler("2"),
|
||||||
|
child: Text("2"),
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () => _pinPadHandler("3"),
|
||||||
|
child: Text("3"),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Wrap(
|
||||||
|
spacing: 5.0,
|
||||||
|
children: <Widget>[
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () => _pinPadHandler("4"),
|
||||||
|
child: Text("4"),
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () => _pinPadHandler("5"),
|
||||||
|
child: Text("5"),
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () => _pinPadHandler("6"),
|
||||||
|
child: Text("6"),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Wrap(
|
||||||
|
spacing: 5.0,
|
||||||
|
children: <Widget>[
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () => _pinPadHandler("7"),
|
||||||
|
child: Text("7"),
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () => _pinPadHandler("8"),
|
||||||
|
child: Text("8"),
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () => _pinPadHandler("9"),
|
||||||
|
child: Text("9"),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Wrap(
|
||||||
|
spacing: 5.0,
|
||||||
|
alignment: WrapAlignment.end,
|
||||||
|
children: <Widget>[
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () => _pinPadHandler("0"),
|
||||||
|
child: Text("0"),
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
onPressed: () => _pinPadClear(),
|
||||||
|
child: Text("CLEAR"),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Widget inputWrapper;
|
||||||
|
if (entity.attributes["code_format"] == null) {
|
||||||
|
inputWrapper = Container(width: 0.0, height: 0.0,);
|
||||||
|
} else {
|
||||||
|
inputWrapper = Container(
|
||||||
|
width: 150.0,
|
||||||
|
child: TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Alarm Code"
|
||||||
|
),
|
||||||
|
//focusNode: _focusNode,
|
||||||
|
obscureText: true,
|
||||||
|
controller: new TextEditingController.fromValue(
|
||||||
|
new TextEditingValue(
|
||||||
|
text: code,
|
||||||
|
selection:
|
||||||
|
new TextSelection.collapsed(offset: code.length)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
code = value;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Widget buttonsWrapper = Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: Sizes.rowPadding),
|
||||||
|
child: Wrap(
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
|
spacing: 15.0,
|
||||||
|
runSpacing: Sizes.rowPadding,
|
||||||
|
children: buttons
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Widget triggerButton = Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
FlatButton(
|
||||||
|
child: Text(
|
||||||
|
"TRIGGER",
|
||||||
|
style: TextStyle(color: Colors.redAccent)
|
||||||
|
),
|
||||||
|
onPressed: () => _askToTrigger(entity),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
widget.extended ? buttonsWrapper : inputWrapper,
|
||||||
|
widget.extended ? inputWrapper : buttonsWrapper,
|
||||||
|
widget.extended ? pinPad : triggerButton
|
||||||
|
]
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -13,6 +13,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
bool _showPending = false;
|
bool _showPending = false;
|
||||||
bool _changedHere = false;
|
bool _changedHere = false;
|
||||||
Timer _resetTimer;
|
Timer _resetTimer;
|
||||||
|
Timer _tempThrottleTimer;
|
||||||
|
Timer _targetTempThrottleTimer;
|
||||||
double _tmpTemperature = 0.0;
|
double _tmpTemperature = 0.0;
|
||||||
double _tmpTargetLow = 0.0;
|
double _tmpTargetLow = 0.0;
|
||||||
double _tmpTargetHigh = 0.0;
|
double _tmpTargetHigh = 0.0;
|
||||||
@ -40,52 +42,68 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
_changedHere = false;
|
_changedHere = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _temperatureUp(ClimateEntity entity, double step) {
|
void _temperatureUp(ClimateEntity entity) {
|
||||||
_tmpTemperature = ((_tmpTemperature + step) <= entity.maxTemp) ? _tmpTemperature + step : entity.maxTemp;
|
_tmpTemperature = ((_tmpTemperature + entity.temperatureStep) <= entity.maxTemp) ? _tmpTemperature + entity.temperatureStep : entity.maxTemp;
|
||||||
_setTemperature(entity);
|
_setTemperature(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _temperatureDown(ClimateEntity entity, double step) {
|
void _temperatureDown(ClimateEntity entity) {
|
||||||
_tmpTemperature = ((_tmpTemperature - step) >= entity.minTemp) ? _tmpTemperature - step : entity.minTemp;
|
_tmpTemperature = ((_tmpTemperature - entity.temperatureStep) >= entity.minTemp) ? _tmpTemperature - entity.temperatureStep : entity.minTemp;
|
||||||
_setTemperature(entity);
|
_setTemperature(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _targetLowUp(ClimateEntity entity, double step) {
|
void _targetLowUp(ClimateEntity entity) {
|
||||||
_tmpTargetLow = ((_tmpTargetLow + step) <= entity.maxTemp) ? _tmpTargetLow + step : entity.maxTemp;
|
_tmpTargetLow = ((_tmpTargetLow + entity.temperatureStep) <= entity.maxTemp) ? _tmpTargetLow + entity.temperatureStep : entity.maxTemp;
|
||||||
_setTargetTemp(entity);
|
_setTargetTemp(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _targetLowDown(ClimateEntity entity, double step) {
|
void _targetLowDown(ClimateEntity entity) {
|
||||||
_tmpTargetLow = ((_tmpTargetLow - step) >= entity.minTemp) ? _tmpTargetLow - step : entity.minTemp;
|
_tmpTargetLow = ((_tmpTargetLow - entity.temperatureStep) >= entity.minTemp) ? _tmpTargetLow - entity.temperatureStep : entity.minTemp;
|
||||||
_setTargetTemp(entity);
|
_setTargetTemp(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _targetHighUp(ClimateEntity entity, double step) {
|
void _targetHighUp(ClimateEntity entity) {
|
||||||
_tmpTargetHigh = ((_tmpTargetHigh + step) <= entity.maxTemp) ? _tmpTargetHigh + step : entity.maxTemp;
|
_tmpTargetHigh = ((_tmpTargetHigh + entity.temperatureStep) <= entity.maxTemp) ? _tmpTargetHigh + entity.temperatureStep : entity.maxTemp;
|
||||||
_setTargetTemp(entity);
|
_setTargetTemp(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _targetHighDown(ClimateEntity entity, double step) {
|
void _targetHighDown(ClimateEntity entity) {
|
||||||
_tmpTargetHigh = ((_tmpTargetHigh - step) >= entity.minTemp) ? _tmpTargetHigh - step : entity.minTemp;
|
_tmpTargetHigh = ((_tmpTargetHigh - entity.temperatureStep) >= entity.minTemp) ? _tmpTargetHigh - entity.temperatureStep : entity.minTemp;
|
||||||
_setTargetTemp(entity);
|
_setTargetTemp(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setTemperature(ClimateEntity entity) {
|
void _setTemperature(ClimateEntity entity) {
|
||||||
|
if (_tempThrottleTimer!=null) {
|
||||||
|
_tempThrottleTimer.cancel();
|
||||||
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_tmpTemperature = double.parse(_tmpTemperature.toStringAsFixed(1));
|
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"temperature": "${_tmpTemperature.toStringAsFixed(1)}"}));
|
_tmpTemperature = double.parse(_tmpTemperature.toStringAsFixed(1));
|
||||||
_resetStateTimer(entity);
|
});
|
||||||
|
_tempThrottleTimer = Timer(Duration(seconds: 2), () {
|
||||||
|
setState(() {
|
||||||
|
_changedHere = true;
|
||||||
|
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"temperature": "${_tmpTemperature.toStringAsFixed(1)}"}));
|
||||||
|
_resetStateTimer(entity);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setTargetTemp(ClimateEntity entity) {
|
void _setTargetTemp(ClimateEntity entity) {
|
||||||
|
if (_targetTempThrottleTimer!=null) {
|
||||||
|
_targetTempThrottleTimer.cancel();
|
||||||
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
|
_changedHere = true;
|
||||||
_tmpTargetLow = double.parse(_tmpTargetLow.toStringAsFixed(1));
|
_tmpTargetLow = double.parse(_tmpTargetLow.toStringAsFixed(1));
|
||||||
_tmpTargetHigh = double.parse(_tmpTargetHigh.toStringAsFixed(1));
|
_tmpTargetHigh = double.parse(_tmpTargetHigh.toStringAsFixed(1));
|
||||||
_changedHere = true;
|
});
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"target_temp_high": "${_tmpTargetHigh.toStringAsFixed(1)}", "target_temp_low": "${_tmpTargetLow.toStringAsFixed(1)}"}));
|
_targetTempThrottleTimer = Timer(Duration(seconds: 2), () {
|
||||||
_resetStateTimer(entity);
|
setState(() {
|
||||||
|
_changedHere = true;
|
||||||
|
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"target_temp_high": "${_tmpTargetHigh.toStringAsFixed(1)}", "target_temp_low": "${_tmpTargetLow.toStringAsFixed(1)}"}));
|
||||||
|
_resetStateTimer(entity);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +185,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
final entityModel = EntityModel.of(context);
|
final entityModel = EntityModel.of(context);
|
||||||
final ClimateEntity entity = entityModel.entityWrapper.entity;
|
final ClimateEntity entity = entityModel.entityWrapper.entity;
|
||||||
if (_changedHere) {
|
if (_changedHere) {
|
||||||
_showPending = (_tmpTemperature != entity.temperature);
|
_showPending = (_tmpTemperature != entity.temperature || _tmpTargetHigh != entity.targetHigh || _tmpTargetLow != entity.targetLow);
|
||||||
_changedHere = false;
|
_changedHere = false;
|
||||||
} else {
|
} else {
|
||||||
_resetTimer?.cancel();
|
_resetTimer?.cancel();
|
||||||
@ -278,10 +296,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
TemperatureControlWidget(
|
TemperatureControlWidget(
|
||||||
value: _tmpTemperature,
|
value: _tmpTemperature,
|
||||||
fontColor: _showPending ? Colors.red : Colors.black,
|
fontColor: _showPending ? Colors.red : Colors.black,
|
||||||
onLargeDec: () => _temperatureDown(entity, 0.5),
|
onDec: () => _temperatureDown(entity),
|
||||||
onLargeInc: () => _temperatureUp(entity, 0.5),
|
onInc: () => _temperatureUp(entity),
|
||||||
onSmallDec: () => _temperatureDown(entity, 0.1),
|
|
||||||
onSmallInc: () => _temperatureUp(entity, 0.1),
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -297,10 +313,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
TemperatureControlWidget(
|
TemperatureControlWidget(
|
||||||
value: _tmpTargetLow,
|
value: _tmpTargetLow,
|
||||||
fontColor: _showPending ? Colors.red : Colors.black,
|
fontColor: _showPending ? Colors.red : Colors.black,
|
||||||
onLargeDec: () => _targetLowDown(entity, 0.5),
|
onDec: () => _targetLowDown(entity),
|
||||||
onLargeInc: () => _targetLowUp(entity, 0.5),
|
onInc: () => _targetLowUp(entity),
|
||||||
onSmallDec: () => _targetLowDown(entity, 0.1),
|
|
||||||
onSmallInc: () => _targetLowUp(entity, 0.1),
|
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(height: 10.0),
|
child: Container(height: 10.0),
|
||||||
@ -312,10 +326,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
TemperatureControlWidget(
|
TemperatureControlWidget(
|
||||||
value: _tmpTargetHigh,
|
value: _tmpTargetHigh,
|
||||||
fontColor: _showPending ? Colors.red : Colors.black,
|
fontColor: _showPending ? Colors.red : Colors.black,
|
||||||
onLargeDec: () => _targetHighDown(entity, 0.5),
|
onDec: () => _targetHighDown(entity),
|
||||||
onLargeInc: () => _targetHighUp(entity, 0.5),
|
onInc: () => _targetHighUp(entity),
|
||||||
onSmallDec: () => _targetHighDown(entity, 0.1),
|
|
||||||
onSmallInc: () => _targetHighUp(entity, 0.1),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -401,18 +413,14 @@ class TemperatureControlWidget extends StatelessWidget {
|
|||||||
final double value;
|
final double value;
|
||||||
final double fontSize;
|
final double fontSize;
|
||||||
final Color fontColor;
|
final Color fontColor;
|
||||||
final onSmallInc;
|
final onInc;
|
||||||
final onLargeInc;
|
final onDec;
|
||||||
final onSmallDec;
|
|
||||||
final onLargeDec;
|
|
||||||
|
|
||||||
TemperatureControlWidget(
|
TemperatureControlWidget(
|
||||||
{Key key,
|
{Key key,
|
||||||
@required this.value,
|
@required this.value,
|
||||||
@required this.onSmallInc,
|
@required this.onInc,
|
||||||
@required this.onSmallDec,
|
@required this.onDec,
|
||||||
@required this.onLargeInc,
|
|
||||||
@required this.onLargeDec,
|
|
||||||
this.fontSize,
|
this.fontSize,
|
||||||
this.fontColor})
|
this.fontColor})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
@ -432,32 +440,16 @@ class TemperatureControlWidget extends StatelessWidget {
|
|||||||
Column(
|
Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||||
'mdi:chevron-up')),
|
'mdi:chevron-up')),
|
||||||
iconSize: 30.0,
|
iconSize: 30.0,
|
||||||
onPressed: () => onSmallInc(),
|
onPressed: () => onInc(),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||||
'mdi:chevron-down')),
|
'mdi:chevron-down')),
|
||||||
iconSize: 30.0,
|
iconSize: 30.0,
|
||||||
onPressed: () => onSmallDec(),
|
onPressed: () => onDec(),
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
|
|
||||||
'mdi:chevron-double-up')),
|
|
||||||
iconSize: 30.0,
|
|
||||||
onPressed: () => onLargeInc(),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
|
|
||||||
'mdi:chevron-double-down')),
|
|
||||||
iconSize: 30.0,
|
|
||||||
onPressed: () => onLargeDec(),
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -157,7 +157,7 @@ class CoverTiltControlsWidget extends StatelessWidget {
|
|||||||
if (entity.supportOpenTilt) {
|
if (entity.supportOpenTilt) {
|
||||||
buttons.add(IconButton(
|
buttons.add(IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
MaterialDesignIcons.createIconDataFromIconName(
|
MaterialDesignIcons.getIconDataFromIconName(
|
||||||
"mdi:arrow-top-right"),
|
"mdi:arrow-top-right"),
|
||||||
size: Sizes.iconSize,
|
size: Sizes.iconSize,
|
||||||
),
|
),
|
||||||
@ -170,7 +170,7 @@ class CoverTiltControlsWidget extends StatelessWidget {
|
|||||||
if (entity.supportStopTilt) {
|
if (entity.supportStopTilt) {
|
||||||
buttons.add(IconButton(
|
buttons.add(IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
MaterialDesignIcons.createIconDataFromIconName("mdi:stop"),
|
MaterialDesignIcons.getIconDataFromIconName("mdi:stop"),
|
||||||
size: Sizes.iconSize,
|
size: Sizes.iconSize,
|
||||||
),
|
),
|
||||||
onPressed: () => _stop(entity)));
|
onPressed: () => _stop(entity)));
|
||||||
@ -182,7 +182,7 @@ class CoverTiltControlsWidget extends StatelessWidget {
|
|||||||
if (entity.supportCloseTilt) {
|
if (entity.supportCloseTilt) {
|
||||||
buttons.add(IconButton(
|
buttons.add(IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
MaterialDesignIcons.createIconDataFromIconName(
|
MaterialDesignIcons.getIconDataFromIconName(
|
||||||
"mdi:arrow-bottom-left"),
|
"mdi:arrow-bottom-left"),
|
||||||
size: Sizes.iconSize,
|
size: Sizes.iconSize,
|
||||||
),
|
),
|
||||||
|
123
lib/entity_widgets/controls/fan_controls.dart
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class FanControlsWidget extends StatefulWidget {
|
||||||
|
|
||||||
|
@override
|
||||||
|
_FanControlsWidgetState createState() => _FanControlsWidgetState();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FanControlsWidgetState extends State<FanControlsWidget> {
|
||||||
|
|
||||||
|
bool _tmpOscillate;
|
||||||
|
bool _tmpDirectionForward;
|
||||||
|
bool _changedHere = false;
|
||||||
|
String _tmpSpeed;
|
||||||
|
|
||||||
|
void _resetState(FanEntity entity) {
|
||||||
|
_tmpOscillate = entity.attributes["oscillating"] ?? false;
|
||||||
|
_tmpDirectionForward = entity.attributes["direction"] == "forward";
|
||||||
|
_tmpSpeed = entity.attributes["speed"];
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setOscillate(FanEntity entity, bool oscillate) {
|
||||||
|
setState(() {
|
||||||
|
_tmpOscillate = oscillate;
|
||||||
|
_changedHere = true;
|
||||||
|
eventBus.fire(new ServiceCallEvent(
|
||||||
|
"fan", "oscillate", entity.entityId,
|
||||||
|
{"oscillating": oscillate}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setDirection(FanEntity entity, bool forward) {
|
||||||
|
setState(() {
|
||||||
|
_tmpDirectionForward = forward;
|
||||||
|
_changedHere = true;
|
||||||
|
eventBus.fire(new ServiceCallEvent(
|
||||||
|
"fan", "set_direction", entity.entityId,
|
||||||
|
{"direction": forward ? "forward" : "reverse"}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setSpeed(FanEntity entity, String value) {
|
||||||
|
setState(() {
|
||||||
|
_tmpSpeed = value;
|
||||||
|
_changedHere = true;
|
||||||
|
eventBus.fire(new ServiceCallEvent(
|
||||||
|
"fan", "set_speed", entity.entityId,
|
||||||
|
{"speed": value}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
final FanEntity entity = entityModel.entityWrapper.entity;
|
||||||
|
if (!_changedHere) {
|
||||||
|
_resetState(entity);
|
||||||
|
} else {
|
||||||
|
_changedHere = false;
|
||||||
|
}
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
_buildSpeedControl(entity),
|
||||||
|
_buildOscillateControl(entity),
|
||||||
|
_buildDirectionControl(entity)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSpeedControl(FanEntity entity) {
|
||||||
|
if (entity.supportSetSpeed && entity.speedList != null && entity.speedList.isNotEmpty) {
|
||||||
|
return ModeSelectorWidget(
|
||||||
|
onChange: (effect) => _setSpeed(entity, effect),
|
||||||
|
caption: "Speed",
|
||||||
|
options: entity.speedList,
|
||||||
|
value: _tmpSpeed
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Container(width: 0.0, height: 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildOscillateControl(FanEntity entity) {
|
||||||
|
if (entity.supportOscillate) {
|
||||||
|
return ModeSwitchWidget(
|
||||||
|
onChange: (value) => _setOscillate(entity, value),
|
||||||
|
caption: "Oscillate",
|
||||||
|
value: _tmpOscillate,
|
||||||
|
expanded: false,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Container(width: 0.0, height: 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDirectionControl(FanEntity entity) {
|
||||||
|
if (entity.supportDirection) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
onPressed: _tmpDirectionForward ?
|
||||||
|
() => _setDirection(entity, false) :
|
||||||
|
null,
|
||||||
|
icon: Icon(Icons.rotate_left),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: !_tmpDirectionForward ?
|
||||||
|
() => _setDirection(entity, true) :
|
||||||
|
null,
|
||||||
|
icon: Icon(Icons.rotate_right),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Container(width: 0.0, height: 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -10,16 +10,18 @@ class LightControlsWidget extends StatefulWidget {
|
|||||||
class _LightControlsWidgetState extends State<LightControlsWidget> {
|
class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||||
|
|
||||||
int _tmpBrightness;
|
int _tmpBrightness;
|
||||||
int _tmpColorTemp;
|
int _tmpWhiteValue;
|
||||||
Color _tmpColor;
|
int _tmpColorTemp = 0;
|
||||||
|
HSVColor _tmpColor = HSVColor.fromAHSV(1.0, 30.0, 0.0, 1.0);
|
||||||
bool _changedHere = false;
|
bool _changedHere = false;
|
||||||
String _tmpEffect;
|
String _tmpEffect;
|
||||||
|
|
||||||
void _resetState(LightEntity entity) {
|
void _resetState(LightEntity entity) {
|
||||||
_tmpBrightness = entity.brightness ?? 0;
|
_tmpBrightness = entity.brightness ?? 0;
|
||||||
_tmpColorTemp = entity.colorTemp;
|
_tmpWhiteValue = entity.whiteValue ?? 0;
|
||||||
_tmpColor = entity.color;
|
_tmpColorTemp = entity.colorTemp ?? entity.minMireds?.toInt();
|
||||||
_tmpEffect = null;
|
_tmpColor = entity.color ?? _tmpColor;
|
||||||
|
_tmpEffect = entity.effect;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setBrightness(LightEntity entity, double value) {
|
void _setBrightness(LightEntity entity, double value) {
|
||||||
@ -38,6 +40,17 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _setWhiteValue(LightEntity entity, double value) {
|
||||||
|
setState(() {
|
||||||
|
_tmpWhiteValue = value.round();
|
||||||
|
_changedHere = true;
|
||||||
|
eventBus.fire(new ServiceCallEvent(
|
||||||
|
entity.domain, "turn_on", entity.entityId,
|
||||||
|
{"white_value": _tmpWhiteValue}));
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void _setColorTemp(LightEntity entity, double value) {
|
void _setColorTemp(LightEntity entity, double value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_tmpColorTemp = value.round();
|
_tmpColorTemp = value.round();
|
||||||
@ -48,20 +61,14 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setColor(LightEntity entity, Color color) {
|
void _setColor(LightEntity entity, HSVColor color) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_tmpColor = color;
|
_tmpColor = color;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
TheLogger.debug( "Color: [${color.red}, ${color.green}, ${color.blue}]");
|
Logger.d( "HS Color: [${color.hue}, ${color.saturation}]");
|
||||||
if ((color == Colors.black) || ((color.red == color.green) && (color.green == color.blue))) {
|
eventBus.fire(new ServiceCallEvent(
|
||||||
eventBus.fire(new ServiceCallEvent(
|
entity.domain, "turn_on", entity.entityId,
|
||||||
entity.domain, "turn_off", entity.entityId,
|
{"hs_color": [color.hue, color.saturation*100]}));
|
||||||
null));
|
|
||||||
} else {
|
|
||||||
eventBus.fire(new ServiceCallEvent(
|
|
||||||
entity.domain, "turn_on", entity.entityId,
|
|
||||||
{"rgb_color": [color.red, color.green, color.blue]}));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +97,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildBrightnessControl(entity),
|
_buildBrightnessControl(entity),
|
||||||
|
_buildWhiteValueControl(entity),
|
||||||
_buildColorTempControl(entity),
|
_buildColorTempControl(entity),
|
||||||
_buildColorControl(entity),
|
_buildColorControl(entity),
|
||||||
_buildEffectControl(entity)
|
_buildEffectControl(entity)
|
||||||
@ -98,7 +106,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBrightnessControl(LightEntity entity) {
|
Widget _buildBrightnessControl(LightEntity entity) {
|
||||||
if ((entity.supportBrightness) && (_tmpBrightness != null) && (entity.state != EntityState.unavailable)) {
|
if ((entity.supportBrightness) && (_tmpBrightness != null)) {
|
||||||
return UniversalSlider(
|
return UniversalSlider(
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -109,7 +117,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
min: 0.0,
|
min: 0.0,
|
||||||
max: 255.0,
|
max: 255.0,
|
||||||
onChangeEnd: (value) => _setBrightness(entity, value),
|
onChangeEnd: (value) => _setBrightness(entity, value),
|
||||||
value: _tmpBrightness.toDouble(),
|
value: _tmpBrightness == null ? 0.0 : _tmpBrightness.toDouble(),
|
||||||
leading: Icon(Icons.brightness_5),
|
leading: Icon(Icons.brightness_5),
|
||||||
title: "Brightness",
|
title: "Brightness",
|
||||||
);
|
);
|
||||||
@ -118,12 +126,33 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildWhiteValueControl(LightEntity entity) {
|
||||||
|
if ((entity.supportWhiteValue) && (_tmpWhiteValue != null)) {
|
||||||
|
return UniversalSlider(
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_changedHere = true;
|
||||||
|
_tmpWhiteValue = value.round();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
min: 0.0,
|
||||||
|
max: 255.0,
|
||||||
|
onChangeEnd: (value) => _setWhiteValue(entity, value),
|
||||||
|
value: _tmpWhiteValue == null ? 0.0 : _tmpWhiteValue.toDouble(),
|
||||||
|
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:file-word-box")),
|
||||||
|
title: "White",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Container(width: 0.0, height: 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildColorTempControl(LightEntity entity) {
|
Widget _buildColorTempControl(LightEntity entity) {
|
||||||
if ((entity.supportColorTemp) && (_tmpColorTemp != null)) {
|
if (entity.supportColorTemp) {
|
||||||
return UniversalSlider(
|
return UniversalSlider(
|
||||||
title: "Color temperature",
|
title: "Color temperature",
|
||||||
leading: Text("Cold", style: TextStyle(color: Colors.lightBlue),),
|
leading: Text("Cold", style: TextStyle(color: Colors.lightBlue),),
|
||||||
value: _tmpColorTemp.toDouble(),
|
value: _tmpColorTemp == null ? entity.maxMireds : _tmpColorTemp.toDouble(),
|
||||||
onChangeEnd: (value) => _setColorTemp(entity, value),
|
onChangeEnd: (value) => _setColorTemp(entity, value),
|
||||||
max: entity.maxMireds,
|
max: entity.maxMireds,
|
||||||
min: entity.minMireds,
|
min: entity.minMireds,
|
||||||
@ -141,25 +170,39 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildColorControl(LightEntity entity) {
|
Widget _buildColorControl(LightEntity entity) {
|
||||||
if ((entity.supportColor) && (entity.color != null)) {
|
if (entity.supportColor) {
|
||||||
|
HSVColor savedColor = HomeAssistantModel.of(context)?.homeAssistant?.savedColor;
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(height: Sizes.rowPadding,),
|
LightColorPicker(
|
||||||
RaisedButton(
|
color: _tmpColor,
|
||||||
onPressed: () => _showColorPicker(entity),
|
onColorSelected: (color) => _setColor(entity, color),
|
||||||
color: _tmpColor ?? Colors.black45,
|
|
||||||
child: Text(
|
|
||||||
"COLOR",
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 50.0,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Container(height: 2*Sizes.rowPadding,),
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
FlatButton(
|
||||||
|
color: _tmpColor.toColor(),
|
||||||
|
child: Text('Copy color'),
|
||||||
|
onPressed: _tmpColor == null ? null : () {
|
||||||
|
setState(() {
|
||||||
|
HomeAssistantModel
|
||||||
|
.of(context)
|
||||||
|
.homeAssistant
|
||||||
|
.savedColor = _tmpColor;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FlatButton(
|
||||||
|
color: savedColor?.toColor() ?? Colors.transparent,
|
||||||
|
child: Text('Paste color'),
|
||||||
|
onPressed: savedColor == null ? null : () {
|
||||||
|
_setColor(entity, savedColor);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -167,28 +210,6 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showColorPicker(LightEntity entity) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
titlePadding: EdgeInsets.all(0.0),
|
|
||||||
contentPadding: EdgeInsets.all(0.0),
|
|
||||||
content: SingleChildScrollView(
|
|
||||||
child: MaterialPicker(
|
|
||||||
pickerColor: _tmpColor,
|
|
||||||
onColorChanged: (color) {
|
|
||||||
_setColor(entity, color);
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
enableLabel: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildEffectControl(LightEntity entity) {
|
Widget _buildEffectControl(LightEntity entity) {
|
||||||
if ((entity.supportEffect) && (entity.effectList != null)) {
|
if ((entity.supportEffect) && (entity.effectList != null)) {
|
||||||
return ModeSelectorWidget(
|
return ModeSelectorWidget(
|
||||||
|
@ -79,11 +79,13 @@ class MediaPlayerWidget extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Image(
|
Flexible(
|
||||||
image: CachedNetworkImageProvider("$homeAssistantWebHost${entity.entityPicture}"),
|
child: Image(
|
||||||
height: 240.0,
|
image: CachedNetworkImageProvider("${entity.entityPicture}"),
|
||||||
width: 320.0,
|
height: 240.0,
|
||||||
fit: BoxFit.contain,
|
//width: 320.0,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -93,7 +95,7 @@ class MediaPlayerWidget extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Icon(
|
Icon(
|
||||||
MaterialDesignIcons.createIconDataFromIconName("mdi:movie"),
|
MaterialDesignIcons.getIconDataFromIconName("mdi:movie"),
|
||||||
size: 150.0,
|
size: 150.0,
|
||||||
color: EntityColor.stateColor("$state"),
|
color: EntityColor.stateColor("$state"),
|
||||||
)
|
)
|
||||||
@ -118,12 +120,12 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
|
|||||||
void _setPower(MediaPlayerEntity entity) {
|
void _setPower(MediaPlayerEntity entity) {
|
||||||
if (entity.state != EntityState.unavailable && entity.state != EntityState.unknown) {
|
if (entity.state != EntityState.unavailable && entity.state != EntityState.unknown) {
|
||||||
if (entity.state == EntityState.off) {
|
if (entity.state == EntityState.off) {
|
||||||
TheLogger.debug("${entity.entityId} turn_on");
|
Logger.d("${entity.entityId} turn_on");
|
||||||
eventBus.fire(new ServiceCallEvent(
|
eventBus.fire(new ServiceCallEvent(
|
||||||
entity.domain, "turn_on", entity.entityId,
|
entity.domain, "turn_on", entity.entityId,
|
||||||
null));
|
null));
|
||||||
} else {
|
} else {
|
||||||
TheLogger.debug("${entity.entityId} turn_off");
|
Logger.d("${entity.entityId} turn_off");
|
||||||
eventBus.fire(new ServiceCallEvent(
|
eventBus.fire(new ServiceCallEvent(
|
||||||
entity.domain, "turn_off", entity.entityId,
|
entity.domain, "turn_off", entity.entityId,
|
||||||
null));
|
null));
|
||||||
@ -132,7 +134,7 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _callAction(MediaPlayerEntity entity, String action) {
|
void _callAction(MediaPlayerEntity entity, String action) {
|
||||||
TheLogger.debug("${entity.entityId} $action");
|
Logger.d("${entity.entityId} $action");
|
||||||
eventBus.fire(new ServiceCallEvent(
|
eventBus.fire(new ServiceCallEvent(
|
||||||
entity.domain, "$action", entity.entityId,
|
entity.domain, "$action", entity.entityId,
|
||||||
null));
|
null));
|
||||||
@ -225,7 +227,7 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
|
|||||||
if (showMenu) {
|
if (showMenu) {
|
||||||
result.add(
|
result.add(
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName(
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||||
"mdi:dots-vertical")),
|
"mdi:dots-vertical")),
|
||||||
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity))
|
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity))
|
||||||
)
|
)
|
||||||
@ -255,6 +257,8 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
|||||||
|
|
||||||
double _newVolumeLevel;
|
double _newVolumeLevel;
|
||||||
bool _changedHere = false;
|
bool _changedHere = false;
|
||||||
|
String _newSoundMode;
|
||||||
|
String _newSource;
|
||||||
|
|
||||||
void _setVolume(double value, String entityId) {
|
void _setVolume(double value, String entityId) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -276,6 +280,22 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
|||||||
eventBus.fire(ServiceCallEvent("media_player", "volume_down", entityId, null));
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final MediaPlayerEntity entity = EntityModel.of(context).entityWrapper.entity;
|
final MediaPlayerEntity entity = EntityModel.of(context).entityWrapper.entity;
|
||||||
@ -287,11 +307,11 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
|||||||
if (entity.state != EntityState.off && entity.state != EntityState.unknown && entity.state != EntityState.unavailable) {
|
if (entity.state != EntityState.off && entity.state != EntityState.unknown && entity.state != EntityState.unavailable) {
|
||||||
Widget muteWidget;
|
Widget muteWidget;
|
||||||
Widget volumeStepWidget;
|
Widget volumeStepWidget;
|
||||||
if (entity.supportVolumeMute) {
|
if (entity.supportVolumeMute || entity.attributes["is_volume_muted"] != null) {
|
||||||
bool isMuted = entity.attributes["is_volume_muted"] ?? false;
|
bool isMuted = entity.attributes["is_volume_muted"] ?? false;
|
||||||
muteWidget =
|
muteWidget =
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(isMuted ? Icons.volume_off : Icons.volume_up),
|
icon: Icon(isMuted ? Icons.volume_up : Icons.volume_off),
|
||||||
onPressed: () => _setVolumeMute(!isMuted, entity.entityId)
|
onPressed: () => _setVolumeMute(!isMuted, entity.entityId)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -302,11 +322,11 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:plus")),
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:plus")),
|
||||||
onPressed: () => _setVolumeUp(entity.entityId)
|
onPressed: () => _setVolumeUp(entity.entityId)
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:minus")),
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:minus")),
|
||||||
onPressed: () => _setVolumeDown(entity.entityId)
|
onPressed: () => _setVolumeDown(entity.entityId)
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -347,6 +367,38 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entity.supportSelectSoundMode && entity.soundModeList != null) {
|
||||||
|
if (!_changedHere) {
|
||||||
|
_newSoundMode = entity.attributes["sound_mode"];
|
||||||
|
} else {
|
||||||
|
_changedHere = false;
|
||||||
|
}
|
||||||
|
children.add(
|
||||||
|
ModeSelectorWidget(
|
||||||
|
options: entity.soundModeList,
|
||||||
|
caption: "Sound mode",
|
||||||
|
value: _newSoundMode,
|
||||||
|
onChange: (value) => _setSoundMode(value, entity.entityId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity.supportSelectSource && entity.sourceList != null) {
|
||||||
|
if (!_changedHere) {
|
||||||
|
_newSource = entity.attributes["source"];
|
||||||
|
} else {
|
||||||
|
_changedHere = false;
|
||||||
|
}
|
||||||
|
children.add(
|
||||||
|
ModeSelectorWidget(
|
||||||
|
options: entity.sourceList,
|
||||||
|
caption: "Source",
|
||||||
|
value: _newSource,
|
||||||
|
onChange: (value) => _setSource(value, entity.entityId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return Column(
|
return Column(
|
||||||
children: children,
|
children: children,
|
||||||
|
@ -10,15 +10,52 @@ class DefaultEntityContainer extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
final EntityModel entityModel = EntityModel.of(context);
|
||||||
mainAxisSize: MainAxisSize.max,
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.MISSED) {
|
||||||
children: <Widget>[
|
return MissedEntityWidget();
|
||||||
EntityIcon(),
|
}
|
||||||
Expanded(
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.DIVIDER) {
|
||||||
child: EntityName(),
|
return Divider();
|
||||||
),
|
}
|
||||||
state
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.SECTION) {
|
||||||
],
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Divider(),
|
||||||
|
Text(
|
||||||
|
"${entityModel.entityWrapper.entity.displayName}",
|
||||||
|
style: TextStyle(color: Colors.blue),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return InkWell(
|
||||||
|
onLongPress: () {
|
||||||
|
if (entityModel.handleTap) {
|
||||||
|
entityModel.entityWrapper.handleHold();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTap: () {
|
||||||
|
if (entityModel.handleTap) {
|
||||||
|
entityModel.entityWrapper.handleTap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: <Widget>[
|
||||||
|
EntityIcon(),
|
||||||
|
|
||||||
|
Flexible(
|
||||||
|
fit: FlexFit.tight,
|
||||||
|
flex: 3,
|
||||||
|
child: EntityName(
|
||||||
|
padding: EdgeInsets.fromLTRB(10.0, 2.0, 10.0, 2.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
state
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,8 @@ part of '../main.dart';
|
|||||||
|
|
||||||
class EntityColor {
|
class EntityColor {
|
||||||
|
|
||||||
|
static const defaultStateColor = Color.fromRGBO(68, 115, 158, 1.0);
|
||||||
|
|
||||||
static const badgeColors = {
|
static const badgeColors = {
|
||||||
"default": Color.fromRGBO(223, 76, 30, 1.0),
|
"default": Color.fromRGBO(223, 76, 30, 1.0),
|
||||||
"binary_sensor": Color.fromRGBO(3, 155, 229, 1.0)
|
"binary_sensor": Color.fromRGBO(3, 155, 229, 1.0)
|
||||||
@ -10,19 +12,29 @@ class EntityColor {
|
|||||||
static const _stateColors = {
|
static const _stateColors = {
|
||||||
EntityState.on: Colors.amber,
|
EntityState.on: Colors.amber,
|
||||||
"auto": Colors.amber,
|
"auto": Colors.amber,
|
||||||
EntityState.idle: Colors.amber,
|
EntityState.active: Colors.amber,
|
||||||
EntityState.playing: Colors.amber,
|
EntityState.playing: Colors.amber,
|
||||||
"above_horizon": Colors.amber,
|
"above_horizon": Colors.amber,
|
||||||
EntityState.home: Colors.amber,
|
EntityState.home: Colors.amber,
|
||||||
EntityState.open: Colors.amber,
|
EntityState.open: Colors.amber,
|
||||||
EntityState.off: Color.fromRGBO(68, 115, 158, 1.0),
|
EntityState.off: defaultStateColor,
|
||||||
EntityState.closed: Color.fromRGBO(68, 115, 158, 1.0),
|
EntityState.closed: defaultStateColor,
|
||||||
"below_horizon": Color.fromRGBO(68, 115, 158, 1.0),
|
"below_horizon": defaultStateColor,
|
||||||
"default": Color.fromRGBO(68, 115, 158, 1.0),
|
"default": defaultStateColor,
|
||||||
|
EntityState.idle: defaultStateColor,
|
||||||
"heat": Colors.redAccent,
|
"heat": Colors.redAccent,
|
||||||
"cool": Colors.lightBlue,
|
"cool": Colors.lightBlue,
|
||||||
EntityState.unavailable: Colors.black26,
|
EntityState.unavailable: Colors.black26,
|
||||||
EntityState.unknown: Colors.black26,
|
EntityState.unknown: Colors.black26,
|
||||||
|
EntityState.alarm_disarmed: Colors.green,
|
||||||
|
EntityState.alarm_armed_away: Colors.redAccent,
|
||||||
|
EntityState.alarm_armed_custom_bypass: Colors.redAccent,
|
||||||
|
EntityState.alarm_armed_home: Colors.redAccent,
|
||||||
|
EntityState.alarm_armed_night: Colors.redAccent,
|
||||||
|
EntityState.alarm_triggered: Colors.redAccent,
|
||||||
|
EntityState.alarm_arming: Colors.amber,
|
||||||
|
EntityState.alarm_disarming: Colors.amber,
|
||||||
|
EntityState.alarm_pending: Colors.amber,
|
||||||
};
|
};
|
||||||
|
|
||||||
static Color stateColor(String state) {
|
static Color stateColor(String state) {
|
||||||
|
@ -3,66 +3,72 @@ part of '../main.dart';
|
|||||||
class EntityIcon extends StatelessWidget {
|
class EntityIcon extends StatelessWidget {
|
||||||
|
|
||||||
final EdgeInsetsGeometry padding;
|
final EdgeInsetsGeometry padding;
|
||||||
final double iconSize;
|
final double size;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
const EntityIcon({Key key, this.iconSize: Sizes.iconSize, this.padding: const EdgeInsets.fromLTRB(
|
const EntityIcon({Key key, this.color, this.size: Sizes.iconSize, this.padding: const EdgeInsets.all(0.0)}) : super(key: key);
|
||||||
Sizes.leftWidgetPadding, 0.0, 12.0, 0.0)}) : super(key: key);
|
|
||||||
|
int getDefaultIconByEntityId(String entityId, String deviceClass, String state) {
|
||||||
|
String domain = entityId.split(".")[0];
|
||||||
|
String iconNameByDomain = MaterialDesignIcons.defaultIconsByDomains["$domain.$state"] ?? MaterialDesignIcons.defaultIconsByDomains["$domain"];
|
||||||
|
String iconNameByDeviceClass;
|
||||||
|
if (deviceClass != null) {
|
||||||
|
iconNameByDeviceClass = MaterialDesignIcons.defaultIconsByDeviceClass["$domain.$deviceClass.$state"] ?? MaterialDesignIcons.defaultIconsByDeviceClass["$domain.$deviceClass"];
|
||||||
|
}
|
||||||
|
String iconName = iconNameByDeviceClass ?? iconNameByDomain;
|
||||||
|
if (iconName != null) {
|
||||||
|
return MaterialDesignIcons.iconsDataMap[iconName] ?? 0;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildIcon(EntityWrapper data, Color color) {
|
||||||
|
if (data == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (data.entityPicture != null) {
|
||||||
|
return Container(
|
||||||
|
height: size+12,
|
||||||
|
width: size+12,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
image: DecorationImage(
|
||||||
|
fit:BoxFit.cover,
|
||||||
|
image: CachedNetworkImageProvider(
|
||||||
|
"${data.entityPicture}"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
String iconName = data.icon;
|
||||||
|
int iconCode = 0;
|
||||||
|
if (iconName.length > 0) {
|
||||||
|
iconCode = MaterialDesignIcons.getIconCodeByIconName(iconName);
|
||||||
|
} else {
|
||||||
|
iconCode = getDefaultIconByEntityId(data.entity.entityId,
|
||||||
|
data.entity.deviceClass, data.entity.state); //
|
||||||
|
}
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(6.0, 6.0, 6.0, 6.0),
|
||||||
|
child: Icon(
|
||||||
|
IconData(iconCode, fontFamily: 'Material Design Icons'),
|
||||||
|
size: size,
|
||||||
|
color: color,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final entityModel = EntityModel.of(context);
|
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||||
return GestureDetector(
|
return Padding(
|
||||||
child: Padding(
|
padding: padding,
|
||||||
padding: padding,
|
child: buildIcon(
|
||||||
child: MaterialDesignIcons.createIconWidgetFromEntityData(
|
entityWrapper,
|
||||||
entityModel.entityWrapper,
|
color ?? EntityColor.stateColor(entityWrapper.entity.state)
|
||||||
iconSize,
|
|
||||||
EntityColor.stateColor(entityModel.entityWrapper.entity.state)
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
onLongPress: () {
|
|
||||||
if (entityModel.handleTap) {
|
|
||||||
switch (entityModel.entityWrapper.holdAction) {
|
|
||||||
case EntityTapAction.toggle: {
|
|
||||||
eventBus.fire(
|
|
||||||
ServiceCallEvent("homeassistant", "toggle", entityModel.entityWrapper.entity.entityId, null));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
eventBus.fire(
|
|
||||||
new ShowEntityPageEvent(entityModel.entityWrapper.entity));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onTap: () {
|
|
||||||
if (entityModel.handleTap) {
|
|
||||||
switch (entityModel.entityWrapper.tapAction) {
|
|
||||||
case EntityTapAction.toggle: {
|
|
||||||
eventBus.fire(
|
|
||||||
ServiceCallEvent("homeassistant", "toggle", entityModel.entityWrapper.entity.entityId, null));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EntityTapAction.callService: {
|
|
||||||
eventBus.fire(
|
|
||||||
ServiceCallEvent(entityModel.entityWrapper.actionService.split(".")[0], entityModel.entityWrapper.actionService.split(".")[1], null, entityModel.entityWrapper.actionServiceData));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
eventBus.fire(
|
|
||||||
new ShowEntityPageEvent(entityModel.entityWrapper.entity));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,27 +7,27 @@ class EntityName extends StatelessWidget {
|
|||||||
final bool wordsWrap;
|
final bool wordsWrap;
|
||||||
final double fontSize;
|
final double fontSize;
|
||||||
final TextAlign textAlign;
|
final TextAlign textAlign;
|
||||||
|
final int maxLines;
|
||||||
|
|
||||||
const EntityName({Key key, this.padding: const EdgeInsets.only(right: 10.0), this.textOverflow: TextOverflow.ellipsis, this.wordsWrap: true, this.fontSize: Sizes.nameFontSize, this.textAlign: TextAlign.left}) : super(key: key);
|
const EntityName({Key key, this.maxLines, this.padding: const EdgeInsets.only(right: 10.0), this.textOverflow: TextOverflow.ellipsis, this.wordsWrap: true, this.fontSize: Sizes.nameFontSize, this.textAlign: TextAlign.left}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final entityModel = EntityModel.of(context);
|
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||||
return GestureDetector(
|
TextStyle textStyle = TextStyle(fontSize: fontSize);
|
||||||
child: Padding(
|
if (entityWrapper.entity.statelessType == StatelessEntityType.WEBLINK) {
|
||||||
padding: padding,
|
textStyle = textStyle.apply(color: Colors.blue, decoration: TextDecoration.underline);
|
||||||
child: Text(
|
}
|
||||||
"${entityModel.entityWrapper.displayName}",
|
return Padding(
|
||||||
overflow: textOverflow,
|
padding: padding,
|
||||||
softWrap: wordsWrap,
|
child: Text(
|
||||||
style: TextStyle(fontSize: fontSize),
|
"${entityWrapper.displayName}",
|
||||||
textAlign: textAlign,
|
overflow: textOverflow,
|
||||||
),
|
softWrap: wordsWrap,
|
||||||
|
maxLines: maxLines,
|
||||||
|
style: textStyle,
|
||||||
|
textAlign: textAlign,
|
||||||
),
|
),
|
||||||
onTap: () =>
|
|
||||||
entityModel.handleTap
|
|
||||||
? eventBus.fire(new ShowEntityPageEvent(entityModel.entityWrapper.entity))
|
|
||||||
: null,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,39 +4,87 @@ class GlanceEntityContainer extends StatelessWidget {
|
|||||||
|
|
||||||
final bool showName;
|
final bool showName;
|
||||||
final bool showState;
|
final bool showState;
|
||||||
|
final bool nameInTheBottom;
|
||||||
|
final double iconSize;
|
||||||
|
final double nameFontSize;
|
||||||
|
final bool wordsWrapInName;
|
||||||
|
|
||||||
GlanceEntityContainer({
|
GlanceEntityContainer({
|
||||||
Key key, @required this.showName, @required this.showState,
|
Key key,
|
||||||
|
@required this.showName,
|
||||||
|
@required this.showState,
|
||||||
|
this.nameInTheBottom: false,
|
||||||
|
this.iconSize: Sizes.iconSize,
|
||||||
|
this.nameFontSize: Sizes.smallFontSize,
|
||||||
|
this.wordsWrapInName: false
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||||
|
if (entityWrapper.entity.statelessType == StatelessEntityType.MISSED) {
|
||||||
|
return MissedEntityWidget();
|
||||||
|
}
|
||||||
|
if (entityWrapper.entity.statelessType > StatelessEntityType.MISSED) {
|
||||||
|
return Container(width: 0.0, height: 0.0,);
|
||||||
|
}
|
||||||
List<Widget> result = [];
|
List<Widget> result = [];
|
||||||
if (showName) {
|
if (!nameInTheBottom) {
|
||||||
result.add(EntityName(
|
if (showName) {
|
||||||
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
|
result.add(_buildName());
|
||||||
textOverflow: TextOverflow.ellipsis,
|
}
|
||||||
wordsWrap: false,
|
} else {
|
||||||
textAlign: TextAlign.center,
|
if (showState) {
|
||||||
fontSize: Sizes.smallFontSize,
|
result.add(_buildState());
|
||||||
));
|
}
|
||||||
}
|
}
|
||||||
result.add(EntityIcon(
|
result.add(
|
||||||
padding: EdgeInsets.all(0.0),
|
EntityIcon(
|
||||||
iconSize: Sizes.largeIconSize,
|
padding: EdgeInsets.all(0.0),
|
||||||
));
|
size: iconSize,
|
||||||
if (showState) {
|
)
|
||||||
result.add(SimpleEntityState(
|
);
|
||||||
textAlign: TextAlign.center,
|
if (!nameInTheBottom) {
|
||||||
expanded: false,
|
if (showState) {
|
||||||
padding: EdgeInsets.only(top: Sizes.rowPadding),
|
result.add(_buildState());
|
||||||
));
|
}
|
||||||
|
} else {
|
||||||
|
result.add(_buildName());
|
||||||
}
|
}
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
return Center(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
child: InkResponse(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
child: ConstrainedBox(
|
||||||
children: result,
|
constraints: BoxConstraints(minWidth: Sizes.iconSize * 2),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
//mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
//crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: result,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () => entityWrapper.handleTap(),
|
||||||
|
onLongPress: () => entityWrapper.handleHold(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildName() {
|
||||||
|
return EntityName(
|
||||||
|
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
|
||||||
|
textOverflow: TextOverflow.ellipsis,
|
||||||
|
wordsWrap: wordsWrapInName,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
fontSize: nameFontSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildState() {
|
||||||
|
return SimpleEntityState(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
expanded: false,
|
||||||
|
maxLines: 1,
|
||||||
|
padding: EdgeInsets.only(top: Sizes.rowPadding),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -94,11 +94,11 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<charts.Series<EntityHistoryMoment, DateTime>> _parseHistory() {
|
List<charts.Series<EntityHistoryMoment, DateTime>> _parseHistory() {
|
||||||
TheLogger.debug(" parsing history...");
|
Logger.d(" parsing history...");
|
||||||
Map<String, List<EntityHistoryMoment>> numericDataLists = {};
|
Map<String, List<EntityHistoryMoment>> numericDataLists = {};
|
||||||
int colorIdCounter = 0;
|
int colorIdCounter = 0;
|
||||||
widget.config.numericAttributesToShow.forEach((String attrName) {
|
widget.config.numericAttributesToShow.forEach((String attrName) {
|
||||||
TheLogger.debug(" parsing attribute $attrName");
|
Logger.d(" parsing attribute $attrName");
|
||||||
List<EntityHistoryMoment> data = [];
|
List<EntityHistoryMoment> data = [];
|
||||||
DateTime now = DateTime.now();
|
DateTime now = DateTime.now();
|
||||||
for (var i = 0; i < widget.rawHistory.length; i++) {
|
for (var i = 0; i < widget.rawHistory.length; i++) {
|
||||||
@ -152,7 +152,7 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
|
|||||||
}
|
}
|
||||||
List<charts.Series<EntityHistoryMoment, DateTime>> result = [];
|
List<charts.Series<EntityHistoryMoment, DateTime>> result = [];
|
||||||
numericDataLists.forEach((attrName, dataList) {
|
numericDataLists.forEach((attrName, dataList) {
|
||||||
TheLogger.debug(" adding ${dataList.length} data values");
|
Logger.d(" adding ${dataList.length} data values");
|
||||||
result.add(
|
result.add(
|
||||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||||
id: "value",
|
id: "value",
|
||||||
|
@ -32,6 +32,7 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
|||||||
List _history;
|
List _history;
|
||||||
bool _needToUpdateHistory;
|
bool _needToUpdateHistory;
|
||||||
DateTime _historyLastUpdated;
|
DateTime _historyLastUpdated;
|
||||||
|
bool _disposed = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -42,21 +43,25 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
|||||||
void _loadHistory(HomeAssistant ha, String entityId) {
|
void _loadHistory(HomeAssistant ha, String entityId) {
|
||||||
DateTime now = DateTime.now();
|
DateTime now = DateTime.now();
|
||||||
if (_historyLastUpdated != null) {
|
if (_historyLastUpdated != null) {
|
||||||
TheLogger.debug("History was updated ${now.difference(_historyLastUpdated).inSeconds} seconds ago");
|
Logger.d("History was updated ${now.difference(_historyLastUpdated).inSeconds} seconds ago");
|
||||||
}
|
}
|
||||||
if (_historyLastUpdated == null || now.difference(_historyLastUpdated).inSeconds > 30) {
|
if (_historyLastUpdated == null || now.difference(_historyLastUpdated).inSeconds > 30) {
|
||||||
_historyLastUpdated = now;
|
_historyLastUpdated = now;
|
||||||
ha.getHistory(entityId).then((history){
|
ha.getHistory(entityId).then((history){
|
||||||
setState(() {
|
if (!_disposed) {
|
||||||
_history = history.isNotEmpty ? history[0] : [];
|
setState(() {
|
||||||
_needToUpdateHistory = false;
|
_history = history.isNotEmpty ? history[0] : [];
|
||||||
});
|
_needToUpdateHistory = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
TheLogger.error("Error loading $entityId history: $e");
|
Logger.e("Error loading $entityId history: $e");
|
||||||
setState(() {
|
if (!_disposed) {
|
||||||
_history = [];
|
setState(() {
|
||||||
_needToUpdateHistory = false;
|
_history = [];
|
||||||
});
|
_needToUpdateHistory = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,7 +127,7 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
TheLogger.debug(" Simple selected as default");
|
Logger.d(" Simple selected as default");
|
||||||
return SimpleStateHistoryChartWidget(
|
return SimpleStateHistoryChartWidget(
|
||||||
rawHistory: _history,
|
rawHistory: _history,
|
||||||
);
|
);
|
||||||
@ -131,4 +136,10 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_disposed = true;
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
19
lib/entity_widgets/missed_entity.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class MissedEntityWidget extends StatelessWidget {
|
||||||
|
MissedEntityWidget({
|
||||||
|
Key key
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final EntityModel entityModel = EntityModel.of(context);
|
||||||
|
return Container(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(5.0),
|
||||||
|
child: Text("Entity not available: ${entityModel.entityWrapper.entity.entityId}"),
|
||||||
|
),
|
||||||
|
color: Colors.amber[100],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +0,0 @@
|
|||||||
part of '../../main.dart';
|
|
||||||
|
|
||||||
class ButtonStateWidget extends StatelessWidget {
|
|
||||||
|
|
||||||
void _setNewState(Entity entity) {
|
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "turn_on", entity.entityId, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final entityModel = EntityModel.of(context);
|
|
||||||
return SizedBox(
|
|
||||||
height: 34.0,
|
|
||||||
child: FlatButton(
|
|
||||||
onPressed: (() {
|
|
||||||
_setNewState(entityModel.entityWrapper.entity);
|
|
||||||
}),
|
|
||||||
child: Text(
|
|
||||||
"EXECUTE",
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
style:
|
|
||||||
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,39 +19,34 @@ class ClimateStateWidget extends StatelessWidget {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
0.0, 0.0, Sizes.rightWidgetPadding, 0.0),
|
0.0, 0.0, Sizes.rightWidgetPadding, 0.0),
|
||||||
child: GestureDetector(
|
child: Column(
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
children: <Widget>[
|
||||||
children: <Widget>[
|
Row(
|
||||||
Row(
|
children: <Widget>[
|
||||||
children: <Widget>[
|
Text("${entity.state}",
|
||||||
Text("${entity.state}",
|
textAlign: TextAlign.right,
|
||||||
textAlign: TextAlign.right,
|
style: new TextStyle(
|
||||||
style: new TextStyle(
|
fontWeight: FontWeight.bold,
|
||||||
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,
|
fontSize: Sizes.stateFontSize,
|
||||||
color: Colors.black45)
|
)),
|
||||||
) :
|
Text(" $targetTemp",
|
||||||
Container(height: 0.0,)
|
textAlign: TextAlign.right,
|
||||||
],
|
style: new TextStyle(
|
||||||
),
|
fontSize: Sizes.stateFontSize,
|
||||||
onTap: () => entityModel.handleTap
|
))
|
||||||
? eventBus.fire(new ShowEntityPageEvent(entity))
|
],
|
||||||
: null,
|
),
|
||||||
|
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,)
|
||||||
|
],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,7 +24,7 @@ class CoverStateWidget extends StatelessWidget {
|
|||||||
if (entity.supportOpen) {
|
if (entity.supportOpen) {
|
||||||
buttons.add(IconButton(
|
buttons.add(IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
MaterialDesignIcons.createIconDataFromIconName("mdi:arrow-up"),
|
MaterialDesignIcons.getIconDataFromIconName("mdi:arrow-up"),
|
||||||
size: Sizes.iconSize,
|
size: Sizes.iconSize,
|
||||||
),
|
),
|
||||||
onPressed: entity.canBeOpened ? () => _open(entity) : null));
|
onPressed: entity.canBeOpened ? () => _open(entity) : null));
|
||||||
@ -36,7 +36,7 @@ class CoverStateWidget extends StatelessWidget {
|
|||||||
if (entity.supportStop) {
|
if (entity.supportStop) {
|
||||||
buttons.add(IconButton(
|
buttons.add(IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
MaterialDesignIcons.createIconDataFromIconName("mdi:stop"),
|
MaterialDesignIcons.getIconDataFromIconName("mdi:stop"),
|
||||||
size: Sizes.iconSize,
|
size: Sizes.iconSize,
|
||||||
),
|
),
|
||||||
onPressed: () => _stop(entity)));
|
onPressed: () => _stop(entity)));
|
||||||
@ -48,7 +48,7 @@ class CoverStateWidget extends StatelessWidget {
|
|||||||
if (entity.supportClose) {
|
if (entity.supportClose) {
|
||||||
buttons.add(IconButton(
|
buttons.add(IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
MaterialDesignIcons.createIconDataFromIconName("mdi:arrow-down"),
|
MaterialDesignIcons.getIconDataFromIconName("mdi:arrow-down"),
|
||||||
size: Sizes.iconSize,
|
size: Sizes.iconSize,
|
||||||
),
|
),
|
||||||
onPressed: entity.canBeClosed ? () => _close(entity) : null));
|
onPressed: entity.canBeClosed ? () => _close(entity) : null));
|
||||||
|
@ -54,7 +54,7 @@ class DateTimeStateWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
TheLogger.warning( "${entity.entityId} has no date and no time");
|
Logger.w( "${entity.entityId} has no date and no time");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
66
lib/entity_widgets/state/lock_state.dart
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class LockStateWidget extends StatelessWidget {
|
||||||
|
|
||||||
|
final bool assumedState;
|
||||||
|
|
||||||
|
const LockStateWidget({Key key, this.assumedState: false}) : super(key: key);
|
||||||
|
|
||||||
|
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;
|
||||||
|
if (assumedState) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
SizedBox(
|
||||||
|
height: 34.0,
|
||||||
|
child: FlatButton(
|
||||||
|
onPressed: () => _unlock(entity),
|
||||||
|
child: Text("UNLOCK",
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style:
|
||||||
|
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 34.0,
|
||||||
|
child: FlatButton(
|
||||||
|
onPressed: () => _lock(entity),
|
||||||
|
child: Text("LOCK",
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style:
|
||||||
|
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return SizedBox(
|
||||||
|
height: 34.0,
|
||||||
|
child: FlatButton(
|
||||||
|
onPressed: (() {
|
||||||
|
entity.isLocked ? _unlock(entity) : _lock(entity);
|
||||||
|
}),
|
||||||
|
child: Text(
|
||||||
|
entity.isLocked ? "UNLOCK" : "LOCK",
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style:
|
||||||
|
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,7 @@ class _SelectStateWidgetState extends State<SelectStateWidget> {
|
|||||||
if (entity.listOptions.isNotEmpty) {
|
if (entity.listOptions.isNotEmpty) {
|
||||||
ctrl = DropdownButton<String>(
|
ctrl = DropdownButton<String>(
|
||||||
value: entity.state,
|
value: entity.state,
|
||||||
|
isExpanded: true,
|
||||||
items: entity.listOptions.map((String value) {
|
items: entity.listOptions.map((String value) {
|
||||||
return new DropdownMenuItem<String>(
|
return new DropdownMenuItem<String>(
|
||||||
value: value,
|
value: value,
|
||||||
@ -36,7 +37,9 @@ class _SelectStateWidgetState extends State<SelectStateWidget> {
|
|||||||
} else {
|
} else {
|
||||||
ctrl = Text('---');
|
ctrl = Text('---');
|
||||||
}
|
}
|
||||||
return Expanded(
|
return Flexible(
|
||||||
|
flex: 2,
|
||||||
|
fit: FlexFit.tight,
|
||||||
//width: Entity.INPUT_WIDTH,
|
//width: Entity.INPUT_WIDTH,
|
||||||
child: ctrl,
|
child: ctrl,
|
||||||
);
|
);
|
||||||
|
@ -5,32 +5,45 @@ class SimpleEntityState extends StatelessWidget {
|
|||||||
final bool expanded;
|
final bool expanded;
|
||||||
final TextAlign textAlign;
|
final TextAlign textAlign;
|
||||||
final EdgeInsetsGeometry padding;
|
final EdgeInsetsGeometry padding;
|
||||||
|
final int maxLines;
|
||||||
|
final String customValue;
|
||||||
|
|
||||||
const SimpleEntityState({Key key, this.expanded: true, this.textAlign: TextAlign.right, this.padding: const EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0)}) : super(key: key);
|
const SimpleEntityState({Key key, this.maxLines: 10, this.expanded: true, this.textAlign: TextAlign.right, this.padding: const EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0), this.customValue}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final entityModel = EntityModel.of(context);
|
final entityModel = EntityModel.of(context);
|
||||||
|
String state;
|
||||||
|
if (customValue == null) {
|
||||||
|
state = entityModel.entityWrapper.entity.displayState ?? "";
|
||||||
|
state = state.replaceAll("\n", "").replaceAll("\t", " ").trim();
|
||||||
|
} else {
|
||||||
|
state = customValue;
|
||||||
|
}
|
||||||
|
TextStyle textStyle = TextStyle(
|
||||||
|
fontSize: Sizes.stateFontSize,
|
||||||
|
);
|
||||||
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.CALL_SERVICE) {
|
||||||
|
textStyle = textStyle.apply(color: Colors.blue);
|
||||||
|
}
|
||||||
|
while (state.contains(" ")){
|
||||||
|
state = state.replaceAll(" ", " ");
|
||||||
|
}
|
||||||
Widget result = Padding(
|
Widget result = Padding(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
child: GestureDetector(
|
child: Text(
|
||||||
child: Text(
|
"$state ${entityModel.entityWrapper.entity.unitOfMeasurement}",
|
||||||
"${entityModel.entityWrapper.entity.state}${entityModel.entityWrapper.entity.unitOfMeasurement}",
|
textAlign: textAlign,
|
||||||
textAlign: textAlign,
|
maxLines: maxLines,
|
||||||
maxLines: 4,
|
overflow: TextOverflow.ellipsis,
|
||||||
overflow: TextOverflow.ellipsis,
|
softWrap: true,
|
||||||
softWrap: true,
|
style: textStyle
|
||||||
style: new TextStyle(
|
)
|
||||||
fontSize: Sizes.stateFontSize,
|
|
||||||
)),
|
|
||||||
onTap: () => entityModel.handleTap
|
|
||||||
? eventBus.fire(new ShowEntityPageEvent(entityModel.entityWrapper.entity))
|
|
||||||
: null,
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
return SizedBox(
|
return Flexible(
|
||||||
width: MediaQuery.of(context).size.width * 0.3,
|
fit: FlexFit.tight,
|
||||||
|
flex: 2,
|
||||||
child: result,
|
child: result,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
part of '../../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class SwitchStateWidget extends StatefulWidget {
|
class SwitchStateWidget extends StatefulWidget {
|
||||||
|
|
||||||
|
final String domainForService;
|
||||||
|
|
||||||
|
const SwitchStateWidget({Key key, this.domainForService}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_SwitchStateWidgetState createState() => _SwitchStateWidgetState();
|
_SwitchStateWidgetState createState() => _SwitchStateWidgetState();
|
||||||
}
|
}
|
||||||
@ -24,11 +29,17 @@ class _SwitchStateWidgetState extends State<SwitchStateWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
newState = entity.state;
|
newState = entity.state;
|
||||||
updatedHere = true;
|
updatedHere = true;
|
||||||
TheLogger.debug("Timer@!!");
|
//TheLogger.debug("Timer@!!");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
String domain;
|
||||||
|
if (widget.domainForService != null) {
|
||||||
|
domain = widget.domainForService;
|
||||||
|
} else {
|
||||||
|
domain = entity.domain;
|
||||||
|
}
|
||||||
eventBus.fire(new ServiceCallEvent(
|
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
|
@override
|
||||||
@ -60,13 +71,13 @@ class _SwitchStateWidgetState extends State<SwitchStateWidget> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => _setNewState(false, entity),
|
onPressed: () => _setNewState(false, entity),
|
||||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:flash-off")),
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:flash-off")),
|
||||||
color: newState == EntityState.on ? Colors.black : Colors.blue,
|
color: newState == EntityState.on ? Colors.black : Colors.blue,
|
||||||
iconSize: Sizes.iconSize,
|
iconSize: Sizes.iconSize,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => _setNewState(true, entity),
|
onPressed: () => _setNewState(true, entity),
|
||||||
icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:flash")),
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:flash")),
|
||||||
color: newState == EntityState.on ? Colors.blue : Colors.black,
|
color: newState == EntityState.on ? Colors.blue : Colors.black,
|
||||||
iconSize: Sizes.iconSize
|
iconSize: Sizes.iconSize
|
||||||
)
|
)
|
||||||
|
@ -66,7 +66,9 @@ class _TextInputStateWidgetState extends State<TextInputStateWidget> {
|
|||||||
_tmpValue = entity.state;
|
_tmpValue = entity.state;
|
||||||
}
|
}
|
||||||
if (entity.isTextField || entity.isPasswordField) {
|
if (entity.isTextField || entity.isPasswordField) {
|
||||||
return Expanded(
|
return Flexible(
|
||||||
|
fit: FlexFit.tight,
|
||||||
|
flex: 2,
|
||||||
//width: Entity.INPUT_WIDTH,
|
//width: Entity.INPUT_WIDTH,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
@ -83,7 +85,7 @@ class _TextInputStateWidgetState extends State<TextInputStateWidget> {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
TheLogger.warning( "Unsupported input mode for ${entity.entityId}");
|
Logger.w( "Unsupported input mode for ${entity.entityId}");
|
||||||
return SimpleEntityState();
|
return SimpleEntityState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
65
lib/entity_widgets/state/timer_state.dart
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class TimerState extends StatefulWidget {
|
||||||
|
//final bool expanded;
|
||||||
|
//final TextAlign textAlign;
|
||||||
|
//final EdgeInsetsGeometry padding;
|
||||||
|
//final int maxLines;
|
||||||
|
|
||||||
|
const TimerState({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_TimerStateState createState() => _TimerStateState();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TimerStateState extends State<TimerState> {
|
||||||
|
|
||||||
|
Timer timer;
|
||||||
|
Duration remaining = Duration(seconds: 0);
|
||||||
|
|
||||||
|
void checkState(TimerEntity entity) {
|
||||||
|
if (entity.state == EntityState.active) {
|
||||||
|
//Logger.d("Timer is active");
|
||||||
|
if (timer == null || !timer.isActive) {
|
||||||
|
timer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||||
|
setState(() {
|
||||||
|
try {
|
||||||
|
int passed = DateTime
|
||||||
|
.now()
|
||||||
|
.difference(entity._lastUpdated)
|
||||||
|
.inSeconds;
|
||||||
|
remaining = Duration(seconds: entity.duration.inSeconds - passed);
|
||||||
|
} catch (e) {
|
||||||
|
Logger.e("Error calculating ${entity.entityId} remaining time: ${e.toString()}");
|
||||||
|
remaining = Duration(seconds: 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timer?.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
EntityModel model = EntityModel.of(context);
|
||||||
|
TimerEntity entity = model.entityWrapper.entity;
|
||||||
|
checkState(entity);
|
||||||
|
if (entity.state != EntityState.active) {
|
||||||
|
return SimpleEntityState();
|
||||||
|
} else {
|
||||||
|
return SimpleEntityState(
|
||||||
|
customValue: "${remaining.toString().split('.')[0]}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
timer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,33 +3,26 @@ part of 'main.dart';
|
|||||||
class HomeAssistant {
|
class HomeAssistant {
|
||||||
String _webSocketAPIEndpoint;
|
String _webSocketAPIEndpoint;
|
||||||
String _password;
|
String _password;
|
||||||
String _authType;
|
|
||||||
bool _useLovelace = false;
|
bool _useLovelace = false;
|
||||||
|
|
||||||
IOWebSocketChannel _hassioChannel;
|
IOWebSocketChannel _hassioChannel;
|
||||||
SendMessageQueue _messageQueue;
|
SendMessageQueue _messageQueue;
|
||||||
|
|
||||||
int _currentMessageId = 0;
|
int _currentMessageId = 0;
|
||||||
int _statesMessageId = 0;
|
|
||||||
int _servicesMessageId = 0;
|
|
||||||
int _subscriptionMessageId = 0;
|
int _subscriptionMessageId = 0;
|
||||||
int _configMessageId = 0;
|
Map<int, Completer> _messageResolver = {};
|
||||||
int _userInfoMessageId = 0;
|
|
||||||
int _lovelaceMessageId = 0;
|
|
||||||
EntityCollection entities;
|
EntityCollection entities;
|
||||||
HomeAssistantUI ui;
|
HomeAssistantUI ui;
|
||||||
Map _instanceConfig = {};
|
Map _instanceConfig = {};
|
||||||
String _userName;
|
String _userName;
|
||||||
|
HSVColor savedColor;
|
||||||
|
|
||||||
Map _rawLovelaceData;
|
Map _rawLovelaceData;
|
||||||
|
|
||||||
|
List<Panel> panels = [];
|
||||||
|
|
||||||
Completer _fetchCompleter;
|
Completer _fetchCompleter;
|
||||||
Completer _statesCompleter;
|
|
||||||
Completer _servicesCompleter;
|
|
||||||
Completer _lovelaceCompleter;
|
|
||||||
Completer _configCompleter;
|
|
||||||
Completer _connectionCompleter;
|
Completer _connectionCompleter;
|
||||||
Completer _userInfoCompleter;
|
|
||||||
Timer _connectionTimer;
|
Timer _connectionTimer;
|
||||||
Timer _fetchTimer;
|
Timer _fetchTimer;
|
||||||
bool autoReconnect = false;
|
bool autoReconnect = false;
|
||||||
@ -56,21 +49,20 @@ class HomeAssistant {
|
|||||||
_messageQueue = SendMessageQueue(messageExpirationTime);
|
_messageQueue = SendMessageQueue(messageExpirationTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateSettings(String url, String password, String authType, bool useLovelace) {
|
void updateSettings(String url, String password, bool useLovelace) {
|
||||||
_webSocketAPIEndpoint = url;
|
_webSocketAPIEndpoint = url;
|
||||||
_password = password;
|
_password = password;
|
||||||
_authType = authType;
|
|
||||||
_useLovelace = useLovelace;
|
_useLovelace = useLovelace;
|
||||||
TheLogger.debug( "Use lovelace is $_useLovelace");
|
Logger.d( "Use lovelace is $_useLovelace");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future fetch() {
|
Future fetch() {
|
||||||
if ((_fetchCompleter != null) && (!_fetchCompleter.isCompleted)) {
|
if ((_fetchCompleter != null) && (!_fetchCompleter.isCompleted)) {
|
||||||
TheLogger.warning("Previous fetch is not complited");
|
Logger.w("Previous fetch is not complited");
|
||||||
} else {
|
} else {
|
||||||
_fetchCompleter = new Completer();
|
_fetchCompleter = new Completer();
|
||||||
_fetchTimer = Timer(fetchTimeout, () {
|
_fetchTimer = Timer(fetchTimeout, () {
|
||||||
TheLogger.error( "Data fetching timeout");
|
Logger.e( "Data fetching timeout");
|
||||||
disconnect().then((_) {
|
disconnect().then((_) {
|
||||||
_completeFetching({
|
_completeFetching({
|
||||||
"errorCode": 9,
|
"errorCode": 9,
|
||||||
@ -90,7 +82,7 @@ class HomeAssistant {
|
|||||||
disconnect() async {
|
disconnect() async {
|
||||||
if ((_hassioChannel != null) && (_hassioChannel.closeCode == null) && (_hassioChannel.sink != null)) {
|
if ((_hassioChannel != null) && (_hassioChannel.closeCode == null) && (_hassioChannel.sink != null)) {
|
||||||
await _hassioChannel.sink.close().timeout(Duration(seconds: 3),
|
await _hassioChannel.sink.close().timeout(Duration(seconds: 3),
|
||||||
onTimeout: () => TheLogger.debug( "Socket sink closed")
|
onTimeout: () => Logger.d( "Socket sink closed")
|
||||||
);
|
);
|
||||||
await _socketSubscription.cancel();
|
await _socketSubscription.cancel();
|
||||||
_hassioChannel = null;
|
_hassioChannel = null;
|
||||||
@ -100,15 +92,15 @@ class HomeAssistant {
|
|||||||
|
|
||||||
Future _connection() {
|
Future _connection() {
|
||||||
if ((_connectionCompleter != null) && (!_connectionCompleter.isCompleted)) {
|
if ((_connectionCompleter != null) && (!_connectionCompleter.isCompleted)) {
|
||||||
TheLogger.debug("Previous connection is not complited");
|
Logger.d("Previous connection is not complited");
|
||||||
} else {
|
} else {
|
||||||
if ((_hassioChannel == null) || (_hassioChannel.closeCode != null)) {
|
if ((_hassioChannel == null) || (_hassioChannel.closeCode != null)) {
|
||||||
_connectionCompleter = new Completer();
|
_connectionCompleter = new Completer();
|
||||||
autoReconnect = false;
|
autoReconnect = false;
|
||||||
disconnect().then((_){
|
disconnect().then((_){
|
||||||
TheLogger.debug( "Socket connecting...");
|
Logger.d( "Socket connecting...");
|
||||||
_connectionTimer = Timer(connectTimeout, () {
|
_connectionTimer = Timer(connectTimeout, () {
|
||||||
TheLogger.error( "Socket connection timeout");
|
Logger.e( "Socket connection timeout");
|
||||||
_handleSocketError(null);
|
_handleSocketError(null);
|
||||||
});
|
});
|
||||||
if (_socketSubscription != null) {
|
if (_socketSubscription != null) {
|
||||||
@ -131,15 +123,15 @@ class HomeAssistant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleSocketClose() {
|
void _handleSocketClose() {
|
||||||
TheLogger.debug("Socket disconnected. Automatic reconnect is $autoReconnect");
|
Logger.d("Socket disconnected. Automatic reconnect is $autoReconnect");
|
||||||
if (autoReconnect) {
|
if (autoReconnect) {
|
||||||
_reconnect();
|
_reconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSocketError(e) {
|
void _handleSocketError(e) {
|
||||||
TheLogger.error("Socket stream Error: $e");
|
Logger.e("Socket stream Error: $e");
|
||||||
TheLogger.debug("Automatic reconnect is $autoReconnect");
|
Logger.d("Automatic reconnect is $autoReconnect");
|
||||||
if (autoReconnect) {
|
if (autoReconnect) {
|
||||||
_reconnect();
|
_reconnect();
|
||||||
} else {
|
} else {
|
||||||
@ -169,6 +161,7 @@ class HomeAssistant {
|
|||||||
futures.add(_getConfig());
|
futures.add(_getConfig());
|
||||||
futures.add(_getServices());
|
futures.add(_getServices());
|
||||||
futures.add(_getUserInfo());
|
futures.add(_getUserInfo());
|
||||||
|
futures.add(_getPanels());
|
||||||
try {
|
try {
|
||||||
await Future.wait(futures);
|
await Future.wait(futures);
|
||||||
_createUI();
|
_createUI();
|
||||||
@ -186,7 +179,7 @@ class HomeAssistant {
|
|||||||
_fetchCompleter.completeError(error);
|
_fetchCompleter.completeError(error);
|
||||||
} else {
|
} else {
|
||||||
autoReconnect = true;
|
autoReconnect = true;
|
||||||
TheLogger.debug( "Fetch complete successful");
|
Logger.d( "Fetch complete successful");
|
||||||
_fetchCompleter.complete();
|
_fetchCompleter.complete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,109 +206,104 @@ class HomeAssistant {
|
|||||||
_handleMessage(String message) {
|
_handleMessage(String message) {
|
||||||
var data = json.decode(message);
|
var data = json.decode(message);
|
||||||
if (data["type"] == "auth_required") {
|
if (data["type"] == "auth_required") {
|
||||||
_sendAuthMessageRaw('{"type": "auth","$_authType": "$_password"}');
|
_sendAuthMessage('{"type": "auth","access_token": "$_password"}');
|
||||||
} else if (data["type"] == "auth_ok") {
|
} else if (data["type"] == "auth_ok") {
|
||||||
_completeConnecting(null);
|
_completeConnecting(null);
|
||||||
_sendSubscribe();
|
_sendSubscribe();
|
||||||
} else if (data["type"] == "auth_invalid") {
|
} else if (data["type"] == "auth_invalid") {
|
||||||
_completeConnecting({"errorCode": 6, "errorMessage": "${data["message"]}"});
|
_completeConnecting({"errorCode": 6, "errorMessage": "${data["message"]}"});
|
||||||
} else if (data["type"] == "result") {
|
} else if (data["type"] == "result") {
|
||||||
TheLogger.debug("[Received] <== id:${data["id"]}, ${data['success'] ? 'success' : 'error'}");
|
Logger.d("[Received] <== id:${data["id"]}, ${data['success'] ? 'success' : 'error'}");
|
||||||
if (data["id"] == _configMessageId) {
|
_messageResolver[data["id"]]?.complete(data);
|
||||||
_parseConfig(data);
|
_messageResolver.remove(data["id"]);
|
||||||
} else if (data["id"] == _statesMessageId) {
|
|
||||||
_parseEntities(data);
|
|
||||||
} else if (data["id"] == _lovelaceMessageId) {
|
|
||||||
_handleLovelace(data);
|
|
||||||
} else if (data["id"] == _servicesMessageId) {
|
|
||||||
_parseServices(data);
|
|
||||||
} else if (data["id"] == _userInfoMessageId) {
|
|
||||||
_parseUserInfo(data);
|
|
||||||
}
|
|
||||||
} else if (data["type"] == "event") {
|
} else if (data["type"] == "event") {
|
||||||
if ((data["event"] != null) && (data["event"]["event_type"] == "state_changed")) {
|
if ((data["event"] != null) && (data["event"]["event_type"] == "state_changed")) {
|
||||||
TheLogger.debug("[Received] <== ${data['type']}.${data["event"]["event_type"]}: ${data["event"]["data"]["entity_id"]}");
|
Logger.d("[Received] <== ${data['type']}.${data["event"]["event_type"]}: ${data["event"]["data"]["entity_id"]}");
|
||||||
_handleEntityStateChange(data["event"]["data"]);
|
_handleEntityStateChange(data["event"]["data"]);
|
||||||
} else if (data["event"] != null) {
|
} else if (data["event"] != null) {
|
||||||
TheLogger.warning("Unhandled event type: ${data["event"]["event_type"]}");
|
Logger.w("Unhandled event type: ${data["event"]["event_type"]}");
|
||||||
} else {
|
} else {
|
||||||
TheLogger.error("Event is null: $message");
|
Logger.e("Event is null: $message");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TheLogger.warning("Unknown message type: $message");
|
Logger.w("Unknown message type: $message");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _sendSubscribe() {
|
void _sendSubscribe() {
|
||||||
_incrementMessageId();
|
_incrementMessageId();
|
||||||
_subscriptionMessageId = _currentMessageId;
|
_subscriptionMessageId = _currentMessageId;
|
||||||
_sendMessageRaw('{"id": $_subscriptionMessageId, "type": "subscribe_events", "event_type": "state_changed"}', false);
|
_send('{"id": $_subscriptionMessageId, "type": "subscribe_events", "event_type": "state_changed"}', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getConfig() {
|
Future _getConfig() async {
|
||||||
_configCompleter = new Completer();
|
await _sendInitialMessage("get_config").then((data) => _instanceConfig = Map.from(data["result"]));
|
||||||
_incrementMessageId();
|
|
||||||
_configMessageId = _currentMessageId;
|
|
||||||
_sendMessageRaw('{"id": $_configMessageId, "type": "get_config"}', false);
|
|
||||||
|
|
||||||
return _configCompleter.future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getStates() {
|
Future _getStates() async {
|
||||||
_statesCompleter = new Completer();
|
await _sendInitialMessage("get_states").then((data) => entities.parse(data["result"]));
|
||||||
_incrementMessageId();
|
|
||||||
_statesMessageId = _currentMessageId;
|
|
||||||
_sendMessageRaw('{"id": $_statesMessageId, "type": "get_states"}', false);
|
|
||||||
|
|
||||||
return _statesCompleter.future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getLovelace() {
|
Future _getLovelace() async {
|
||||||
_lovelaceCompleter = new Completer();
|
await _sendInitialMessage("lovelace/config").then((data) => _rawLovelaceData = data["result"]);
|
||||||
_incrementMessageId();
|
|
||||||
_lovelaceMessageId = _currentMessageId;
|
|
||||||
_sendMessageRaw('{"id": $_lovelaceMessageId, "type": "lovelace/config"}', false);
|
|
||||||
|
|
||||||
return _lovelaceCompleter.future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getUserInfo() {
|
Future _getUserInfo() async {
|
||||||
_userInfoCompleter = new Completer();
|
_userName = null;
|
||||||
_incrementMessageId();
|
await _sendInitialMessage("auth/current_user").then((data) => _userName = data["result"]["name"]);
|
||||||
_userInfoMessageId = _currentMessageId;
|
|
||||||
_sendMessageRaw('{"id": $_userInfoMessageId, "type": "auth/current_user"}', false);
|
|
||||||
|
|
||||||
return _userInfoCompleter.future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getServices() {
|
Future _getServices() async {
|
||||||
_servicesCompleter = new Completer();
|
await _sendInitialMessage("get_services").then((data) => Logger.d("We actually don`t need the list of servcies for now"));
|
||||||
_incrementMessageId();
|
}
|
||||||
_servicesMessageId = _currentMessageId;
|
|
||||||
_sendMessageRaw('{"id": $_servicesMessageId, "type": "get_services"}', false);
|
|
||||||
|
|
||||||
return _servicesCompleter.future;
|
Future _getPanels() async {
|
||||||
|
panels.clear();
|
||||||
|
await _sendInitialMessage("get_panels").then((data) {
|
||||||
|
if (data["success"]) {
|
||||||
|
data["result"].forEach((k,v) {
|
||||||
|
String title = v['title'] == null ? "${k[0]?.toUpperCase()}${k?.substring(1)}" : "${v['title'][0]?.toUpperCase()}${v['title']?.substring(1)}";
|
||||||
|
panels.add(Panel(
|
||||||
|
id: k,
|
||||||
|
type: v["component_name"],
|
||||||
|
title: title,
|
||||||
|
urlPath: v["url_path"],
|
||||||
|
config: v["config"],
|
||||||
|
icon: v["icon"]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_incrementMessageId() {
|
_incrementMessageId() {
|
||||||
_currentMessageId += 1;
|
_currentMessageId += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _sendAuthMessageRaw(String message) {
|
void _sendAuthMessage(String message) {
|
||||||
TheLogger.debug( "[Sending] ==> auth request");
|
Logger.d( "[Sending] ==> auth request");
|
||||||
_hassioChannel.sink.add(message);
|
_hassioChannel.sink.add(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendMessageRaw(String message, bool queued) {
|
Future _sendInitialMessage(String type) {
|
||||||
|
Completer _completer = Completer();
|
||||||
|
_incrementMessageId();
|
||||||
|
_messageResolver[_currentMessageId] = _completer;
|
||||||
|
_send('{"id": $_currentMessageId, "type": "$type"}', false);
|
||||||
|
return _completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
_send(String message, bool queued) {
|
||||||
var sendCompleter = Completer();
|
var sendCompleter = Completer();
|
||||||
if (queued) _messageQueue.add(message);
|
if (queued) _messageQueue.add(message);
|
||||||
_connection().then((r) {
|
_connection().then((r) {
|
||||||
_messageQueue.getActualMessages().forEach((message){
|
_messageQueue.getActualMessages().forEach((message){
|
||||||
TheLogger.debug( "[Sending queued] ==> $message");
|
Logger.d( "[Sending queued] ==> $message");
|
||||||
_hassioChannel.sink.add(message);
|
_hassioChannel.sink.add(message);
|
||||||
});
|
});
|
||||||
if (!queued) {
|
if (!queued) {
|
||||||
TheLogger.debug( "[Sending] ==> $message");
|
Logger.d( "[Sending] ==> $message");
|
||||||
_hassioChannel.sink.add(message);
|
_hassioChannel.sink.add(message);
|
||||||
}
|
}
|
||||||
sendCompleter.complete();
|
sendCompleter.complete();
|
||||||
@ -361,62 +349,41 @@ class HomeAssistant {
|
|||||||
}
|
}
|
||||||
message += '}';
|
message += '}';
|
||||||
}
|
}
|
||||||
return _sendMessageRaw(message, true);
|
return _send(message, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleEntityStateChange(Map eventData) {
|
void _handleEntityStateChange(Map eventData) {
|
||||||
//TheLogger.debug( "New state for ${eventData['entity_id']}");
|
//TheLogger.debug( "New state for ${eventData['entity_id']}");
|
||||||
Map data = Map.from(eventData);
|
Map data = Map.from(eventData);
|
||||||
entities.updateState(data);
|
eventBus.fire(new StateChangedEvent(
|
||||||
eventBus.fire(new StateChangedEvent(data["entity_id"], null));
|
entityId: data["entity_id"],
|
||||||
}
|
needToRebuildUI: entities.updateState(data)
|
||||||
|
));
|
||||||
void _parseConfig(Map data) {
|
|
||||||
if (data["success"] == true) {
|
|
||||||
_instanceConfig = Map.from(data["result"]);
|
|
||||||
_configCompleter.complete();
|
|
||||||
} else {
|
|
||||||
_configCompleter.completeError({"errorCode": 2, "errorMessage": data["error"]["message"]});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _parseUserInfo(Map data) {
|
|
||||||
if (data["success"] == true) {
|
|
||||||
_userName = data["result"]["name"];
|
|
||||||
} else {
|
|
||||||
_userName = null;
|
|
||||||
TheLogger.warning("There was an error getting current user: $data");
|
|
||||||
}
|
|
||||||
_userInfoCompleter.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _parseServices(response) {
|
|
||||||
_servicesCompleter.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleLovelace(response) {
|
|
||||||
if (response["success"] == true) {
|
|
||||||
_rawLovelaceData = response["result"];
|
|
||||||
} else {
|
|
||||||
TheLogger.error("There was an error getting Lovelace config: $response");
|
|
||||||
_rawLovelaceData = null;
|
|
||||||
}
|
|
||||||
_lovelaceCompleter.complete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _parseLovelace() {
|
void _parseLovelace() {
|
||||||
TheLogger.debug("--Title: ${_rawLovelaceData["title"]}");
|
Logger.d("--Title: ${_rawLovelaceData["title"]}");
|
||||||
ui.title = _rawLovelaceData["title"];
|
ui.title = _rawLovelaceData["title"];
|
||||||
int viewCounter = 0;
|
int viewCounter = 0;
|
||||||
TheLogger.debug("--Views count: ${_rawLovelaceData['views'].length}");
|
Logger.d("--Views count: ${_rawLovelaceData['views'].length}");
|
||||||
_rawLovelaceData["views"].forEach((rawView){
|
_rawLovelaceData["views"].forEach((rawView){
|
||||||
TheLogger.debug("----view id: ${rawView['id']}");
|
Logger.d("----view id: ${rawView['id']}");
|
||||||
HAView view = HAView(
|
HAView view = HAView(
|
||||||
count: viewCounter,
|
count: viewCounter,
|
||||||
id: "${rawView['id']}",
|
id: "${rawView['id']}",
|
||||||
name: rawView['title'],
|
name: rawView['title'],
|
||||||
iconName: rawView['icon']
|
iconName: rawView['icon']
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (rawView['badges'] != null && rawView['badges'] is List) {
|
||||||
|
rawView['badges'].forEach((entity) {
|
||||||
|
if (entities.isExist(entity)) {
|
||||||
|
Entity e = entities.get(entity);
|
||||||
|
view.badges.add(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
view.cards.addAll(_createLovelaceCards(rawView["cards"] ?? []));
|
view.cards.addAll(_createLovelaceCards(rawView["cards"] ?? []));
|
||||||
ui.views.add(
|
ui.views.add(
|
||||||
view
|
view
|
||||||
@ -428,36 +395,91 @@ class HomeAssistant {
|
|||||||
List<HACard> _createLovelaceCards(List rawCards) {
|
List<HACard> _createLovelaceCards(List rawCards) {
|
||||||
List<HACard> result = [];
|
List<HACard> result = [];
|
||||||
rawCards.forEach((rawCard){
|
rawCards.forEach((rawCard){
|
||||||
if (rawCard["cards"] != null) {
|
try {
|
||||||
result.addAll(_createLovelaceCards(rawCard["cards"]));
|
bool isThereCardOptionsInside = rawCard["card"] != null;
|
||||||
} else {
|
|
||||||
HACard card = HACard(
|
HACard card = HACard(
|
||||||
id: "card",
|
id: "card",
|
||||||
name: rawCard["title"],
|
name: isThereCardOptionsInside ? rawCard["card"]["title"] ??
|
||||||
type: rawCard['type'],
|
rawCard["card"]["name"] : rawCard["title"] ?? rawCard["name"],
|
||||||
columnsCount: rawCard['columns'] ?? 4,
|
type: isThereCardOptionsInside
|
||||||
showName: rawCard['show_name'] ?? true,
|
? rawCard["card"]['type']
|
||||||
showState: rawCard['show_state'] ?? true,
|
: rawCard['type'],
|
||||||
|
columnsCount: isThereCardOptionsInside
|
||||||
|
? rawCard["card"]['columns'] ?? 4
|
||||||
|
: rawCard['columns'] ?? 4,
|
||||||
|
showName: isThereCardOptionsInside ? rawCard["card"]['show_name'] ??
|
||||||
|
true : rawCard['show_name'] ?? true,
|
||||||
|
showState: isThereCardOptionsInside
|
||||||
|
? rawCard["card"]['show_state'] ?? true
|
||||||
|
: rawCard['show_state'] ?? true,
|
||||||
|
showEmpty: rawCard['show_empty'] ?? true,
|
||||||
|
stateFilter: rawCard['state_filter'] ?? [],
|
||||||
|
states: rawCard['states'],
|
||||||
|
content: rawCard['content']
|
||||||
);
|
);
|
||||||
|
if (rawCard["cards"] != null) {
|
||||||
|
card.childCards = _createLovelaceCards(rawCard["cards"]);
|
||||||
|
}
|
||||||
rawCard["entities"]?.forEach((rawEntity) {
|
rawCard["entities"]?.forEach((rawEntity) {
|
||||||
if (rawEntity is String) {
|
if (rawEntity is String) {
|
||||||
if (entities.isExist(rawEntity)) {
|
if (entities.isExist(rawEntity)) {
|
||||||
card.entities.add(EntityWrapper(entity: entities.get(rawEntity)));
|
card.entities.add(EntityWrapper(entity: entities.get(rawEntity)));
|
||||||
|
} else {
|
||||||
|
card.entities.add(EntityWrapper(entity: Entity.missed(rawEntity)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (entities.isExist(rawEntity["entity"])) {
|
if (rawEntity["type"] == "divider") {
|
||||||
|
card.entities.add(EntityWrapper(entity: Entity.divider()));
|
||||||
|
} else if (rawEntity["type"] == "section") {
|
||||||
|
card.entities.add(EntityWrapper(entity: Entity.section(rawEntity["label"] ?? "")));
|
||||||
|
} else if (rawEntity["type"] == "call-service") {
|
||||||
|
Map uiActionData = {
|
||||||
|
"tap_action": {
|
||||||
|
"action": EntityUIAction.callService,
|
||||||
|
"service": rawEntity["service"],
|
||||||
|
"service_data": rawEntity["service_data"]
|
||||||
|
},
|
||||||
|
"hold_action": EntityUIAction.none
|
||||||
|
};
|
||||||
|
card.entities.add(EntityWrapper(
|
||||||
|
entity: Entity.callService(
|
||||||
|
icon: rawEntity["icon"],
|
||||||
|
name: rawEntity["name"],
|
||||||
|
service: rawEntity["service"],
|
||||||
|
actionName: rawEntity["action_name"]
|
||||||
|
),
|
||||||
|
uiAction: EntityUIAction(rawEntityData: uiActionData)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (rawEntity["type"] == "weblink") {
|
||||||
|
Map uiActionData = {
|
||||||
|
"tap_action": {
|
||||||
|
"action": EntityUIAction.navigate,
|
||||||
|
"service": rawEntity["url"]
|
||||||
|
},
|
||||||
|
"hold_action": EntityUIAction.none
|
||||||
|
};
|
||||||
|
card.entities.add(EntityWrapper(
|
||||||
|
entity: Entity.weblink(
|
||||||
|
icon: rawEntity["icon"],
|
||||||
|
name: rawEntity["name"],
|
||||||
|
url: rawEntity["url"]
|
||||||
|
),
|
||||||
|
uiAction: EntityUIAction(rawEntityData: uiActionData)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (entities.isExist(rawEntity["entity"])) {
|
||||||
Entity e = entities.get(rawEntity["entity"]);
|
Entity e = entities.get(rawEntity["entity"]);
|
||||||
card.entities.add(
|
card.entities.add(
|
||||||
EntityWrapper(
|
EntityWrapper(
|
||||||
entity: e,
|
entity: e,
|
||||||
displayName: rawEntity["name"],
|
displayName: rawEntity["name"],
|
||||||
icon: rawEntity["icon"],
|
icon: rawEntity["icon"],
|
||||||
tapAction: rawEntity["tap_action"] ?? EntityTapAction.moreInfo,
|
uiAction: EntityUIAction(rawEntityData: rawEntity)
|
||||||
holdAction: rawEntity["hold_action"] ?? EntityTapAction.moreInfo,
|
|
||||||
actionService: rawEntity["service"],
|
|
||||||
actionServiceData: rawEntity["service_data"] ?? {"entity_id": e.entityId}
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
card.entities.add(EntityWrapper(entity: Entity.missed(rawEntity["entity"])));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -465,41 +487,45 @@ class HomeAssistant {
|
|||||||
var en = rawCard["entity"];
|
var en = rawCard["entity"];
|
||||||
if (en is String) {
|
if (en is String) {
|
||||||
if (entities.isExist(en)) {
|
if (entities.isExist(en)) {
|
||||||
card.linkedEntity = EntityWrapper(entity: entities.get(en));
|
Entity e = entities.get(en);
|
||||||
|
card.linkedEntityWrapper = EntityWrapper(
|
||||||
|
entity: e,
|
||||||
|
icon: rawCard["icon"],
|
||||||
|
displayName: rawCard["name"],
|
||||||
|
uiAction: EntityUIAction(rawEntityData: rawCard)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
card.linkedEntityWrapper = EntityWrapper(entity: Entity.missed(en));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (entities.isExist(en["entity"])) {
|
if (entities.isExist(en["entity"])) {
|
||||||
card.linkedEntity = EntityWrapper(
|
Entity e = entities.get(en["entity"]);
|
||||||
entity: entities.get(en["entity"]),
|
card.linkedEntityWrapper = EntityWrapper(
|
||||||
icon: en["icon"],
|
entity: e,
|
||||||
displayName: en["name"]
|
icon: en["icon"],
|
||||||
|
displayName: en["name"],
|
||||||
|
uiAction: EntityUIAction(rawEntityData: rawCard)
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
card.linkedEntityWrapper = EntityWrapper(entity: Entity.missed(en["entity"]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
result.add(card);
|
result.add(card);
|
||||||
|
} catch (e) {
|
||||||
|
Logger.e("There was an error parsing card: ${e.toString()}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _parseEntities(response) async {
|
|
||||||
if (response["success"] == false) {
|
|
||||||
_statesCompleter.completeError({"errorCode": 3, "errorMessage": response["error"]["message"]});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
entities.parse(response["result"]);
|
|
||||||
_statesCompleter.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _createUI() {
|
void _createUI() {
|
||||||
ui = HomeAssistantUI();
|
ui = HomeAssistantUI();
|
||||||
if ((_useLovelace) && (_rawLovelaceData != null)) {
|
if ((_useLovelace) && (_rawLovelaceData != null)) {
|
||||||
TheLogger.debug("Creating Lovelace UI");
|
Logger.d("Creating Lovelace UI");
|
||||||
_parseLovelace();
|
_parseLovelace();
|
||||||
} else {
|
} else {
|
||||||
TheLogger.debug("Creating group-based UI");
|
Logger.d("Creating group-based UI");
|
||||||
int viewCounter = 0;
|
int viewCounter = 0;
|
||||||
if (!entities.hasDefaultView) {
|
if (!entities.hasDefaultView) {
|
||||||
HAView view = HAView(
|
HAView view = HAView(
|
||||||
@ -529,8 +555,8 @@ class HomeAssistant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildViews(BuildContext context, bool lovelace) {
|
Widget buildViews(BuildContext context, bool lovelace, TabController tabController) {
|
||||||
return ui.build(context);
|
return ui.build(context, tabController);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List> getHistory(String entityId) async {
|
Future<List> getHistory(String entityId) async {
|
||||||
@ -538,22 +564,15 @@ class HomeAssistant {
|
|||||||
//String endTime = formatDate(now, [yyyy, '-', mm, '-', dd, 'T', HH, ':', nn, ':', ss, z]);
|
//String endTime = formatDate(now, [yyyy, '-', mm, '-', dd, 'T', HH, ':', nn, ':', ss, z]);
|
||||||
String startTime = formatDate(now.subtract(Duration(hours: 24)), [yyyy, '-', mm, '-', dd, 'T', HH, ':', nn, ':', ss, z]);
|
String startTime = formatDate(now.subtract(Duration(hours: 24)), [yyyy, '-', mm, '-', dd, 'T', HH, ':', nn, ':', ss, z]);
|
||||||
String url = "$homeAssistantWebHost/api/history/period/$startTime?&filter_entity_id=$entityId";
|
String url = "$homeAssistantWebHost/api/history/period/$startTime?&filter_entity_id=$entityId";
|
||||||
TheLogger.debug("[Sending] ==> $url");
|
Logger.d("[Sending] ==> $url");
|
||||||
http.Response historyResponse;
|
http.Response historyResponse;
|
||||||
if (_authType == "access_token") {
|
historyResponse = await http.get(url, headers: {
|
||||||
historyResponse = await http.get(url, headers: {
|
|
||||||
"authorization": "Bearer $_password",
|
"authorization": "Bearer $_password",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
historyResponse = await http.get(url, headers: {
|
|
||||||
"X-HA-Access": "$_password",
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var history = json.decode(historyResponse.body);
|
var history = json.decode(historyResponse.body);
|
||||||
if (history is List) {
|
if (history is List) {
|
||||||
TheLogger.debug( "[Received] <== ${history.first.length} history recors");
|
Logger.d( "[Received] <== ${history.first.length} history recors");
|
||||||
return history;
|
return history;
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
|
@ -19,7 +19,7 @@ class _LogViewPageState extends State<LogViewPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_loadLog() async {
|
_loadLog() async {
|
||||||
_logData = TheLogger.getLog();
|
_logData = Logger.getLog();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
385
lib/main.dart
@ -1,23 +1,27 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:web_socket_channel/io.dart';
|
import 'package:web_socket_channel/io.dart';
|
||||||
import 'package:progress_indicators/progress_indicators.dart';
|
|
||||||
import 'package:event_bus/event_bus.dart';
|
import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart' as urlLauncher;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:date_format/date_format.dart';
|
import 'package:date_format/date_format.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:flutter_colorpicker/material_picker.dart';
|
|
||||||
import 'package:charts_flutter/flutter.dart' as charts;
|
import 'package:charts_flutter/flutter.dart' as charts;
|
||||||
|
import 'package:progress_indicators/progress_indicators.dart';
|
||||||
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||||
|
|
||||||
part 'entity_class/const.dart';
|
part 'entity_class/const.dart';
|
||||||
part 'entity_class/entity.class.dart';
|
part 'entity_class/entity.class.dart';
|
||||||
part 'entity_class/entity_wrapper.class.dart';
|
part 'entity_class/entity_wrapper.class.dart';
|
||||||
|
part 'entity_class/timer_entity.dart';
|
||||||
part 'entity_class/switch_entity.class.dart';
|
part 'entity_class/switch_entity.class.dart';
|
||||||
part 'entity_class/button_entity.class.dart';
|
part 'entity_class/button_entity.class.dart';
|
||||||
part 'entity_class/text_entity.class.dart';
|
part 'entity_class/text_entity.class.dart';
|
||||||
@ -29,10 +33,18 @@ part 'entity_class/select_entity.class.dart';
|
|||||||
part 'entity_class/other_entity.class.dart';
|
part 'entity_class/other_entity.class.dart';
|
||||||
part 'entity_class/slider_entity.dart';
|
part 'entity_class/slider_entity.dart';
|
||||||
part 'entity_class/media_player_entity.class.dart';
|
part 'entity_class/media_player_entity.class.dart';
|
||||||
|
part 'entity_class/lock_entity.class.dart';
|
||||||
|
part 'entity_class/group_entity.class.dart';
|
||||||
|
part 'entity_class/fan_entity.class.dart';
|
||||||
|
part 'entity_class/automation_entity.dart';
|
||||||
|
part 'entity_class/camera_entity.class.dart';
|
||||||
|
part 'entity_class/alarm_control_panel.class.dart';
|
||||||
part 'entity_widgets/common/badge.dart';
|
part 'entity_widgets/common/badge.dart';
|
||||||
part 'entity_widgets/model_widgets.dart';
|
part 'entity_widgets/model_widgets.dart';
|
||||||
part 'entity_widgets/default_entity_container.dart';
|
part 'entity_widgets/default_entity_container.dart';
|
||||||
|
part 'entity_widgets/missed_entity.dart';
|
||||||
part 'entity_widgets/glance_entity_container.dart';
|
part 'entity_widgets/glance_entity_container.dart';
|
||||||
|
part 'entity_widgets/button_entity_container.dart';
|
||||||
part 'entity_widgets/common/entity_attributes_list.dart';
|
part 'entity_widgets/common/entity_attributes_list.dart';
|
||||||
part 'entity_widgets/entity_icon.dart';
|
part 'entity_widgets/entity_icon.dart';
|
||||||
part 'entity_widgets/entity_name.dart';
|
part 'entity_widgets/entity_name.dart';
|
||||||
@ -40,6 +52,9 @@ part 'entity_widgets/common/last_updated.dart';
|
|||||||
part 'entity_widgets/common/mode_swicth.dart';
|
part 'entity_widgets/common/mode_swicth.dart';
|
||||||
part 'entity_widgets/common/mode_selector.dart';
|
part 'entity_widgets/common/mode_selector.dart';
|
||||||
part 'entity_widgets/common/universal_slider.dart';
|
part 'entity_widgets/common/universal_slider.dart';
|
||||||
|
part 'entity_widgets/common/flat_service_button.dart';
|
||||||
|
part 'entity_widgets/common/light_color_picker.dart';
|
||||||
|
part 'entity_widgets/common/camera_stream_view.dart';
|
||||||
part 'entity_widgets/entity_colors.class.dart';
|
part 'entity_widgets/entity_colors.class.dart';
|
||||||
part 'entity_widgets/entity_page_container.dart';
|
part 'entity_widgets/entity_page_container.dart';
|
||||||
part 'entity_widgets/history_chart/entity_history.dart';
|
part 'entity_widgets/history_chart/entity_history.dart';
|
||||||
@ -53,15 +68,19 @@ part 'entity_widgets/controls/slider_controls.dart';
|
|||||||
part 'entity_widgets/state/text_input_state.dart';
|
part 'entity_widgets/state/text_input_state.dart';
|
||||||
part 'entity_widgets/state/select_state.dart';
|
part 'entity_widgets/state/select_state.dart';
|
||||||
part 'entity_widgets/state/simple_state.dart';
|
part 'entity_widgets/state/simple_state.dart';
|
||||||
|
part 'entity_widgets/state/timer_state.dart';
|
||||||
part 'entity_widgets/state/climate_state.dart';
|
part 'entity_widgets/state/climate_state.dart';
|
||||||
part 'entity_widgets/state/cover_state.dart';
|
part 'entity_widgets/state/cover_state.dart';
|
||||||
part 'entity_widgets/state/date_time_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/climate_controls.dart';
|
||||||
part 'entity_widgets/controls/cover_controls.dart';
|
part 'entity_widgets/controls/cover_controls.dart';
|
||||||
part 'entity_widgets/controls/light_controls.dart';
|
part 'entity_widgets/controls/light_controls.dart';
|
||||||
part 'entity_widgets/controls/media_player_widgets.dart';
|
part 'entity_widgets/controls/media_player_widgets.dart';
|
||||||
|
part 'entity_widgets/controls/fan_controls.dart';
|
||||||
|
part 'entity_widgets/controls/alarm_control_panel_controls.dart';
|
||||||
part 'settings.page.dart';
|
part 'settings.page.dart';
|
||||||
|
part 'panel.page.dart';
|
||||||
part 'home_assistant.class.dart';
|
part 'home_assistant.class.dart';
|
||||||
part 'log.page.dart';
|
part 'log.page.dart';
|
||||||
part 'entity.page.dart';
|
part 'entity.page.dart';
|
||||||
@ -72,24 +91,23 @@ part 'ui_class/ui.dart';
|
|||||||
part 'ui_class/view.class.dart';
|
part 'ui_class/view.class.dart';
|
||||||
part 'ui_class/card.class.dart';
|
part 'ui_class/card.class.dart';
|
||||||
part 'ui_class/sizes_class.dart';
|
part 'ui_class/sizes_class.dart';
|
||||||
|
part 'ui_class/panel_class.dart';
|
||||||
part 'ui_widgets/view.dart';
|
part 'ui_widgets/view.dart';
|
||||||
part 'ui_widgets/entities_card.dart';
|
part 'ui_widgets/card_widget.dart';
|
||||||
part 'ui_widgets/glance_card.dart';
|
|
||||||
part 'ui_widgets/unsupported_card.dart';
|
|
||||||
part 'ui_widgets/media_control_card.dart';
|
|
||||||
part 'ui_widgets/card_header_widget.dart';
|
part 'ui_widgets/card_header_widget.dart';
|
||||||
|
part 'ui_widgets/config_panel_widget.dart';
|
||||||
|
|
||||||
|
|
||||||
EventBus eventBus = new EventBus();
|
EventBus eventBus = new EventBus();
|
||||||
const String appName = "HA Client";
|
const String appName = "HA Client";
|
||||||
const appVersion = "0.3.9";
|
const appVersion = "0.5.4";
|
||||||
|
|
||||||
String homeAssistantWebHost;
|
String homeAssistantWebHost;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
FlutterError.onError = (errorDetails) {
|
FlutterError.onError = (errorDetails) {
|
||||||
TheLogger.error( "${errorDetails.exception}");
|
Logger.e( "${errorDetails.exception}");
|
||||||
if (TheLogger.isInDebugMode) {
|
if (Logger.isInDebugMode) {
|
||||||
FlutterError.dumpErrorToConsole(errorDetails);
|
FlutterError.dumpErrorToConsole(errorDetails);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -97,9 +115,9 @@ void main() {
|
|||||||
runZoned(() {
|
runZoned(() {
|
||||||
runApp(new HAClientApp());
|
runApp(new HAClientApp());
|
||||||
}, onError: (error, stack) {
|
}, onError: (error, stack) {
|
||||||
TheLogger.error("$error");
|
Logger.e("$error");
|
||||||
TheLogger.error("$stack");
|
Logger.e("$stack");
|
||||||
if (TheLogger.isInDebugMode) {
|
if (Logger.isInDebugMode) {
|
||||||
debugPrint("$stack");
|
debugPrint("$stack");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -118,6 +136,7 @@ class HAClientApp extends StatelessWidget {
|
|||||||
routes: {
|
routes: {
|
||||||
"/": (context) => MainPage(title: 'HA Client'),
|
"/": (context) => MainPage(title: 'HA Client'),
|
||||||
"/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"),
|
"/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"),
|
||||||
|
"/configuration": (context) => PanelPage(title: "Configuration"),
|
||||||
"/log-view": (context) => LogViewPage(title: "Log")
|
"/log-view": (context) => LogViewPage(title: "Log")
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -133,24 +152,22 @@ class MainPage extends StatefulWidget {
|
|||||||
_MainPageState createState() => new _MainPageState();
|
_MainPageState createState() => new _MainPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
class _MainPageState extends State<MainPage> with WidgetsBindingObserver, TickerProviderStateMixin {
|
||||||
HomeAssistant _homeAssistant;
|
HomeAssistant _homeAssistant;
|
||||||
//Map _instanceConfig;
|
//Map _instanceConfig;
|
||||||
String _webSocketApiEndpoint;
|
String _webSocketApiEndpoint;
|
||||||
String _password;
|
String _password;
|
||||||
String _authType;
|
|
||||||
//int _uiViewsCount = 0;
|
//int _uiViewsCount = 0;
|
||||||
String _instanceHost;
|
String _instanceHost;
|
||||||
StreamSubscription _stateSubscription;
|
StreamSubscription _stateSubscription;
|
||||||
StreamSubscription _settingsSubscription;
|
StreamSubscription _settingsSubscription;
|
||||||
StreamSubscription _serviceCallSubscription;
|
StreamSubscription _serviceCallSubscription;
|
||||||
StreamSubscription _showEntityPageSubscription;
|
StreamSubscription _showEntityPageSubscription;
|
||||||
StreamSubscription _refreshDataSubscription;
|
|
||||||
StreamSubscription _showErrorSubscription;
|
StreamSubscription _showErrorSubscription;
|
||||||
int _isLoading = 1;
|
|
||||||
bool _settingsLoaded = false;
|
bool _settingsLoaded = false;
|
||||||
bool _accountMenuExpanded = false;
|
bool _accountMenuExpanded = false;
|
||||||
bool _useLovelaceUI;
|
bool _useLovelaceUI;
|
||||||
|
int _previousViewCount;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -158,10 +175,11 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
_settingsLoaded = false;
|
_settingsLoaded = false;
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
|
||||||
|
Logger.d("<!!!> Creating new HomeAssistant instance");
|
||||||
_homeAssistant = HomeAssistant();
|
_homeAssistant = HomeAssistant();
|
||||||
|
|
||||||
_settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) {
|
_settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) {
|
||||||
TheLogger.debug("Settings change event: reconnect=${event.reconnect}");
|
Logger.d("Settings change event: reconnect=${event.reconnect}");
|
||||||
if (event.reconnect) {
|
if (event.reconnect) {
|
||||||
_homeAssistant.disconnect().then((_){
|
_homeAssistant.disconnect().then((_){
|
||||||
_initialLoad();
|
_initialLoad();
|
||||||
@ -176,16 +194,13 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
_subscribe();
|
_subscribe();
|
||||||
_refreshData();
|
_refreshData();
|
||||||
}, onError: (_) {
|
}, onError: (_) {
|
||||||
setState(() {
|
_showErrorBottomBar(message: _, errorCode: 5);
|
||||||
_isLoading = 2;
|
|
||||||
});
|
|
||||||
_showErrorSnackBar(message: _, errorCode: 5);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
TheLogger.debug("$state");
|
Logger.d("$state");
|
||||||
if (state == AppLifecycleState.resumed && _settingsLoaded) {
|
if (state == AppLifecycleState.resumed && _settingsLoaded) {
|
||||||
_refreshData();
|
_refreshData();
|
||||||
}
|
}
|
||||||
@ -199,8 +214,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
_webSocketApiEndpoint = "${prefs.getString('hassio-protocol')}://$domain:$port/api/websocket";
|
_webSocketApiEndpoint = "${prefs.getString('hassio-protocol')}://$domain:$port/api/websocket";
|
||||||
homeAssistantWebHost = "${prefs.getString('hassio-res-protocol')}://$domain:$port";
|
homeAssistantWebHost = "${prefs.getString('hassio-res-protocol')}://$domain:$port";
|
||||||
_password = prefs.getString('hassio-password');
|
_password = prefs.getString('hassio-password');
|
||||||
_authType = prefs.getString('hassio-auth-type');
|
_useLovelaceUI = prefs.getBool('use-lovelace') ?? true;
|
||||||
_useLovelaceUI = prefs.getBool('use-lovelace') ?? false;
|
|
||||||
if ((domain == null) || (port == null) || (_password == null) ||
|
if ((domain == null) || (port == null) || (_password == null) ||
|
||||||
(domain.length == 0) || (port.length == 0) || (_password.length == 0)) {
|
(domain.length == 0) || (port.length == 0) || (_password.length == 0)) {
|
||||||
throw("Check connection settings");
|
throw("Check connection settings");
|
||||||
@ -212,7 +226,12 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
_subscribe() {
|
_subscribe() {
|
||||||
if (_stateSubscription == null) {
|
if (_stateSubscription == null) {
|
||||||
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
|
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
|
||||||
setState(() {});
|
if (event.needToRebuildUI) {
|
||||||
|
Logger.d("New entity. Need to rebuild UI");
|
||||||
|
_refreshData();
|
||||||
|
} else {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (_serviceCallSubscription == null) {
|
if (_serviceCallSubscription == null) {
|
||||||
@ -226,33 +245,29 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
if (_showEntityPageSubscription == null) {
|
if (_showEntityPageSubscription == null) {
|
||||||
_showEntityPageSubscription =
|
_showEntityPageSubscription =
|
||||||
eventBus.on<ShowEntityPageEvent>().listen((event) {
|
eventBus.on<ShowEntityPageEvent>().listen((event) {
|
||||||
_showEntityPage(event.entity);
|
_showEntityPage(event.entity.entityId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_refreshDataSubscription == null) {
|
|
||||||
_refreshDataSubscription = eventBus.on<RefreshDataEvent>().listen((event){
|
|
||||||
_refreshData();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_showErrorSubscription == null) {
|
if (_showErrorSubscription == null) {
|
||||||
_showErrorSubscription = eventBus.on<ShowErrorEvent>().listen((event){
|
_showErrorSubscription = eventBus.on<ShowErrorEvent>().listen((event){
|
||||||
_showErrorSnackBar(message: event.text, errorCode: event.errorCode);
|
_showErrorBottomBar(message: event.text, errorCode: event.errorCode);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_refreshData() async {
|
_refreshData() async {
|
||||||
_homeAssistant.updateSettings(_webSocketApiEndpoint, _password, _authType, _useLovelaceUI);
|
_homeAssistant.updateSettings(_webSocketApiEndpoint, _password, _useLovelaceUI);
|
||||||
setState(() {
|
_hideBottomBar();
|
||||||
_hideErrorSnackBar();
|
_showInfoBottomBar(progress: true,);
|
||||||
_isLoading = 1;
|
|
||||||
});
|
|
||||||
await _homeAssistant.fetch().then((result) {
|
await _homeAssistant.fetch().then((result) {
|
||||||
setState(() {
|
_hideBottomBar();
|
||||||
_isLoading = 0;
|
int currentViewCount = _homeAssistant.ui?.views?.length ?? 0;
|
||||||
});
|
if (_previousViewCount != currentViewCount) {
|
||||||
|
Logger.d("Views count changed ($_previousViewCount->$currentViewCount). Creating new tabs controller.");
|
||||||
|
_viewsTabController = TabController(vsync: this, length: currentViewCount);
|
||||||
|
_previousViewCount = currentViewCount;
|
||||||
|
}
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
_setErrorState(e);
|
_setErrorState(e);
|
||||||
});
|
});
|
||||||
@ -260,18 +275,15 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_setErrorState(e) {
|
_setErrorState(e) {
|
||||||
setState(() {
|
|
||||||
_isLoading = 2;
|
|
||||||
});
|
|
||||||
if (e is Error) {
|
if (e is Error) {
|
||||||
TheLogger.error(e.toString());
|
Logger.e(e.toString());
|
||||||
TheLogger.error("${e.stackTrace}");
|
Logger.e("${e.stackTrace}");
|
||||||
_showErrorSnackBar(
|
_showErrorBottomBar(
|
||||||
message: "There was some error",
|
message: "There was some error",
|
||||||
errorCode: 13
|
errorCode: 13
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_showErrorSnackBar(
|
_showErrorBottomBar(
|
||||||
message: e != null ? e["errorMessage"] ?? "$e" : "Unknown error",
|
message: e != null ? e["errorMessage"] ?? "$e" : "Unknown error",
|
||||||
errorCode: e["errorCode"] != null ? e["errorCode"] : 99
|
errorCode: e["errorCode"] != null ? e["errorCode"] : 99
|
||||||
);
|
);
|
||||||
@ -279,14 +291,18 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _callService(String domain, String service, String entityId, Map<String, dynamic> additionalParams) {
|
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));
|
_homeAssistant.callService(domain, service, entityId, additionalParams).catchError((e) => _setErrorState(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showEntityPage(Entity entity) {
|
void _showEntityPage(String entityId) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => EntityViewPage(entity: entity, homeAssistant: _homeAssistant),
|
builder: (context) => EntityViewPage(entityId: entityId, homeAssistant: _homeAssistant),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -303,31 +319,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
return result;
|
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() {
|
Drawer _buildAppDrawer() {
|
||||||
List<Widget> menuItems = [];
|
List<Widget> menuItems = [];
|
||||||
menuItems.add(
|
menuItems.add(
|
||||||
@ -362,6 +353,27 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
Divider(),
|
Divider(),
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
|
if (_homeAssistant != null && _homeAssistant.panels.isNotEmpty) {
|
||||||
|
_homeAssistant.panels.forEach((Panel panel) {
|
||||||
|
if (!panel.isHidden) {
|
||||||
|
menuItems.add(
|
||||||
|
new ListTile(
|
||||||
|
leading: Icon(MaterialDesignIcons.getIconDataFromIconName(panel.icon)),
|
||||||
|
title: Text("${panel.title}"),
|
||||||
|
onTap: () => panel.handleOpen(context)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
menuItems.addAll([
|
||||||
|
new ListTile(
|
||||||
|
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:home-assistant")),
|
||||||
|
title: Text("Open Web UI"),
|
||||||
|
onTap: () => HAUtils.launchURL(homeAssistantWebHost),
|
||||||
|
),
|
||||||
|
Divider()
|
||||||
|
]);
|
||||||
|
}
|
||||||
menuItems.addAll([
|
menuItems.addAll([
|
||||||
new ListTile(
|
new ListTile(
|
||||||
leading: Icon(Icons.insert_drive_file),
|
leading: Icon(Icons.insert_drive_file),
|
||||||
@ -372,7 +384,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
new ListTile(
|
new ListTile(
|
||||||
leading: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:github-circle")),
|
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:github-circle")),
|
||||||
title: Text("Report an issue"),
|
title: Text("Report an issue"),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
@ -380,25 +392,64 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Divider(),
|
Divider(),
|
||||||
|
new ListTile(
|
||||||
|
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:discord")),
|
||||||
|
title: Text("Join Discord server"),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
HAUtils.launchURL("https://discord.gg/AUzEvwn");
|
||||||
|
},
|
||||||
|
),
|
||||||
new AboutListTile(
|
new AboutListTile(
|
||||||
aboutBoxChildren: <Widget>[
|
aboutBoxChildren: <Widget>[
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
HAUtils.launchURL("http://www.keyboardcrumbs.io/");
|
HAUtils.launchURL("http://ha-client.homemade.systems/");
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
"www.keyboardcrumbs.io",
|
"ha-client.homemade.systems",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
decoration: TextDecoration.underline
|
decoration: TextDecoration.underline
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 10.0,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
HAUtils.launchURLInCustomTab(context, "http://ha-client.homemade.systems/terms_and_conditions");
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
"Terms and Conditions",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.blue,
|
||||||
|
decoration: TextDecoration.underline
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 10.0,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
HAUtils.launchURLInCustomTab(context, "http://ha-client.homemade.systems/privacy_policy");
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
"Privacy Policy",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.blue,
|
||||||
|
decoration: TextDecoration.underline
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
applicationName: appName,
|
applicationName: appName,
|
||||||
applicationVersion: appVersion,
|
applicationVersion: appVersion
|
||||||
applicationLegalese: "Keyboard Crumbs",
|
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -409,21 +460,51 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _hideErrorSnackBar() {
|
void _hideBottomBar() {
|
||||||
_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||||
|
setState(() {
|
||||||
|
_showBottomBar = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showErrorSnackBar({Key key, @required String message, @required int errorCode}) {
|
Widget _bottomBarAction;
|
||||||
SnackBarAction action;
|
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) {
|
switch (errorCode) {
|
||||||
case 9:
|
case 9:
|
||||||
case 11:
|
case 11:
|
||||||
case 7:
|
case 7:
|
||||||
case 1: {
|
case 1: {
|
||||||
action = SnackBarAction(
|
_bottomBarAction = FlatButton(
|
||||||
label: "Retry",
|
child: Text("Retry", style: textStyle),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||||
_refreshData();
|
_refreshData();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -432,10 +513,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
case 5: {
|
case 5: {
|
||||||
message = "Check connection settings";
|
message = "Check connection settings";
|
||||||
action = SnackBarAction(
|
_bottomBarAction = FlatButton(
|
||||||
label: "Open",
|
child: Text("Open", style: textStyle),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||||
Navigator.pushNamed(context, '/connection-settings');
|
Navigator.pushNamed(context, '/connection-settings');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -443,10 +524,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 6: {
|
case 6: {
|
||||||
action = SnackBarAction(
|
_bottomBarAction = FlatButton(
|
||||||
label: "Settings",
|
child: Text("Settings", style: textStyle),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||||
Navigator.pushNamed(context, '/connection-settings');
|
Navigator.pushNamed(context, '/connection-settings');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -454,10 +535,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 10: {
|
case 10: {
|
||||||
action = SnackBarAction(
|
_bottomBarAction = FlatButton(
|
||||||
label: "Refresh",
|
child: Text("Refresh", style: textStyle),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||||
_refreshData();
|
_refreshData();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -465,10 +546,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 8: {
|
case 8: {
|
||||||
action = SnackBarAction(
|
_bottomBarAction = FlatButton(
|
||||||
label: "Reconnect",
|
child: Text("Reconnect", style: textStyle),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||||
_refreshData();
|
_refreshData();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -476,24 +557,29 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
action = SnackBarAction(
|
_bottomBarAction = FlatButton(
|
||||||
label: "Reload",
|
child: Text("Reload", style: textStyle),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||||
_refreshData();
|
_refreshData();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_scaffoldKey.currentState.hideCurrentSnackBar();
|
setState(() {
|
||||||
|
_bottomBarProgress = false;
|
||||||
|
_bottomBarText = "$message (code: $errorCode)";
|
||||||
|
_showBottomBar = true;
|
||||||
|
});
|
||||||
|
/*_scaffoldKey.currentState.hideCurrentSnackBar();
|
||||||
_scaffoldKey.currentState.showSnackBar(
|
_scaffoldKey.currentState.showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text("$message (code: $errorCode)"),
|
content: Text("$message (code: $errorCode)"),
|
||||||
action: action,
|
action: action,
|
||||||
duration: Duration(hours: 1),
|
duration: Duration(hours: 1),
|
||||||
)
|
)
|
||||||
);
|
);*/
|
||||||
}
|
}
|
||||||
|
|
||||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||||
@ -506,7 +592,27 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
floating: true,
|
floating: true,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
primary: true,
|
primary: true,
|
||||||
title: _buildAppTitle(),
|
title: Text(_homeAssistant != null ? _homeAssistant.locationName : ""),
|
||||||
|
actions: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||||
|
"mdi:dots-vertical"), color: Colors.white,),
|
||||||
|
onPressed: () {
|
||||||
|
showMenu(
|
||||||
|
position: RelativeRect.fromLTRB(MediaQuery.of(context).size.width, 70.0, 0.0, 0.0),
|
||||||
|
context: context,
|
||||||
|
items: [PopupMenuItem<String>(
|
||||||
|
child: new Text("Reload"),
|
||||||
|
value: "reload",
|
||||||
|
)]
|
||||||
|
).then((String val) {
|
||||||
|
if (val == "reload") {
|
||||||
|
_refreshData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
],
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: Icon(Icons.menu),
|
icon: Icon(Icons.menu),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -517,6 +623,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
bottom: empty ? null : TabBar(
|
bottom: empty ? null : TabBar(
|
||||||
|
controller: _viewsTabController,
|
||||||
tabs: buildUIViewTabs(),
|
tabs: buildUIViewTabs(),
|
||||||
isScrollable: true,
|
isScrollable: true,
|
||||||
),
|
),
|
||||||
@ -530,26 +637,77 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
MaterialDesignIcons.createIconDataFromIconName("mdi:home-assistant"),
|
MaterialDesignIcons.getIconDataFromIconName("mdi:home-assistant"),
|
||||||
size: 100.0,
|
size: 100.0,
|
||||||
color: _isLoading == 2 ? Colors.redAccent : Colors.blue,
|
color: Colors.blue,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
:
|
:
|
||||||
_homeAssistant.buildViews(context, _useLovelaceUI),
|
_homeAssistant.buildViews(context, _useLovelaceUI, _viewsTabController),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TabController _viewsTabController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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.
|
// This method is rerun every time setState is called.
|
||||||
if (_homeAssistant.ui == null || _homeAssistant.ui.views == null) {
|
if (_homeAssistant.ui == null || _homeAssistant.ui.views == null) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
key: _scaffoldKey,
|
key: _scaffoldKey,
|
||||||
primary: false,
|
primary: false,
|
||||||
drawer: _buildAppDrawer(),
|
drawer: _buildAppDrawer(),
|
||||||
|
bottomNavigationBar: bottomBar,
|
||||||
body: _buildScaffoldBody(true)
|
body: _buildScaffoldBody(true)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -557,9 +715,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
key: _scaffoldKey,
|
key: _scaffoldKey,
|
||||||
drawer: _buildAppDrawer(),
|
drawer: _buildAppDrawer(),
|
||||||
primary: false,
|
primary: false,
|
||||||
body: DefaultTabController(
|
bottomNavigationBar: bottomBar,
|
||||||
length: _homeAssistant.ui?.views?.length ?? 0,
|
body: HomeAssistantModel(
|
||||||
child: _buildScaffoldBody(false),
|
child: _buildScaffoldBody(false),
|
||||||
|
homeAssistant: _homeAssistant
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -568,11 +727,11 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
_viewsTabController.dispose();
|
||||||
if (_stateSubscription != null) _stateSubscription.cancel();
|
if (_stateSubscription != null) _stateSubscription.cancel();
|
||||||
if (_settingsSubscription != null) _settingsSubscription.cancel();
|
if (_settingsSubscription != null) _settingsSubscription.cancel();
|
||||||
if (_serviceCallSubscription != null) _serviceCallSubscription.cancel();
|
if (_serviceCallSubscription != null) _serviceCallSubscription.cancel();
|
||||||
if (_showEntityPageSubscription != null) _showEntityPageSubscription.cancel();
|
if (_showEntityPageSubscription != null) _showEntityPageSubscription.cancel();
|
||||||
if (_refreshDataSubscription != null) _refreshDataSubscription.cancel();
|
|
||||||
if (_showErrorSubscription != null) _showErrorSubscription.cancel();
|
if (_showErrorSubscription != null) _showErrorSubscription.cancel();
|
||||||
_homeAssistant.disconnect();
|
_homeAssistant.disconnect();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
40
lib/panel.page.dart
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
part of 'main.dart';
|
||||||
|
|
||||||
|
class PanelPage extends StatefulWidget {
|
||||||
|
PanelPage({Key key, this.title, this.panel}) : super(key: key);
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final Panel panel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_PanelPageState createState() => new _PanelPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PanelPageState extends State<PanelPage> {
|
||||||
|
|
||||||
|
List<ConfigurationItem> _items;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
|
return new Scaffold(
|
||||||
|
appBar: new AppBar(
|
||||||
|
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
|
||||||
|
Navigator.pop(context);
|
||||||
|
}),
|
||||||
|
title: new Text(widget.title),
|
||||||
|
),
|
||||||
|
body: widget.panel.getWidget(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -18,10 +18,8 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
|||||||
String _newHassioPassword = "";
|
String _newHassioPassword = "";
|
||||||
String _socketProtocol = "wss";
|
String _socketProtocol = "wss";
|
||||||
String _newSocketProtocol = "wss";
|
String _newSocketProtocol = "wss";
|
||||||
String _authType = "access_token";
|
bool _useLovelace = true;
|
||||||
String _newAuthType = "access_token";
|
bool _newUseLovelace = true;
|
||||||
bool _useLovelace = false;
|
|
||||||
bool _newUseLovelace = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -37,11 +35,10 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
|||||||
_hassioPort = _newHassioPort = prefs.getString("hassio-port") ?? "";
|
_hassioPort = _newHassioPort = prefs.getString("hassio-port") ?? "";
|
||||||
_hassioPassword = _newHassioPassword = prefs.getString("hassio-password") ?? "";
|
_hassioPassword = _newHassioPassword = prefs.getString("hassio-password") ?? "";
|
||||||
_socketProtocol = _newSocketProtocol = prefs.getString("hassio-protocol") ?? 'wss';
|
_socketProtocol = _newSocketProtocol = prefs.getString("hassio-protocol") ?? 'wss';
|
||||||
_authType = _newAuthType = prefs.getString("hassio-auth-type") ?? 'access_token';
|
|
||||||
try {
|
try {
|
||||||
_useLovelace = _newUseLovelace = prefs.getBool("use-lovelace") ?? false;
|
_useLovelace = _newUseLovelace = prefs.getBool("use-lovelace") ?? true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_useLovelace = _newUseLovelace = false;
|
_useLovelace = _newUseLovelace = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -51,7 +48,6 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
|||||||
(_newHassioPort != _hassioPort) ||
|
(_newHassioPort != _hassioPort) ||
|
||||||
(_newHassioDomain != _hassioDomain) ||
|
(_newHassioDomain != _hassioDomain) ||
|
||||||
(_newSocketProtocol != _socketProtocol) ||
|
(_newSocketProtocol != _socketProtocol) ||
|
||||||
(_newAuthType != _authType) ||
|
|
||||||
(_newUseLovelace != _useLovelace));
|
(_newUseLovelace != _useLovelace));
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -66,7 +62,6 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
|||||||
prefs.setString("hassio-password", _newHassioPassword);
|
prefs.setString("hassio-password", _newHassioPassword);
|
||||||
prefs.setString("hassio-protocol", _newSocketProtocol);
|
prefs.setString("hassio-protocol", _newSocketProtocol);
|
||||||
prefs.setString("hassio-res-protocol", _newSocketProtocol == "wss" ? "https" : "http");
|
prefs.setString("hassio-res-protocol", _newSocketProtocol == "wss" ? "https" : "http");
|
||||||
prefs.setString("hassio-auth-type", _newAuthType);
|
|
||||||
prefs.setBool("use-lovelace", _newUseLovelace);
|
prefs.setBool("use-lovelace", _newUseLovelace);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,13 +78,13 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
|||||||
icon: Icon(Icons.check),
|
icon: Icon(Icons.check),
|
||||||
onPressed: (){
|
onPressed: (){
|
||||||
if (_checkConfigChanged()) {
|
if (_checkConfigChanged()) {
|
||||||
TheLogger.debug("Settings changed. Saving...");
|
Logger.d("Settings changed. Saving...");
|
||||||
_saveSettings().then((r) {
|
_saveSettings().then((r) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
eventBus.fire(SettingsChangedEvent(true));
|
eventBus.fire(SettingsChangedEvent(true));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
TheLogger.debug("Settings was not changed");
|
Logger.d("Settings was not changed");
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,6 +92,7 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
@ -149,22 +145,13 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
|||||||
_newHassioPort = value;
|
_newHassioPort = value;
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
new Row(
|
new Text(
|
||||||
children: [
|
"Try ports 80 and 443 if default is not working and you don't know why.",
|
||||||
Text("Login with access token (HA >= 0.78.0)"),
|
style: TextStyle(color: Colors.grey),
|
||||||
Switch(
|
|
||||||
value: (_newAuthType == "access_token"),
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
_newAuthType = value ? "access_token" : "api_password";
|
|
||||||
});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
new TextField(
|
new TextField(
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: _newAuthType == "access_token" ? "Access token" : "API password"
|
labelText: "Access token"
|
||||||
),
|
),
|
||||||
controller: new TextEditingController.fromValue(
|
controller: new TextEditingController.fromValue(
|
||||||
new TextEditingValue(
|
new TextEditingValue(
|
||||||
|
@ -2,76 +2,49 @@ part of '../main.dart';
|
|||||||
|
|
||||||
class HACard {
|
class HACard {
|
||||||
List<EntityWrapper> entities = [];
|
List<EntityWrapper> entities = [];
|
||||||
EntityWrapper linkedEntity;
|
List<HACard> childCards = [];
|
||||||
|
EntityWrapper linkedEntityWrapper;
|
||||||
String name;
|
String name;
|
||||||
String id;
|
String id;
|
||||||
String type;
|
String type;
|
||||||
bool showName;
|
bool showName;
|
||||||
bool showState;
|
bool showState;
|
||||||
|
bool showEmpty;
|
||||||
int columnsCount;
|
int columnsCount;
|
||||||
|
List stateFilter;
|
||||||
|
List states;
|
||||||
|
String content;
|
||||||
|
|
||||||
HACard({
|
HACard({
|
||||||
this.name,
|
this.name,
|
||||||
this.id,
|
this.id,
|
||||||
this.linkedEntity,
|
this.linkedEntityWrapper,
|
||||||
this.columnsCount: 4,
|
this.columnsCount: 4,
|
||||||
this.showName: true,
|
this.showName: true,
|
||||||
this.showState: true,
|
this.showState: true,
|
||||||
|
this.stateFilter: const [],
|
||||||
|
this.showEmpty: true,
|
||||||
|
this.content,
|
||||||
|
this.states,
|
||||||
@required this.type
|
@required this.type
|
||||||
});
|
});
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
List<EntityWrapper> getEntitiesToShow() {
|
||||||
switch (type) {
|
return entities.where((entityWrapper) {
|
||||||
|
if (entityWrapper.entity.isHidden) {
|
||||||
case "entities": {
|
return false;
|
||||||
return EntitiesCardWidget(
|
|
||||||
card: this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "glance": {
|
|
||||||
return GlanceCardWidget(
|
|
||||||
card: this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "media-control": {
|
|
||||||
return MediaControlCardWidget(
|
|
||||||
card: this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "weather-forecast":
|
|
||||||
case "thermostat":
|
|
||||||
case "sensor":
|
|
||||||
case "plant-status":
|
|
||||||
case "picture-entity":
|
|
||||||
case "picture-elements":
|
|
||||||
case "picture":
|
|
||||||
case "map":
|
|
||||||
case "iframe":
|
|
||||||
case "gauge":
|
|
||||||
case "entity-button":
|
|
||||||
case "conditional":
|
|
||||||
case "alarm-panel": {
|
|
||||||
return UnsupportedCardWidget(
|
|
||||||
card: this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
if ((linkedEntity == null) && (entities.isNotEmpty)) {
|
|
||||||
return EntitiesCardWidget(
|
|
||||||
card: this,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return UnsupportedCardWidget(
|
|
||||||
card: this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
if (stateFilter.isNotEmpty) {
|
||||||
|
return stateFilter.contains(entityWrapper.entity.state);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CardWidget(
|
||||||
|
card: this,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
57
lib/ui_class/panel_class.dart
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class Panel {
|
||||||
|
|
||||||
|
static const iconsByComponent = {
|
||||||
|
"config": "mdi:settings",
|
||||||
|
"history": "mdi:poll-box",
|
||||||
|
"map": "mdi:tooltip-account",
|
||||||
|
"logbook": "mdi:format-list-bulleted-type",
|
||||||
|
"custom": "mdi:home-assistant"
|
||||||
|
};
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final String type;
|
||||||
|
final String title;
|
||||||
|
final String urlPath;
|
||||||
|
final Map config;
|
||||||
|
String icon;
|
||||||
|
bool isHidden = true;
|
||||||
|
|
||||||
|
Panel({this.id, this.type, this.title, this.urlPath, this.icon, this.config}) {
|
||||||
|
if (icon == null || !icon.startsWith("mdi:")) {
|
||||||
|
icon = Panel.iconsByComponent[type];
|
||||||
|
}
|
||||||
|
isHidden = (type != "iframe" && type != "config");
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleOpen(BuildContext context) {
|
||||||
|
if (type == "iframe") {
|
||||||
|
Logger.d("Launching custom tab with ${config["url"]}");
|
||||||
|
HAUtils.launchURLInCustomTab(context, config["url"]);
|
||||||
|
} else if (type == "config") {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => PanelPage(title: "$title", panel: this),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
String url = "$homeAssistantWebHost/$urlPath";
|
||||||
|
Logger.d("Launching custom tab with $url");
|
||||||
|
HAUtils.launchURLInCustomTab(context, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getWidget() {
|
||||||
|
switch (type) {
|
||||||
|
case "config": {
|
||||||
|
return ConfigPanelWidget();
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return Text("Unsupported panel component: $type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,13 +1,14 @@
|
|||||||
part of '../main.dart';
|
part of '../main.dart';
|
||||||
|
|
||||||
class Sizes {
|
class Sizes {
|
||||||
static const rightWidgetPadding = 14.0;
|
static const rightWidgetPadding = 16.0;
|
||||||
static const leftWidgetPadding = 8.0;
|
static const leftWidgetPadding = 16.0;
|
||||||
|
static const buttonPadding = 4.0;
|
||||||
static const extendedWidgetHeight = 50.0;
|
static const extendedWidgetHeight = 50.0;
|
||||||
static const iconSize = 28.0;
|
static const iconSize = 28.0;
|
||||||
static const largeIconSize = 34.0;
|
static const largeIconSize = 46.0;
|
||||||
static const stateFontSize = 16.0;
|
static const stateFontSize = 15.0;
|
||||||
static const nameFontSize = 16.0;
|
static const nameFontSize = 15.0;
|
||||||
static const smallFontSize = 14.0;
|
static const smallFontSize = 14.0;
|
||||||
static const largeFontSize = 24.0;
|
static const largeFontSize = 24.0;
|
||||||
static const inputWidth = 160.0;
|
static const inputWidth = 160.0;
|
||||||
|
@ -8,8 +8,9 @@ class HomeAssistantUI {
|
|||||||
views = [];
|
views = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, TabController tabController) {
|
||||||
return TabBarView(
|
return TabBarView(
|
||||||
|
controller: tabController,
|
||||||
children: _buildViews(context)
|
children: _buildViews(context)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,8 @@ class HAView {
|
|||||||
HACard card = HACard(
|
HACard card = HACard(
|
||||||
name: e.displayName,
|
name: e.displayName,
|
||||||
id: e.entityId,
|
id: e.entityId,
|
||||||
linkedEntity: EntityWrapper(entity: e),
|
linkedEntityWrapper: EntityWrapper(entity: e),
|
||||||
type: "media-control"
|
type: CardType.mediaControl
|
||||||
);
|
);
|
||||||
cards.add(card);
|
cards.add(card);
|
||||||
});
|
});
|
||||||
@ -40,7 +40,7 @@ class HAView {
|
|||||||
HACard card = HACard(
|
HACard card = HACard(
|
||||||
id: groupIdToAdd,
|
id: groupIdToAdd,
|
||||||
name: entity.domain,
|
name: entity.domain,
|
||||||
type: "entities"
|
type: CardType.entities
|
||||||
);
|
);
|
||||||
card.entities.add(EntityWrapper(entity: entity));
|
card.entities.add(EntityWrapper(entity: entity));
|
||||||
autoGeneratedCards.add(card);
|
autoGeneratedCards.add(card);
|
||||||
@ -51,16 +51,16 @@ class HAView {
|
|||||||
HACard card = HACard(
|
HACard card = HACard(
|
||||||
name: entity.displayName,
|
name: entity.displayName,
|
||||||
id: entity.entityId,
|
id: entity.entityId,
|
||||||
linkedEntity: EntityWrapper(entity: entity),
|
linkedEntityWrapper: EntityWrapper(entity: entity),
|
||||||
type: "entities"
|
type: CardType.entities
|
||||||
);
|
);
|
||||||
card.entities.addAll(entity.childEntities.where((entity) {return entity.domain != "media_player";}).map((e) {return EntityWrapper(entity: e);}));
|
card.entities.addAll(entity.childEntities.where((entity) {return entity.domain != "media_player";}).map((e) {return EntityWrapper(entity: e);}));
|
||||||
entity.childEntities.where((entity) {return entity.domain == "media_player";}).forEach((entity){
|
entity.childEntities.where((entity) {return entity.domain == "media_player";}).forEach((entity){
|
||||||
HACard mediaCard = HACard(
|
HACard mediaCard = HACard(
|
||||||
name: entity.displayName,
|
name: entity.displayName,
|
||||||
id: entity.entityId,
|
id: entity.entityId,
|
||||||
linkedEntity: EntityWrapper(entity: entity),
|
linkedEntityWrapper: EntityWrapper(entity: entity),
|
||||||
type: "media-control"
|
type: CardType.mediaControl
|
||||||
);
|
);
|
||||||
cards.add(mediaCard);
|
cards.add(mediaCard);
|
||||||
});
|
});
|
||||||
@ -77,7 +77,7 @@ class HAView {
|
|||||||
Tab(
|
Tab(
|
||||||
icon:
|
icon:
|
||||||
Icon(
|
Icon(
|
||||||
MaterialDesignIcons.createIconDataFromIconName(
|
MaterialDesignIcons.getIconDataFromIconName(
|
||||||
iconName ?? "mdi:home-assistant"),
|
iconName ?? "mdi:home-assistant"),
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
)
|
)
|
||||||
@ -85,21 +85,21 @@ class HAView {
|
|||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
Tab(
|
Tab(
|
||||||
text: name.toUpperCase(),
|
text: "${name?.toUpperCase()}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (linkedEntity.icon != null && linkedEntity.icon.length > 0) {
|
if (linkedEntity.icon != null && linkedEntity.icon.length > 0) {
|
||||||
return Tab(
|
return Tab(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
MaterialDesignIcons.createIconDataFromIconName(
|
MaterialDesignIcons.getIconDataFromIconName(
|
||||||
linkedEntity.icon),
|
linkedEntity.icon),
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Tab(
|
return Tab(
|
||||||
text: linkedEntity.displayName.toUpperCase(),
|
text: "${linkedEntity?.displayName?.toUpperCase()}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,18 +3,22 @@ part of '../main.dart';
|
|||||||
class CardHeaderWidget extends StatelessWidget {
|
class CardHeaderWidget extends StatelessWidget {
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
|
final Widget trailing;
|
||||||
|
final Widget subtitle;
|
||||||
|
|
||||||
const CardHeaderWidget({Key key, this.name}) : super(key: key);
|
const CardHeaderWidget({Key key, this.name, this.trailing, this.subtitle}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var result;
|
var result;
|
||||||
if ((name != null) && (name.trim().length > 0)) {
|
if ((name != null) && (name.trim().length > 0)) {
|
||||||
result = new ListTile(
|
result = new ListTile(
|
||||||
|
trailing: trailing,
|
||||||
|
subtitle: subtitle,
|
||||||
title: Text("$name",
|
title: Text("$name",
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: 25.0)),
|
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: Sizes.largeFontSize)),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
result = new Container(width: 0.0, height: 0.0);
|
result = new Container(width: 0.0, height: 0.0);
|
||||||
|
289
lib/ui_widgets/card_widget.dart
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class CardWidget extends StatelessWidget {
|
||||||
|
|
||||||
|
final HACard card;
|
||||||
|
|
||||||
|
const CardWidget({
|
||||||
|
Key key,
|
||||||
|
this.card
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (card.linkedEntityWrapper!= null) {
|
||||||
|
if (card.linkedEntityWrapper.entity.isHidden) {
|
||||||
|
return Container(width: 0.0, height: 0.0,);
|
||||||
|
}
|
||||||
|
if (card.linkedEntityWrapper.entity.statelessType == StatelessEntityType.MISSED) {
|
||||||
|
return EntityModel(
|
||||||
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
|
child: MissedEntityWidget(),
|
||||||
|
handleTap: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (card.type) {
|
||||||
|
|
||||||
|
case CardType.entities: {
|
||||||
|
return _buildEntitiesCard(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
case CardType.glance: {
|
||||||
|
return _buildGlanceCard(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
case CardType.mediaControl: {
|
||||||
|
return _buildMediaControlsCard(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
case CardType.entityButton: {
|
||||||
|
return _buildEntityButtonCard(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
case CardType.markdown: {
|
||||||
|
return _buildMarkdownCard(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
case CardType.alarmPanel: {
|
||||||
|
return _buildAlarmPanelCard(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
case CardType.horizontalStack: {
|
||||||
|
if (card.childCards.isNotEmpty) {
|
||||||
|
List<Widget> children = [];
|
||||||
|
card.childCards.forEach((card) {
|
||||||
|
if (card.getEntitiesToShow().isNotEmpty || card.showEmpty) {
|
||||||
|
children.add(
|
||||||
|
Flexible(
|
||||||
|
fit: FlexFit.tight,
|
||||||
|
child: card.build(context),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container(height: 0.0, width: 0.0,);
|
||||||
|
}
|
||||||
|
|
||||||
|
case CardType.verticalStack: {
|
||||||
|
if (card.childCards.isNotEmpty) {
|
||||||
|
List<Widget> children = [];
|
||||||
|
card.childCards.forEach((card) {
|
||||||
|
children.add(
|
||||||
|
card.build(context)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container(height: 0.0, width: 0.0,);
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
if ((card.linkedEntityWrapper == null) && (card.entities.isNotEmpty)) {
|
||||||
|
return _buildEntitiesCard(context);
|
||||||
|
} else {
|
||||||
|
return _buildUnsupportedCard(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEntitiesCard(BuildContext context) {
|
||||||
|
List<EntityWrapper> entitiesToShow = card.getEntitiesToShow();
|
||||||
|
if (entitiesToShow.isEmpty && !card.showEmpty) {
|
||||||
|
return Container(height: 0.0, width: 0.0,);
|
||||||
|
}
|
||||||
|
List<Widget> body = [];
|
||||||
|
body.add(CardHeaderWidget(name: card.name));
|
||||||
|
entitiesToShow.forEach((EntityWrapper entity) {
|
||||||
|
if (!entity.entity.isHidden) {
|
||||||
|
body.add(
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(10.0, 4.0, 0.0, 4.0),
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: entity,
|
||||||
|
handleTap: true,
|
||||||
|
child: entity.entity.buildDefaultWidget(context)
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Card(
|
||||||
|
child: new Column(mainAxisSize: MainAxisSize.min, children: body)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMarkdownCard(BuildContext context) {
|
||||||
|
if (card.content == null) {
|
||||||
|
return Container(height: 0.0, width: 0.0,);
|
||||||
|
}
|
||||||
|
List<Widget> body = [];
|
||||||
|
body.add(CardHeaderWidget(name: card.name));
|
||||||
|
body.add(MarkdownBody(data: card.content));
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
|
||||||
|
child: new Column(mainAxisSize: MainAxisSize.min, children: body),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAlarmPanelCard(BuildContext context) {
|
||||||
|
List<Widget> body = [];
|
||||||
|
body.add(CardHeaderWidget(
|
||||||
|
name: card.name ?? "",
|
||||||
|
subtitle: Text("${card.linkedEntityWrapper.entity.displayState}",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
EntityIcon(
|
||||||
|
size: 50.0,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 26.0,
|
||||||
|
child: IconButton(
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||||
|
"mdi:dots-vertical")),
|
||||||
|
onPressed: () => eventBus.fire(new ShowEntityPageEvent(card.linkedEntityWrapper.entity))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
));
|
||||||
|
body.add(
|
||||||
|
AlarmControlPanelControlsWidget(
|
||||||
|
extended: true,
|
||||||
|
states: card.states,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return Card(
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
|
handleTap: null,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: body
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildGlanceCard(BuildContext context) {
|
||||||
|
List<EntityWrapper> entitiesToShow = card.getEntitiesToShow();
|
||||||
|
if (entitiesToShow.isEmpty && !card.showEmpty) {
|
||||||
|
return Container(height: 0.0, width: 0.0,);
|
||||||
|
}
|
||||||
|
List<Widget> rows = [];
|
||||||
|
rows.add(CardHeaderWidget(name: card.name));
|
||||||
|
|
||||||
|
List<Widget> result = [];
|
||||||
|
int columnsCount = entitiesToShow.length >= card.columnsCount ? card.columnsCount : entitiesToShow.length;
|
||||||
|
|
||||||
|
entitiesToShow.forEach((EntityWrapper entity) {
|
||||||
|
result.add(
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: 1/columnsCount,
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: entity,
|
||||||
|
child: GlanceEntityContainer(
|
||||||
|
showName: card.showName,
|
||||||
|
showState: card.showState,
|
||||||
|
),
|
||||||
|
handleTap: true
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
rows.add(
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, 2*Sizes.rowPadding),
|
||||||
|
child: Wrap(
|
||||||
|
//alignment: WrapAlignment.spaceAround,
|
||||||
|
runSpacing: Sizes.rowPadding*2,
|
||||||
|
children: result,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
child: new Column(mainAxisSize: MainAxisSize.min, children: rows)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMediaControlsCard(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
|
handleTap: null,
|
||||||
|
child: MediaPlayerWidget()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEntityButtonCard(BuildContext context) {
|
||||||
|
card.linkedEntityWrapper.displayName = card.name?.toUpperCase() ??
|
||||||
|
card.linkedEntityWrapper.displayName?.toUpperCase();
|
||||||
|
return Card(
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
|
child: ButtonEntityContainer(),
|
||||||
|
handleTap: true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildUnsupportedCard(BuildContext context) {
|
||||||
|
List<Widget> body = [];
|
||||||
|
body.add(CardHeaderWidget(name: card.name ?? ""));
|
||||||
|
List<Widget> result = [];
|
||||||
|
if (card.linkedEntityWrapper != null) {
|
||||||
|
result.addAll(<Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
|
handleTap: true,
|
||||||
|
child: card.linkedEntityWrapper.entity.buildDefaultWidget(context)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
result.addAll(<Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
|
||||||
|
child: Text("'${card.type}' card is not supported yet"),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
body.addAll(result);
|
||||||
|
return Card(
|
||||||
|
child: new Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: body
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
95
lib/ui_widgets/config_panel_widget.dart
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class ConfigPanelWidget extends StatefulWidget {
|
||||||
|
ConfigPanelWidget({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ConfigPanelWidgetState createState() => new _ConfigPanelWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConfigurationItem {
|
||||||
|
ConfigurationItem({ this.isExpanded: false, this.header, this.body });
|
||||||
|
|
||||||
|
bool isExpanded;
|
||||||
|
final String header;
|
||||||
|
final Widget body;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConfigPanelWidgetState extends State<ConfigPanelWidget> {
|
||||||
|
|
||||||
|
List<ConfigurationItem> _items;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_items = <ConfigurationItem>[
|
||||||
|
ConfigurationItem(
|
||||||
|
header: 'General',
|
||||||
|
body: Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, 0.0, Sizes.rightWidgetPadding, Sizes.rowPadding),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Text("Server management", style: TextStyle(fontSize: Sizes.largeFontSize)),
|
||||||
|
Container(height: Sizes.rowPadding,),
|
||||||
|
Text("Control your Home Assistant server from HA Client."),
|
||||||
|
Divider(),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
FlatServiceButton(
|
||||||
|
text: "Restart",
|
||||||
|
serviceName: "restart",
|
||||||
|
serviceDomain: "homeassistant",
|
||||||
|
entityId: null,
|
||||||
|
),
|
||||||
|
FlatServiceButton(
|
||||||
|
text: "Stop",
|
||||||
|
serviceName: "stop",
|
||||||
|
serviceDomain: "homeassistant",
|
||||||
|
entityId: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
children: [
|
||||||
|
new ExpansionPanelList(
|
||||||
|
expansionCallback: (int index, bool isExpanded) {
|
||||||
|
setState(() {
|
||||||
|
_items[index].isExpanded = !_items[index].isExpanded;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
children: _items.map((ConfigurationItem item) {
|
||||||
|
return new ExpansionPanel(
|
||||||
|
headerBuilder: (BuildContext context, bool isExpanded) {
|
||||||
|
return CardHeaderWidget(
|
||||||
|
name: item.header,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isExpanded: item.isExpanded,
|
||||||
|
body: new Container(
|
||||||
|
child: item.body,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -1,43 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class EntitiesCardWidget extends StatelessWidget {
|
|
||||||
|
|
||||||
final HACard card;
|
|
||||||
|
|
||||||
const EntitiesCardWidget({
|
|
||||||
Key key,
|
|
||||||
this.card
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if ((card.linkedEntity!= null) && (card.linkedEntity.entity.isHidden)) {
|
|
||||||
return Container(width: 0.0, height: 0.0,);
|
|
||||||
}
|
|
||||||
List<Widget> body = [];
|
|
||||||
body.add(CardHeaderWidget(name: card.name));
|
|
||||||
body.addAll(_buildCardBody(context));
|
|
||||||
return Card(
|
|
||||||
child: new Column(mainAxisSize: MainAxisSize.min, children: body)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _buildCardBody(BuildContext context) {
|
|
||||||
List<Widget> result = [];
|
|
||||||
card.entities.forEach((EntityWrapper entity) {
|
|
||||||
if (!entity.entity.isHidden) {
|
|
||||||
result.add(
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
|
||||||
child: EntityModel(
|
|
||||||
entityWrapper: entity,
|
|
||||||
handleTap: true,
|
|
||||||
child: entity.entity.buildDefaultWidget(context)
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class GlanceCardWidget extends StatelessWidget {
|
|
||||||
|
|
||||||
final HACard card;
|
|
||||||
|
|
||||||
const GlanceCardWidget({
|
|
||||||
Key key,
|
|
||||||
this.card
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if ((card.linkedEntity!= null) && (card.linkedEntity.entity.isHidden)) {
|
|
||||||
return Container(width: 0.0, height: 0.0,);
|
|
||||||
}
|
|
||||||
List<Widget> rows = [];
|
|
||||||
rows.add(CardHeaderWidget(name: card.name));
|
|
||||||
rows.add(_buildRows(context));
|
|
||||||
return Card(
|
|
||||||
child: new Column(mainAxisSize: MainAxisSize.min, children: rows)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildRows(BuildContext context) {
|
|
||||||
List<Widget> result = [];
|
|
||||||
double width = MediaQuery.of(context).size.width - Sizes.leftWidgetPadding - (2*Sizes.rightWidgetPadding);
|
|
||||||
List<EntityWrapper> toShow = card.entities.where((entity) {return !entity.entity.isHidden;}).toList();
|
|
||||||
int columnsCount = toShow.length >= card.columnsCount ? card.columnsCount : toShow.length;
|
|
||||||
card.entities.forEach((EntityWrapper entity) {
|
|
||||||
if (!entity.entity.isHidden) {
|
|
||||||
result.add(
|
|
||||||
SizedBox(
|
|
||||||
width: width / columnsCount,
|
|
||||||
child: EntityModel(
|
|
||||||
entityWrapper: entity,
|
|
||||||
child: entity.entity.buildGlanceWidget(context, card.showName, card.showState),
|
|
||||||
handleTap: true
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, 2*Sizes.rowPadding),
|
|
||||||
child: Wrap(
|
|
||||||
alignment: WrapAlignment.spaceAround,
|
|
||||||
runSpacing: Sizes.rowPadding*2,
|
|
||||||
children: result,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class MediaControlCardWidget extends StatelessWidget {
|
|
||||||
|
|
||||||
final HACard card;
|
|
||||||
|
|
||||||
const MediaControlCardWidget({
|
|
||||||
Key key,
|
|
||||||
this.card
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if ((card.linkedEntity == null) || (card.linkedEntity.entity.isHidden)) {
|
|
||||||
return Container(width: 0.0, height: 0.0,);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
child: EntityModel(
|
|
||||||
entityWrapper: card.linkedEntity,
|
|
||||||
handleTap: null,
|
|
||||||
child: MediaPlayerWidget()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class UnsupportedCardWidget extends StatelessWidget {
|
|
||||||
|
|
||||||
final HACard card;
|
|
||||||
|
|
||||||
const UnsupportedCardWidget({
|
|
||||||
Key key,
|
|
||||||
this.card
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if ((card.linkedEntity!= null) && (card.linkedEntity.entity.isHidden)) {
|
|
||||||
return Container(width: 0.0, height: 0.0,);
|
|
||||||
}
|
|
||||||
List<Widget> body = [];
|
|
||||||
body.add(CardHeaderWidget(name: card.name ?? ""));
|
|
||||||
body.addAll(_buildCardBody(context));
|
|
||||||
return Card(
|
|
||||||
child: new Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: body
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _buildCardBody(BuildContext context) {
|
|
||||||
List<Widget> result = [];
|
|
||||||
if (card.linkedEntity != null) {
|
|
||||||
result.addAll(<Widget>[
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
|
||||||
child: EntityModel(
|
|
||||||
entityWrapper: card.linkedEntity,
|
|
||||||
handleTap: true,
|
|
||||||
child: card.linkedEntity.entity.buildDefaultWidget(context)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
result.addAll(<Widget>[
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
|
|
||||||
child: Text("'${card.type}' card is not supported yet"),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -17,29 +17,17 @@ class ViewWidget extends StatefulWidget {
|
|||||||
|
|
||||||
class ViewWidgetState extends State<ViewWidget> {
|
class ViewWidgetState extends State<ViewWidget> {
|
||||||
|
|
||||||
StreamSubscription _refreshDataSubscription;
|
|
||||||
Completer _refreshCompleter;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
|
|
||||||
if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) {
|
|
||||||
_refreshCompleter.complete();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return RefreshIndicator(
|
return ListView(
|
||||||
color: Colors.amber,
|
padding: EdgeInsets.all(0.0),
|
||||||
child: ListView(
|
//physics: const AlwaysScrollableScrollPhysics(),
|
||||||
padding: EdgeInsets.all(0.0),
|
children: _buildChildren(context),
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
|
||||||
children: _buildChildren(context),
|
|
||||||
),
|
|
||||||
onRefresh: () => _refreshData(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,12 +45,22 @@ class ViewWidgetState extends State<ViewWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Widget> cards = [];
|
||||||
widget.view.cards.forEach((HACard card){
|
widget.view.cards.forEach((HACard card){
|
||||||
result.add(
|
cards.add(
|
||||||
card.build(context)
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxWidth: 500),
|
||||||
|
child: card.build(context),
|
||||||
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
result.add(
|
||||||
|
Column (
|
||||||
|
children: cards,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,19 +74,8 @@ class ViewWidgetState extends State<ViewWidget> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _refreshData() {
|
|
||||||
if ((_refreshCompleter != null) && (!_refreshCompleter.isCompleted)) {
|
|
||||||
TheLogger.debug("Previous data refresh is still in progress");
|
|
||||||
} else {
|
|
||||||
_refreshCompleter = Completer();
|
|
||||||
eventBus.fire(RefreshDataEvent());
|
|
||||||
}
|
|
||||||
return _refreshCompleter.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_refreshDataSubscription.cancel();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
part of 'main.dart';
|
part of 'main.dart';
|
||||||
|
|
||||||
class TheLogger {
|
class Logger {
|
||||||
|
|
||||||
static List<String> _log = [];
|
static List<String> _log = [];
|
||||||
|
|
||||||
@ -20,15 +20,15 @@ class TheLogger {
|
|||||||
return inDebugMode;
|
return inDebugMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void error(String message) {
|
static void e(String message) {
|
||||||
_writeToLog("Error", message);
|
_writeToLog("Error", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void warning(String message) {
|
static void w(String message) {
|
||||||
_writeToLog("Warning", message);
|
_writeToLog("Warning", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void debug(String message) {
|
static void d(String message) {
|
||||||
_writeToLog("Debug", message);
|
_writeToLog("Debug", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,10 +47,42 @@ class TheLogger {
|
|||||||
|
|
||||||
class HAUtils {
|
class HAUtils {
|
||||||
static void launchURL(String url) async {
|
static void launchURL(String url) async {
|
||||||
if (await canLaunch(url)) {
|
if (await urlLauncher.canLaunch(url)) {
|
||||||
await launch(url);
|
await urlLauncher.launch(url);
|
||||||
} else {
|
} else {
|
||||||
TheLogger.error( "Could not launch $url");
|
Logger.e( "Could not launch $url");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void launchURLInCustomTab(BuildContext context, String url) async {
|
||||||
|
try {
|
||||||
|
await launch(
|
||||||
|
"$url",
|
||||||
|
option: new CustomTabsOption(
|
||||||
|
toolbarColor: Theme.of(context).primaryColor,
|
||||||
|
enableDefaultShare: true,
|
||||||
|
enableUrlBarHiding: true,
|
||||||
|
showPageTitle: true,
|
||||||
|
animation: new CustomTabsAnimation.slideIn()
|
||||||
|
// or user defined animation.
|
||||||
|
/*animation: new CustomTabsAnimation(
|
||||||
|
startEnter: 'slide_up',
|
||||||
|
startExit: 'android:anim/fade_out',
|
||||||
|
endEnter: 'android:anim/fade_in',
|
||||||
|
endExit: 'slide_down',
|
||||||
|
)*/,
|
||||||
|
extraCustomTabs: <String>[
|
||||||
|
// ref. https://play.google.com/store/apps/details?id=org.mozilla.firefox
|
||||||
|
'org.mozilla.firefox',
|
||||||
|
// ref. https://play.google.com/store/apps/details?id=com.microsoft.emmx
|
||||||
|
'com.microsoft.emmx',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
Logger.w("Can't open custom tab: ${e.toString()}");
|
||||||
|
Logger.w("Launching in default browser");
|
||||||
|
HAUtils.launchURL(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,8 +90,13 @@ class HAUtils {
|
|||||||
class StateChangedEvent {
|
class StateChangedEvent {
|
||||||
String entityId;
|
String entityId;
|
||||||
String newState;
|
String newState;
|
||||||
|
bool needToRebuildUI;
|
||||||
|
|
||||||
StateChangedEvent(this.entityId, this.newState);
|
StateChangedEvent({
|
||||||
|
this.entityId,
|
||||||
|
this.newState,
|
||||||
|
this.needToRebuildUI: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsChangedEvent {
|
class SettingsChangedEvent {
|
||||||
@ -68,10 +105,6 @@ class SettingsChangedEvent {
|
|||||||
SettingsChangedEvent(this.reconnect);
|
SettingsChangedEvent(this.reconnect);
|
||||||
}
|
}
|
||||||
|
|
||||||
class RefreshDataEvent {
|
|
||||||
RefreshDataEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
class RefreshDataFinishedEvent {
|
class RefreshDataFinishedEvent {
|
||||||
RefreshDataFinishedEvent();
|
RefreshDataFinishedEvent();
|
||||||
}
|
}
|
||||||
|
107
pubspec.lock
@ -7,7 +7,7 @@ packages:
|
|||||||
name: archive
|
name: archive
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.4"
|
version: "2.0.8"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -35,7 +35,7 @@ packages:
|
|||||||
name: cached_network_image
|
name: cached_network_image
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.0+1"
|
version: "0.8.0"
|
||||||
charcode:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -49,14 +49,14 @@ packages:
|
|||||||
name: charts_common
|
name: charts_common
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.0"
|
version: "0.6.0"
|
||||||
charts_flutter:
|
charts_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: charts_flutter
|
name: charts_flutter
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.0"
|
version: "0.6.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -70,7 +70,7 @@ packages:
|
|||||||
name: convert
|
name: convert
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.1.1"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -83,7 +83,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: HEAD
|
ref: HEAD
|
||||||
resolved-ref: c5727795659e886a7db8b39a14e2c8987280fe1f
|
resolved-ref: a7ed88a4793e094a4d5d5c2d88a89e55510accde
|
||||||
url: "https://github.com/MarkOSullivan94/dart_config.git"
|
url: "https://github.com/MarkOSullivan94/dart_config.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.5.0"
|
version: "0.5.0"
|
||||||
@ -93,14 +93,14 @@ packages:
|
|||||||
name: date_format
|
name: date_format
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.6"
|
||||||
event_bus:
|
event_bus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: event_bus
|
name: event_bus
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.1.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -112,21 +112,35 @@ packages:
|
|||||||
name: flutter_cache_manager
|
name: flutter_cache_manager
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0+1"
|
version: "0.3.2"
|
||||||
flutter_colorpicker:
|
flutter_custom_tabs:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_colorpicker
|
name: flutter_custom_tabs
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.0"
|
version: "0.6.0"
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_launcher_icons
|
name: flutter_launcher_icons
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.1"
|
version: "0.7.0"
|
||||||
|
flutter_markdown:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_markdown
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
|
flutter_svg:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_svg
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.4"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -138,7 +152,7 @@ packages:
|
|||||||
name: http
|
name: http
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.0"
|
version: "0.12.0+2"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -152,14 +166,14 @@ packages:
|
|||||||
name: image
|
name: image
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.4"
|
version: "2.0.8"
|
||||||
intl:
|
intl:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.15.7"
|
version: "0.15.8"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -167,6 +181,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.3+2"
|
version: "0.11.3+2"
|
||||||
|
markdown:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: markdown
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -188,20 +209,41 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.2"
|
version: "1.6.2"
|
||||||
|
path_drawing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_drawing
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.1"
|
||||||
|
path_parsing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_parsing
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.4"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.1"
|
version: "0.5.0+1"
|
||||||
|
pedantic:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pedantic
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: petitparser
|
name: petitparser
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.1.1"
|
||||||
progress_indicators:
|
progress_indicators:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -222,7 +264,7 @@ packages:
|
|||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.3"
|
version: "0.5.2"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -234,7 +276,14 @@ packages:
|
|||||||
name: source_span
|
name: source_span
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.5.4"
|
||||||
|
sqflite:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.5"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -262,21 +311,21 @@ packages:
|
|||||||
name: synchronized
|
name: synchronized
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.3"
|
version: "2.1.0"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: term_glyph
|
name: term_glyph
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.1.0"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1"
|
version: "0.2.2"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -290,14 +339,14 @@ packages:
|
|||||||
name: url_launcher
|
name: url_launcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.1"
|
version: "5.0.2"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "2.0.1"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -311,14 +360,14 @@ packages:
|
|||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.9"
|
version: "1.0.12"
|
||||||
xml:
|
xml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xml
|
name: xml
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.3"
|
version: "3.2.5"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -327,5 +376,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.15"
|
version: "2.1.15"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.0.0 <=2.1.0-dev.9.3.flutter-9c07fb64c4"
|
dart: ">=2.1.0 <3.0.0"
|
||||||
flutter: ">=0.5.6 <2.0.0"
|
flutter: ">=1.2.1 <2.0.0"
|
||||||
|
12
pubspec.yaml
@ -1,7 +1,7 @@
|
|||||||
name: hass_client
|
name: hass_client
|
||||||
description: Home Assistant Android Client
|
description: Home Assistant Android Client
|
||||||
|
|
||||||
version: 0.3.9+67
|
version: 0.5.4+541
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.0.0-dev.68.0 <3.0.0"
|
sdk: ">=2.0.0-dev.68.0 <3.0.0"
|
||||||
@ -16,12 +16,10 @@ dependencies:
|
|||||||
cached_network_image: any
|
cached_network_image: any
|
||||||
url_launcher: any
|
url_launcher: any
|
||||||
date_format: any
|
date_format: any
|
||||||
flutter_colorpicker: any
|
|
||||||
charts_flutter: any
|
charts_flutter: any
|
||||||
|
flutter_markdown: any
|
||||||
# The following adds the Cupertino Icons font to your application.
|
flutter_svg: ^0.10.3
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
flutter_custom_tabs: ^0.6.0
|
||||||
#cupertino_icons: ^0.1.2
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@ -62,7 +60,7 @@ flutter:
|
|||||||
fonts:
|
fonts:
|
||||||
- family: "Material Design Icons"
|
- family: "Material Design Icons"
|
||||||
fonts:
|
fonts:
|
||||||
- asset: fonts/materialdesignicons-webfont.ttf
|
- asset: fonts/materialdesignicons-webfont-3-5-95.ttf
|
||||||
# fonts:
|
# fonts:
|
||||||
# - family: Schyler
|
# - family: Schyler
|
||||||
# fonts:
|
# fonts:
|
||||||
|