Compare commits
260 Commits
0.6.0-alph
...
beta/0.7.2
Author | SHA1 | Date | |
---|---|---|---|
139533d2ca | |||
889682f771 | |||
f16c98057f | |||
26ec807c25 | |||
45af6cbe3c | |||
5dd9cde12d | |||
472fb1d367 | |||
8b372fbc0b | |||
40d72eb6e1 | |||
ced008a7c1 | |||
d1f652282a | |||
f656528d5b | |||
bcdb2a648c | |||
8a78745aa7 | |||
2a3eaabbe4 | |||
bcd175fbfb | |||
f9f013636d | |||
b34cc97080 | |||
327f623ef7 | |||
4d0877e5ae | |||
0eac217399 | |||
9c42ad687d | |||
5cda98da46 | |||
958f545f65 | |||
44165993b4 | |||
283ae6cfd4 | |||
4068b295bd | |||
e36b33dcec | |||
4b12912697 | |||
49a21967cc | |||
cf36406f2a | |||
872ad044f1 | |||
345301c03a | |||
117923413d | |||
24ccbc58c4 | |||
89c91b4b01 | |||
4494da1f4f | |||
c263542c54 | |||
c70f52a73d | |||
423813d6fb | |||
ec6a86f4b0 | |||
64cf18cb23 | |||
e0e064bc67 | |||
5cee6cbd9c | |||
43659b26f7 | |||
98e15ad429 | |||
90728cdf8b | |||
d1ec4f36cc | |||
079070071e | |||
520fd6bc38 | |||
085aead36b | |||
fcbaf298cc | |||
eedc0c9b22 | |||
f70c1e12ff | |||
ec094a4362 | |||
11646c840e | |||
86987c57c9 | |||
e4d6e842f5 | |||
cfe4dd1c59 | |||
3387ab2850 | |||
abd23e27ea | |||
2f110b20bb | |||
f88e6f9b61 | |||
2836973dca | |||
a4477e9f83 | |||
96fa7ece25 | |||
b84caa4cc3 | |||
49c212632e | |||
92165aa7ed | |||
cbbdb754aa | |||
7e3fe0608d | |||
781f39f281 | |||
bfb80f6f8c | |||
801b8f9288 | |||
b988fcfcdd | |||
dff6457cb2 | |||
f50f68f318 | |||
c869ad41d9 | |||
cd41f9a236 | |||
1dbe162bf0 | |||
1a52203bd7 | |||
753df3c724 | |||
dc62a08da3 | |||
0c26aff498 | |||
6323f8f2e6 | |||
885c0b1316 | |||
14958d9165 | |||
bf6a52e0b9 | |||
72aad5cc16 | |||
340e8569cc | |||
8fc7d0b61e | |||
5dcb27ada7 | |||
db1a076132 | |||
6707201e23 | |||
b8b92171a8 | |||
3dd7069292 | |||
7177419472 | |||
c37313cf07 | |||
a65f42d0fd | |||
78dd7df686 | |||
2ea7d9440c | |||
abdcd49368 | |||
6da7a5ab90 | |||
20ffe03139 | |||
a71213c589 | |||
d61103ac42 | |||
298a64b7ae | |||
9e2c673966 | |||
092469d668 | |||
bcf3dab0e2 | |||
7ecfc8a9ff | |||
ecf0a696f7 | |||
dc5db28e01 | |||
555f305c22 | |||
76bf07cfcd | |||
c4663576d1 | |||
a64aa73aae | |||
a3a60dd707 | |||
9c28b0085b | |||
d5baabdd53 | |||
56a333a852 | |||
c5922368de | |||
8c2316a51a | |||
e2e6c015de | |||
0a6ff4586d | |||
fc228d85ae | |||
61823cb43b | |||
127e0b8182 | |||
38c37fa212 | |||
dfaf2a2924 | |||
c90c40c046 | |||
d2049b726a | |||
6508f109f7 | |||
37e63637a7 | |||
6650c5c145 | |||
9160dbf7f2 | |||
243fcd7c49 | |||
c114bcfb35 | |||
83defb08f1 | |||
57ebdbbe85 | |||
c6aceed623 | |||
ba4c88ec5d | |||
ee1685e981 | |||
996fbf7bba | |||
56cd8963d7 | |||
5759aad0cb | |||
02717332f7 | |||
8d1b159f56 | |||
fb335e1100 | |||
5f0bc83d67 | |||
6a8cee2cc2 | |||
0d2f1cf9aa | |||
8efeb3da8a | |||
620aa3b8d8 | |||
ab5bf3b807 | |||
6663bcad72 | |||
113cd29f74 | |||
f2fdfb0a32 | |||
691e48a36b | |||
2036cc117f | |||
389d28a1e1 | |||
27e6198d83 | |||
de762a4878 | |||
e8efefe25d | |||
21f3e8985a | |||
622543d405 | |||
abdc0fc1c8 | |||
1ecb839042 | |||
cece4d1e16 | |||
623634cb6e | |||
f9c37f5084 | |||
3e12f4f8a4 | |||
b07ff6fe71 | |||
5a3b57c28e | |||
e858eee83b | |||
73f00d3bd7 | |||
eea59cf11b | |||
61b459ed8a | |||
dca8c309aa | |||
be53500104 | |||
bc1a791608 | |||
b112ff980a | |||
7beab9ae93 | |||
8c0d1f90a3 | |||
05c05ba768 | |||
67e885e76a | |||
594bce0b8d | |||
7f6569e0db | |||
1c829c8364 | |||
7ca4b02e6d | |||
fadfefd836 | |||
37155901ef | |||
fbbb96409d | |||
5126c54914 | |||
916d0b7e3c | |||
0815840a9c | |||
bc237796b2 | |||
7f44800f64 | |||
85ac746e9d | |||
8215175098 | |||
39ee8b1799 | |||
c76d3d68c8 | |||
cde257922b | |||
be0c9d3372 | |||
66cd7ea307 | |||
b704ce6984 | |||
247c856a41 | |||
9afaebfa12 | |||
929abea5d3 | |||
13102a6b04 | |||
57c3083f9f | |||
5c31ddd00f | |||
8f55be187d | |||
1fe82d8b0d | |||
cbc56a8105 | |||
b63cddfa46 | |||
91db82f730 | |||
0c4d1b78ff | |||
5af2fd0562 | |||
2375543ebf | |||
de187f3ed5 | |||
9266ffacf3 | |||
3c0ca5d16d | |||
caabf25260 | |||
0af2afbb80 | |||
12d226509d | |||
3417c38426 | |||
c7fc5afbb8 | |||
11f565a9dc | |||
53240faac3 | |||
95d4878785 | |||
ef15026203 | |||
ad6355503b | |||
491c2b0dc0 | |||
5b99ade088 | |||
e1d9d9f304 | |||
209ccd4f7f | |||
5a8a207f2e | |||
19c85d9c16 | |||
a916ddfa50 | |||
8c1ad9c7f9 | |||
93af1eca7e | |||
cabf836fa3 | |||
15b3d31a6f | |||
9b98689012 | |||
84ebd0c33c | |||
ccd7774931 | |||
b2773635f5 | |||
8b046b7313 | |||
885a516676 | |||
921b0e09b0 | |||
277c67fc6f | |||
2a01ff8a03 | |||
b246b7bc1d | |||
e1868b9a14 | |||
125f3ac16c | |||
be502b5668 | |||
6f33fdca9f | |||
a7cda2a35e | |||
102b10ade0 |
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help improve HA Client
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please provide as much information as possible.
|
||||||
|
-->
|
||||||
|
**HA Client version:** <!-- Main app menu => About HA Client -->
|
||||||
|
|
||||||
|
**Home Assistant version:** <!-- 0.94.1 for example -->
|
||||||
|
|
||||||
|
**Device name:** <!-- Pixel 2 for example -->
|
||||||
|
|
||||||
|
**Android version:** <!-- 8.1 for example -->
|
||||||
|
|
||||||
|
**Connection type:** <!-- For example "Local IP" or "Remote UI" or "Own domain"-->
|
||||||
|
|
||||||
|
**Login type:** <!-- For example "HA Login" or "Manual token"-->
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
<!--
|
||||||
|
Describe your issue here
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
<!--
|
||||||
|
Please provide screenshots if it is a UI issue. Also you can attach screenshot from Home Assistant web UI as an expected result
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Logs**
|
||||||
|
<!--
|
||||||
|
Right after issue reproduced go to app menu and tap "Log". Copy log with a "Copy" button in the upper-right corner and post it below
|
||||||
|
-->
|
||||||
|
```
|
||||||
|
[Replace this text with your logs]
|
||||||
|
```
|
12
.github/ISSUE_TEMPLATE/entity-support-request.md
vendored
Normal file
12
.github/ISSUE_TEMPLATE/entity-support-request.md
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
name: Entity support request
|
||||||
|
about: Suggest to add support of any entity type
|
||||||
|
title: ''
|
||||||
|
labels: ENTITY, feature/improvement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Entity type:**
|
||||||
|
|
||||||
|
**Link to documentation:**
|
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for HA Client if it is not a card or entity support
|
||||||
|
title: ''
|
||||||
|
labels: feature/improvement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
12
.github/ISSUE_TEMPLATE/lovelace-card-support-request.md
vendored
Normal file
12
.github/ISSUE_TEMPLATE/lovelace-card-support-request.md
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
name: Lovelace Card support request
|
||||||
|
about: Suggest to add any Lovelace card support
|
||||||
|
title: ''
|
||||||
|
labels: CARD, feature/improvement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Card name:**
|
||||||
|
|
||||||
|
**Link to card repository or web page:**
|
11
.github/no-response.yml
vendored
Normal file
11
.github/no-response.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Configuration for probot-no-response - https://github.com/probot/no-response
|
||||||
|
|
||||||
|
# Number of days of inactivity before an Issue is closed for lack of response
|
||||||
|
daysUntilClose: 14
|
||||||
|
# Label requiring a response
|
||||||
|
responseRequiredLabel: more info needed
|
||||||
|
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
|
||||||
|
closeComment: >
|
||||||
|
This issue has been automatically closed because there has been no response
|
||||||
|
to our request for more information from the original author. If the issue still relevant
|
||||||
|
feel free to reopen this issue and add more information, or report a new one.
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -9,5 +9,13 @@ build/
|
|||||||
.flutter-plugins
|
.flutter-plugins
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.theia/
|
||||||
|
.project/
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
flutter_export_environment.sh
|
||||||
|
|
||||||
key.properties
|
key.properties
|
||||||
|
premium_features_manager.class.dart
|
||||||
|
pubspec.lock
|
12
.gitpod.dockerfile
Normal file
12
.gitpod.dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM gitpod/workspace-full:latest
|
||||||
|
|
||||||
|
ENV ANDROID_HOME=/workspace/android-sdk \
|
||||||
|
FLUTTER_ROOT=/workspace/flutter \
|
||||||
|
FLUTTER_HOME=/workspace/flutter
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get -y install build-essential libkrb5-dev gcc make gradle openjdk-8-jdk && \
|
||||||
|
apt-get clean && \
|
||||||
|
apt-get -y autoremove
|
27
.gitpod.yml
Normal file
27
.gitpod.yml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
image:
|
||||||
|
file: .gitpod.dockerfile
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- before: |
|
||||||
|
export PATH=$FLUTTER_HOME/bin:$ANDROID_HOME/bin:$ANDROID_HOME/platform-tools:$PATH
|
||||||
|
mkdir -p /home/gitpod/.android
|
||||||
|
touch /home/gitpod/.android/repositories.cfg
|
||||||
|
init: |
|
||||||
|
echo "Installing Flutter SDK..."
|
||||||
|
cd /workspace && wget -qO flutter_sdk.tar.xz https://storage.googleapis.com/flutter_infra/releases/stable/linux/flutter_linux_v1.9.1+hotfix.4-stable.tar.xz && tar -xf flutter_sdk.tar.xz && rm -f flutter_sdk.tar.xz
|
||||||
|
echo "Installing Android SDK..."
|
||||||
|
mkdir -p /workspace/android-sdk && cd /workspace/android-sdk && wget https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip && unzip sdk-tools-linux-4333796.zip && rm -f sdk-tools-linux-4333796.zip
|
||||||
|
/workspace/android-sdk/tools/bin/sdkmanager "platform-tools" "platforms;android-28" "build-tools;28.0.3"
|
||||||
|
echo "Init Flutter..."
|
||||||
|
cd /workspace/ha_client
|
||||||
|
flutter upgrade
|
||||||
|
flutter doctor --android-licenses
|
||||||
|
flutter pub get
|
||||||
|
command: |
|
||||||
|
flutter pub upgrade
|
||||||
|
echo "Ready to go!"
|
||||||
|
flutter doctor
|
||||||
|
vscode:
|
||||||
|
extensions:
|
||||||
|
- Dart-Code.dart-code@3.5.0-beta.1:Wg2nTABftVR/Dry4tqeY1w==
|
||||||
|
- Dart-Code.flutter@3.5.0:/kOacEWdiDRLyN/idUiM4A==
|
13
README.md
13
README.md
@ -1,12 +1,15 @@
|
|||||||
[](https://somegeeky.website/badges/flutter) [](https://somegeeky.website/badges/dart)
|
[](https://gitpod.io/#https://github.com/estevez-dev/ha_client)
|
||||||
# HA Client
|
# HA Client
|
||||||
## Native Android client for Home Assistant
|
## Native Android client for Home Assistant
|
||||||
### With Lovelace UI support
|
### With notifications and Lovelace UI support
|
||||||
|
|
||||||
Visit [homemade.systems](http://ha-client.homemade.systems/) for more info.
|
Visit [homemade.systems](http://ha-client.homemade.systems/) for more info.
|
||||||
|
|
||||||
Join [Google Group](https://groups.google.com/d/forum/ha-client-alpha-testing) to become an alpha tester
|
Download the app from [Google Play](https://play.google.com/apps/testing/com.keyboardcrumbs.haclient)
|
||||||
|
|
||||||
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 on [Discord server](https://discord.gg/AUzEvwn)
|
||||||
|
|
||||||
Discuss it in [Home Assistant community](https://community.home-assistant.io/t/alpha-testing-ha-client-native-android-client-for-home-assistant/69912)
|
#### Pre-release CI build
|
||||||
|
[](https://codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5da8bdab9f20ef798f7c2c64/latest_build)
|
||||||
|
#### Beta CI build
|
||||||
|
[](https://codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5db1862025dc3f0b0288a57a/latest_build)
|
||||||
|
1
android/.gitignore
vendored
1
android/.gitignore
vendored
@ -8,3 +8,4 @@
|
|||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
GeneratedPluginRegistrant.java
|
GeneratedPluginRegistrant.java
|
||||||
|
.project/
|
17
android/.project
Normal file
17
android/.project
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>android</name>
|
||||||
|
<comment>Project android created by Buildship.</comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
6
android/app/.classpath
Normal file
6
android/app/.classpath
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||||
|
<classpathentry kind="output" path="bin/default"/>
|
||||||
|
</classpath>
|
23
android/app/.project
Normal file
23
android/app/.project
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>app</name>
|
||||||
|
<comment>Project app created by Buildship.</comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
@ -50,6 +50,14 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
if (!System.getenv()["CI"]) {
|
||||||
|
debug {
|
||||||
|
keyAlias keystoreProperties['debugKeyAlias']
|
||||||
|
keyPassword keystoreProperties['debugKeyPassword']
|
||||||
|
storeFile file(keystoreProperties['debugStoreFile'])
|
||||||
|
storePassword keystoreProperties['debugStorePassword']
|
||||||
|
}
|
||||||
|
}
|
||||||
release {
|
release {
|
||||||
keyAlias keystoreProperties['keyAlias']
|
keyAlias keystoreProperties['keyAlias']
|
||||||
keyPassword keystoreProperties['keyPassword']
|
keyPassword keystoreProperties['keyPassword']
|
||||||
|
@ -14,6 +14,30 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"oauth_client": [
|
"oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "441874387819-uqmkibhf361828od1982o2jhl0n3m0ov.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.keyboardcrumbs.haclient",
|
||||||
|
"certificate_hash": "bebe4d970fbebf0bff2c93244fdc7fcbcefb3470"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "441874387819-5q7vmimci4s2jl3v0ncugv1ocp4m48nb.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.keyboardcrumbs.haclient",
|
||||||
|
"certificate_hash": "0ea12348468be44bc2aa5792ee7e8924c633da81"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "441874387819-joi8plo5345ebt8i1dug27u2aenv5tg7.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.keyboardcrumbs.haclient",
|
||||||
|
"certificate_hash": "fcbc805d965ccf6a4d5417398d191edc9c9890b0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"client_id": "441874387819-id0hqsfprj3b5kc312faqv3lmdfpm7l8.apps.googleusercontent.com",
|
"client_id": "441874387819-id0hqsfprj3b5kc312faqv3lmdfpm7l8.apps.googleusercontent.com",
|
||||||
"client_type": 3
|
"client_type": 3
|
||||||
@ -25,15 +49,13 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"services": {
|
"services": {
|
||||||
"analytics_service": {
|
|
||||||
"status": 1
|
|
||||||
},
|
|
||||||
"appinvite_service": {
|
"appinvite_service": {
|
||||||
"status": 1,
|
"other_platform_oauth_client": [
|
||||||
"other_platform_oauth_client": []
|
{
|
||||||
},
|
"client_id": "441874387819-id0hqsfprj3b5kc312faqv3lmdfpm7l8.apps.googleusercontent.com",
|
||||||
"ads_service": {
|
"client_type": 3
|
||||||
"status": 2
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.keyboardcrumbs.hassclient">
|
package="com.keyboardcrumbs.hassclient">
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.touchscreen"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
<!-- The INTERNET permission is required for development. Specifically,
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
flutter needs it to communicate with the running application
|
flutter needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
|
|
||||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||||
@ -13,10 +21,15 @@
|
|||||||
additional functionality it is fine to subclass or reimplement
|
additional functionality it is fine to subclass or reimplement
|
||||||
FlutterApplication and put your custom class here. -->
|
FlutterApplication and put your custom class here. -->
|
||||||
<application
|
<application
|
||||||
android:name="io.flutter.app.FlutterApplication"
|
android:name=".Application"
|
||||||
android:label="HA Client"
|
android:label="HA Client"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:usesCleartextTraffic="true">
|
android:usesCleartextTraffic="true">
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||||
|
android:value="ha_notify" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
@ -27,10 +40,10 @@
|
|||||||
<!-- This keeps the window background of the activity showing
|
<!-- This keeps the window background of the activity showing
|
||||||
until Flutter renders its first frame. It can be removed if
|
until Flutter renders its first frame. It can be removed if
|
||||||
there is no splash screen (such as the default splash screen
|
there is no splash screen (such as the default splash screen
|
||||||
defined in @style/LaunchTheme). -->
|
defined in @style/LaunchTheme).
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
|
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
|
||||||
android:value="true" />
|
android:value="true" />-->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
|
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
@ -39,6 +52,29 @@
|
|||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data
|
||||||
|
android:scheme="haclient"
|
||||||
|
android:host="auth" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="io.flutter.plugins.androidalarmmanager.AlarmService"
|
||||||
|
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||||
|
android:exported="false"/>
|
||||||
|
<receiver
|
||||||
|
android:name="io.flutter.plugins.androidalarmmanager.AlarmBroadcastReceiver"
|
||||||
|
android:exported="false"/>
|
||||||
|
<receiver
|
||||||
|
android:name="io.flutter.plugins.androidalarmmanager.RebootBroadcastReceiver"
|
||||||
|
android:enabled="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED"></action>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.keyboardcrumbs.hassclient;
|
||||||
|
|
||||||
|
import io.flutter.app.FlutterApplication;
|
||||||
|
import io.flutter.plugin.common.PluginRegistry;
|
||||||
|
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback;
|
||||||
|
import io.flutter.plugins.GeneratedPluginRegistrant;
|
||||||
|
import be.tramckrijte.workmanager.WorkmanagerPlugin;
|
||||||
|
|
||||||
|
public class Application extends FlutterApplication implements PluginRegistrantCallback {
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
WorkmanagerPlugin.setPluginRegistrantCallback(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerWith(PluginRegistry registry) {
|
||||||
|
GeneratedPluginRegistrant.registerWith(registry);
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,10 @@ package com.keyboardcrumbs.hassclient;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import io.flutter.app.FlutterActivity;
|
import io.flutter.app.FlutterActivity;
|
||||||
import io.flutter.plugins.GeneratedPluginRegistrant;
|
import io.flutter.plugins.GeneratedPluginRegistrant;
|
||||||
|
import io.flutter.plugins.share.FlutterShareReceiverActivity;
|
||||||
|
|
||||||
|
public class MainActivity extends FlutterShareReceiverActivity {
|
||||||
|
|
||||||
public class MainActivity extends FlutterActivity {
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
BIN
android/app/src/main/res/drawable/mini_icon.png
Normal file
BIN
android/app/src/main/res/drawable/mini_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 612 B |
1232
android/hs_err_pid766.log
Normal file
1232
android/hs_err_pid766.log
Normal file
File diff suppressed because it is too large
Load Diff
16
assets/js/externalAuth.js
Normal file
16
assets/js/externalAuth.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
window.externalApp = {};
|
||||||
|
window.externalApp.getExternalAuth = function(options) {
|
||||||
|
console.log("Starting external auth");
|
||||||
|
var options = JSON.parse(options);
|
||||||
|
if (options && options.callback) {
|
||||||
|
var responseData = {
|
||||||
|
access_token: "[token]",
|
||||||
|
expires_in: 1800
|
||||||
|
};
|
||||||
|
console.log("Waiting for callback to be added");
|
||||||
|
setTimeout(function(){
|
||||||
|
console.log("Calling a callback");
|
||||||
|
window[options.callback](true, responseData);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
};
|
41
flutter_01.log
Normal file
41
flutter_01.log
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
Flutter crash report; please file at https://github.com/flutter/flutter/issues.
|
||||||
|
|
||||||
|
## command
|
||||||
|
|
||||||
|
flutter --no-color run --machine --track-widget-creation --device-id=89AY052S4 lib/main.dart
|
||||||
|
|
||||||
|
## exception
|
||||||
|
|
||||||
|
_InternalLinkedHashMap<String, dynamic>: {code: 105, message: Isolate must be runnable, data: {request: {method: _reloadSources, params: {pause: true, rootLibUri: file:///data/user/0/com.keyboardcrumbs.haclient/code_cache/ha_clientSYJJZI/ha_client/lib/main.dart.incremental.dill, packagesUri: file:///data/user/0/com.keyboardcrumbs.haclient/code_cache/ha_clientSYJJZI/ha_client/.packages, isolateId: isolates/68989666}}, details: Isolate must be runnable before this request is made.}}
|
||||||
|
|
||||||
|
```
|
||||||
|
null```
|
||||||
|
|
||||||
|
## flutter doctor
|
||||||
|
|
||||||
|
```
|
||||||
|
[✓] Flutter (Channel stable, v1.7.8+hotfix.4, on Linux, locale en_US.UTF-8)
|
||||||
|
• Flutter version 1.7.8+hotfix.4 at /home/estevez/sdk/flutter
|
||||||
|
• Framework revision 20e59316b8 (6 weeks ago), 2019-07-18 20:04:33 -0700
|
||||||
|
• Engine revision fee001c93f
|
||||||
|
• Dart version 2.4.0
|
||||||
|
|
||||||
|
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
|
||||||
|
• Android SDK at /home/estevez/Android/Sdk
|
||||||
|
• Android NDK location not configured (optional; useful for native profiling support)
|
||||||
|
• Platform android-29, build-tools 29.0.2
|
||||||
|
• Java binary at: /home/estevez/bin/android-studio/jre/bin/java
|
||||||
|
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
|
||||||
|
• All Android licenses accepted.
|
||||||
|
|
||||||
|
[✓] Android Studio (version 3.5)
|
||||||
|
• Android Studio at /home/estevez/bin/android-studio
|
||||||
|
• Flutter plugin version 38.2.3
|
||||||
|
• Dart plugin version 191.8423
|
||||||
|
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
|
||||||
|
|
||||||
|
[✓] Connected device (1 available)
|
||||||
|
• Pixel 3 XL • 89AY052S4 • android-arm64 • Android 9 (API 28)
|
||||||
|
|
||||||
|
• No issues found!
|
||||||
|
```
|
41
flutter_02.log
Normal file
41
flutter_02.log
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
Flutter crash report; please file at https://github.com/flutter/flutter/issues.
|
||||||
|
|
||||||
|
## command
|
||||||
|
|
||||||
|
flutter --no-color run --machine --track-widget-creation --device-id=89AY052S4 lib/main.dart
|
||||||
|
|
||||||
|
## exception
|
||||||
|
|
||||||
|
_InternalLinkedHashMap<String, dynamic>: {code: 105, message: Isolate must be runnable, data: {request: {method: _reloadSources, params: {pause: true, rootLibUri: file:///data/user/0/com.keyboardcrumbs.haclient/code_cache/ha_clientWYMXDL/ha_client/lib/main.dart.incremental.dill, packagesUri: file:///data/user/0/com.keyboardcrumbs.haclient/code_cache/ha_clientWYMXDL/ha_client/.packages, isolateId: isolates/289688365}}, details: Isolate must be runnable before this request is made.}}
|
||||||
|
|
||||||
|
```
|
||||||
|
null```
|
||||||
|
|
||||||
|
## flutter doctor
|
||||||
|
|
||||||
|
```
|
||||||
|
[✓] Flutter (Channel stable, v1.7.8+hotfix.4, on Linux, locale en_US.UTF-8)
|
||||||
|
• Flutter version 1.7.8+hotfix.4 at /home/estevez/sdk/flutter
|
||||||
|
• Framework revision 20e59316b8 (6 weeks ago), 2019-07-18 20:04:33 -0700
|
||||||
|
• Engine revision fee001c93f
|
||||||
|
• Dart version 2.4.0
|
||||||
|
|
||||||
|
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
|
||||||
|
• Android SDK at /home/estevez/Android/Sdk
|
||||||
|
• Android NDK location not configured (optional; useful for native profiling support)
|
||||||
|
• Platform android-29, build-tools 29.0.2
|
||||||
|
• Java binary at: /home/estevez/bin/android-studio/jre/bin/java
|
||||||
|
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
|
||||||
|
• All Android licenses accepted.
|
||||||
|
|
||||||
|
[✓] Android Studio (version 3.5)
|
||||||
|
• Android Studio at /home/estevez/bin/android-studio
|
||||||
|
• Flutter plugin version 38.2.3
|
||||||
|
• Dart plugin version 191.8423
|
||||||
|
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
|
||||||
|
|
||||||
|
[✓] Connected device (1 available)
|
||||||
|
• Pixel 3 XL • 89AY052S4 • android-arm64 • Android 9 (API 28)
|
||||||
|
|
||||||
|
• No issues found!
|
||||||
|
```
|
41
flutter_03.log
Normal file
41
flutter_03.log
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
Flutter crash report; please file at https://github.com/flutter/flutter/issues.
|
||||||
|
|
||||||
|
## command
|
||||||
|
|
||||||
|
flutter --no-color run --machine --track-widget-creation --device-id=89AY052S4 lib/main.dart
|
||||||
|
|
||||||
|
## exception
|
||||||
|
|
||||||
|
_InternalLinkedHashMap<String, dynamic>: {code: 105, message: Isolate must be runnable, data: {request: {method: _reloadSources, params: {pause: true, rootLibUri: file:///data/user/0/com.keyboardcrumbs.haclient/code_cache/ha_clientLNSJAH/ha_client/lib/main.dart.incremental.dill, packagesUri: file:///data/user/0/com.keyboardcrumbs.haclient/code_cache/ha_clientLNSJAH/ha_client/.packages, isolateId: isolates/866521062}}, details: Isolate must be runnable before this request is made.}}
|
||||||
|
|
||||||
|
```
|
||||||
|
null```
|
||||||
|
|
||||||
|
## flutter doctor
|
||||||
|
|
||||||
|
```
|
||||||
|
[✓] Flutter (Channel stable, v1.7.8+hotfix.4, on Linux, locale en_US.UTF-8)
|
||||||
|
• Flutter version 1.7.8+hotfix.4 at /home/estevez/sdk/flutter
|
||||||
|
• Framework revision 20e59316b8 (6 weeks ago), 2019-07-18 20:04:33 -0700
|
||||||
|
• Engine revision fee001c93f
|
||||||
|
• Dart version 2.4.0
|
||||||
|
|
||||||
|
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
|
||||||
|
• Android SDK at /home/estevez/Android/Sdk
|
||||||
|
• Android NDK location not configured (optional; useful for native profiling support)
|
||||||
|
• Platform android-29, build-tools 29.0.2
|
||||||
|
• Java binary at: /home/estevez/bin/android-studio/jre/bin/java
|
||||||
|
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
|
||||||
|
• All Android licenses accepted.
|
||||||
|
|
||||||
|
[✓] Android Studio (version 3.5)
|
||||||
|
• Android Studio at /home/estevez/bin/android-studio
|
||||||
|
• Flutter plugin version 38.2.3
|
||||||
|
• Dart plugin version 191.8423
|
||||||
|
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
|
||||||
|
|
||||||
|
[✓] Connected device (1 available)
|
||||||
|
• Pixel 3 XL • 89AY052S4 • android-arm64 • Android 9 (API 28)
|
||||||
|
|
||||||
|
• No issues found!
|
||||||
|
```
|
Binary file not shown.
BIN
fonts/materialdesignicons-webfont-4.5.95.ttf
Normal file
BIN
fonts/materialdesignicons-webfont-4.5.95.ttf
Normal file
Binary file not shown.
Binary file not shown.
@ -1,44 +0,0 @@
|
|||||||
part of 'main.dart';
|
|
||||||
|
|
||||||
class AuthManager {
|
|
||||||
|
|
||||||
static final AuthManager _instance = AuthManager._internal();
|
|
||||||
|
|
||||||
factory AuthManager() {
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthManager._internal();
|
|
||||||
|
|
||||||
Future getTempToken({String httpWebHost, String oauthUrl}) {
|
|
||||||
Completer completer = Completer();
|
|
||||||
final flutterWebviewPlugin = new FlutterWebviewPlugin();
|
|
||||||
flutterWebviewPlugin.onUrlChanged.listen((String url) {
|
|
||||||
if (url.startsWith("http://ha-client.homemade.systems/service/auth_callback.html")) {
|
|
||||||
String authCode = url.split("=")[1];
|
|
||||||
Logger.d("We have auth code. Getting temporary access token...");
|
|
||||||
Connection().sendHTTPPost(
|
|
||||||
host: httpWebHost,
|
|
||||||
endPoint: "/auth/token",
|
|
||||||
contentType: "application/x-www-form-urlencoded",
|
|
||||||
includeAuthHeader: false,
|
|
||||||
data: "grant_type=authorization_code&code=$authCode&client_id=${Uri.encodeComponent('http://ha-client.homemade.systems/')}"
|
|
||||||
).then((response) {
|
|
||||||
Logger.d("Gottemp token");
|
|
||||||
String tempToken = json.decode(response)['access_token'];
|
|
||||||
Logger.d("Closing webview...");
|
|
||||||
flutterWebviewPlugin.close();
|
|
||||||
completer.complete(tempToken);
|
|
||||||
}).catchError((e) {
|
|
||||||
flutterWebviewPlugin.close();
|
|
||||||
completer.completeError({"errorCode": 61, "errorMessage": "Error getting temp token"});
|
|
||||||
Logger.e("Error getting temp token: ${e.toString()}");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Logger.d("Launching OAuth...");
|
|
||||||
eventBus.fire(StartAuthEvent(oauthUrl));
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -13,7 +13,12 @@ class HACard {
|
|||||||
int columnsCount;
|
int columnsCount;
|
||||||
List stateFilter;
|
List stateFilter;
|
||||||
List states;
|
List states;
|
||||||
|
List conditions;
|
||||||
String content;
|
String content;
|
||||||
|
String unit;
|
||||||
|
int min;
|
||||||
|
int max;
|
||||||
|
Map severity;
|
||||||
|
|
||||||
HACard({
|
HACard({
|
||||||
this.name,
|
this.name,
|
||||||
@ -26,12 +31,21 @@ class HACard {
|
|||||||
this.showEmpty: true,
|
this.showEmpty: true,
|
||||||
this.content,
|
this.content,
|
||||||
this.states,
|
this.states,
|
||||||
|
this.conditions: const [],
|
||||||
|
this.unit,
|
||||||
|
this.min,
|
||||||
|
this.max,
|
||||||
|
this.severity,
|
||||||
@required this.type
|
@required this.type
|
||||||
});
|
}) {
|
||||||
|
if (this.columnsCount <= 0) {
|
||||||
|
this.columnsCount = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<EntityWrapper> getEntitiesToShow() {
|
List<EntityWrapper> getEntitiesToShow() {
|
||||||
return entities.where((entityWrapper) {
|
return entities.where((entityWrapper) {
|
||||||
if (entityWrapper.entity.isHidden) {
|
if (!ConnectionManager().useLovelace && entityWrapper.entity.isHidden) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (stateFilter.isNotEmpty) {
|
if (stateFilter.isNotEmpty) {
|
@ -24,33 +24,58 @@ class CardWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (card.conditions.isNotEmpty) {
|
||||||
|
bool showCardByConditions = true;
|
||||||
|
for (var condition in card.conditions) {
|
||||||
|
Entity conditionEntity = HomeAssistant().entities.get(condition['entity']);
|
||||||
|
if (conditionEntity != null &&
|
||||||
|
((condition['state'] != null && conditionEntity.state != condition['state']) ||
|
||||||
|
(condition['state_not'] != null && conditionEntity.state == condition['state_not']))
|
||||||
|
) {
|
||||||
|
showCardByConditions = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!showCardByConditions) {
|
||||||
|
return Container(width: 0.0, height: 0.0,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (card.type) {
|
switch (card.type) {
|
||||||
|
|
||||||
case CardType.entities: {
|
case CardType.ENTITIES: {
|
||||||
return _buildEntitiesCard(context);
|
return _buildEntitiesCard(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
case CardType.glance: {
|
case CardType.GLANCE: {
|
||||||
return _buildGlanceCard(context);
|
return _buildGlanceCard(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
case CardType.mediaControl: {
|
case CardType.MEDIA_CONTROL: {
|
||||||
return _buildMediaControlsCard(context);
|
return _buildMediaControlsCard(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
case CardType.entityButton: {
|
case CardType.ENTITY_BUTTON: {
|
||||||
return _buildEntityButtonCard(context);
|
return _buildEntityButtonCard(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
case CardType.markdown: {
|
case CardType.GAUGE: {
|
||||||
|
return _buildGaugeCard(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* case CardType.LIGHT: {
|
||||||
|
return _buildLightCard(context);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
case CardType.MARKDOWN: {
|
||||||
return _buildMarkdownCard(context);
|
return _buildMarkdownCard(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
case CardType.alarmPanel: {
|
case CardType.ALARM_PANEL: {
|
||||||
return _buildAlarmPanelCard(context);
|
return _buildAlarmPanelCard(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
case CardType.horizontalStack: {
|
case CardType.HORIZONTAL_STACK: {
|
||||||
if (card.childCards.isNotEmpty) {
|
if (card.childCards.isNotEmpty) {
|
||||||
List<Widget> children = [];
|
List<Widget> children = [];
|
||||||
card.childCards.forEach((card) {
|
card.childCards.forEach((card) {
|
||||||
@ -73,7 +98,7 @@ class CardWidget extends StatelessWidget {
|
|||||||
return Container(height: 0.0, width: 0.0,);
|
return Container(height: 0.0, width: 0.0,);
|
||||||
}
|
}
|
||||||
|
|
||||||
case CardType.verticalStack: {
|
case CardType.VERTICAL_STACK: {
|
||||||
if (card.childCards.isNotEmpty) {
|
if (card.childCards.isNotEmpty) {
|
||||||
List<Widget> children = [];
|
List<Widget> children = [];
|
||||||
card.childCards.forEach((card) {
|
card.childCards.forEach((card) {
|
||||||
@ -107,22 +132,23 @@ class CardWidget extends StatelessWidget {
|
|||||||
return Container(height: 0.0, width: 0.0,);
|
return Container(height: 0.0, width: 0.0,);
|
||||||
}
|
}
|
||||||
List<Widget> body = [];
|
List<Widget> body = [];
|
||||||
body.add(CardHeaderWidget(name: card.name));
|
body.add(CardHeader(name: card.name));
|
||||||
entitiesToShow.forEach((EntityWrapper entity) {
|
entitiesToShow.forEach((EntityWrapper entity) {
|
||||||
if (!entity.entity.isHidden) {
|
body.add(
|
||||||
body.add(
|
Padding(
|
||||||
Padding(
|
padding: EdgeInsets.fromLTRB(0.0, 4.0, 0.0, 4.0),
|
||||||
padding: EdgeInsets.fromLTRB(10.0, 4.0, 0.0, 4.0),
|
child: EntityModel(
|
||||||
child: EntityModel(
|
entityWrapper: entity,
|
||||||
entityWrapper: entity,
|
handleTap: true,
|
||||||
handleTap: true,
|
child: entity.entity.buildDefaultWidget(context)
|
||||||
child: entity.entity.buildDefaultWidget(context)
|
),
|
||||||
),
|
));
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return Card(
|
return Card(
|
||||||
child: new Column(mainAxisSize: MainAxisSize.min, children: body)
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(right: Sizes.rightWidgetPadding, left: Sizes.leftWidgetPadding),
|
||||||
|
child: Column(mainAxisSize: MainAxisSize.min, children: body),
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +157,7 @@ class CardWidget extends StatelessWidget {
|
|||||||
return Container(height: 0.0, width: 0.0,);
|
return Container(height: 0.0, width: 0.0,);
|
||||||
}
|
}
|
||||||
List<Widget> body = [];
|
List<Widget> body = [];
|
||||||
body.add(CardHeaderWidget(name: card.name));
|
body.add(CardHeader(name: card.name));
|
||||||
body.add(MarkdownBody(data: card.content));
|
body.add(MarkdownBody(data: card.content));
|
||||||
return Card(
|
return Card(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@ -143,7 +169,7 @@ class CardWidget extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildAlarmPanelCard(BuildContext context) {
|
Widget _buildAlarmPanelCard(BuildContext context) {
|
||||||
List<Widget> body = [];
|
List<Widget> body = [];
|
||||||
body.add(CardHeaderWidget(
|
body.add(CardHeader(
|
||||||
name: card.name ?? "",
|
name: card.name ?? "",
|
||||||
subtitle: Text("${card.linkedEntityWrapper.entity.displayState}",
|
subtitle: Text("${card.linkedEntityWrapper.entity.displayState}",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -164,7 +190,7 @@ class CardWidget extends StatelessWidget {
|
|||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||||
"mdi:dots-vertical")),
|
"mdi:dots-vertical")),
|
||||||
onPressed: () => eventBus.fire(new ShowEntityPageEvent(card.linkedEntityWrapper.entity))
|
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity: card.linkedEntityWrapper.entity))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -195,39 +221,51 @@ class CardWidget extends StatelessWidget {
|
|||||||
return Container(height: 0.0, width: 0.0,);
|
return Container(height: 0.0, width: 0.0,);
|
||||||
}
|
}
|
||||||
List<Widget> rows = [];
|
List<Widget> rows = [];
|
||||||
rows.add(CardHeaderWidget(name: card.name));
|
rows.add(CardHeader(name: card.name));
|
||||||
|
|
||||||
List<Widget> result = [];
|
|
||||||
int columnsCount = entitiesToShow.length >= card.columnsCount ? card.columnsCount : entitiesToShow.length;
|
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(
|
rows.add(
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, 2*Sizes.rowPadding),
|
padding: EdgeInsets.only(bottom: Sizes.rowPadding, top: Sizes.rowPadding),
|
||||||
child: Wrap(
|
child: FractionallySizedBox(
|
||||||
//alignment: WrapAlignment.spaceAround,
|
widthFactor: 1,
|
||||||
runSpacing: Sizes.rowPadding*2,
|
child: LayoutBuilder(
|
||||||
children: result,
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
List<Widget> buttons = [];
|
||||||
|
double buttonWidth = constraints.maxWidth / columnsCount;
|
||||||
|
entitiesToShow.forEach((EntityWrapper entity) {
|
||||||
|
buttons.add(
|
||||||
|
SizedBox(
|
||||||
|
width: buttonWidth,
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: entity,
|
||||||
|
child: GlanceCardEntityContainer(
|
||||||
|
showName: card.showName,
|
||||||
|
showState: card.showState,
|
||||||
|
),
|
||||||
|
handleTap: true
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return Wrap(
|
||||||
|
//spacing: 5.0,
|
||||||
|
//alignment: WrapAlignment.spaceEvenly,
|
||||||
|
runSpacing: Sizes.doubleRowPadding,
|
||||||
|
children: buttons,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
child: new Column(mainAxisSize: MainAxisSize.min, children: rows)
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: rows
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +285,41 @@ class CardWidget extends StatelessWidget {
|
|||||||
return Card(
|
return Card(
|
||||||
child: EntityModel(
|
child: EntityModel(
|
||||||
entityWrapper: card.linkedEntityWrapper,
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
child: ButtonEntityContainer(),
|
child: EntityButtonCardBody(),
|
||||||
|
handleTap: true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildGaugeCard(BuildContext context) {
|
||||||
|
card.linkedEntityWrapper.displayName = card.name ??
|
||||||
|
card.linkedEntityWrapper.displayName;
|
||||||
|
card.linkedEntityWrapper.unitOfMeasurement = card.unit ??
|
||||||
|
card.linkedEntityWrapper.unitOfMeasurement;
|
||||||
|
return Card(
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
|
child: GaugeCardBody(
|
||||||
|
min: card.min,
|
||||||
|
max: card.max,
|
||||||
|
severity: card.severity,
|
||||||
|
),
|
||||||
|
handleTap: true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLightCard(BuildContext context) {
|
||||||
|
card.linkedEntityWrapper.displayName = card.name ??
|
||||||
|
card.linkedEntityWrapper.displayName;
|
||||||
|
return Card(
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
|
child: LightCardBody(
|
||||||
|
min: card.min,
|
||||||
|
max: card.max,
|
||||||
|
severity: card.severity,
|
||||||
|
),
|
||||||
handleTap: true
|
handleTap: true
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -255,7 +327,7 @@ class CardWidget extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildUnsupportedCard(BuildContext context) {
|
Widget _buildUnsupportedCard(BuildContext context) {
|
||||||
List<Widget> body = [];
|
List<Widget> body = [];
|
||||||
body.add(CardHeaderWidget(name: card.name ?? ""));
|
body.add(CardHeader(name: card.name ?? ""));
|
||||||
List<Widget> result = [];
|
List<Widget> result = [];
|
||||||
if (card.linkedEntityWrapper != null) {
|
if (card.linkedEntityWrapper != null) {
|
||||||
result.addAll(<Widget>[
|
result.addAll(<Widget>[
|
@ -1,12 +1,12 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class CardHeaderWidget extends StatelessWidget {
|
class CardHeader extends StatelessWidget {
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final Widget trailing;
|
final Widget trailing;
|
||||||
final Widget subtitle;
|
final Widget subtitle;
|
||||||
|
|
||||||
const CardHeaderWidget({Key key, this.name, this.trailing, this.subtitle}) : super(key: key);
|
const CardHeader({Key key, this.name, this.trailing, this.subtitle}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
@ -1,8 +1,8 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class ButtonEntityContainer extends StatelessWidget {
|
class EntityButtonCardBody extends StatelessWidget {
|
||||||
|
|
||||||
ButtonEntityContainer({
|
EntityButtonCardBody({
|
||||||
Key key,
|
Key key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -15,24 +15,25 @@ class ButtonEntityContainer extends StatelessWidget {
|
|||||||
if (entityWrapper.entity.statelessType > StatelessEntityType.MISSED) {
|
if (entityWrapper.entity.statelessType > StatelessEntityType.MISSED) {
|
||||||
return Container(width: 0.0, height: 0.0,);
|
return Container(width: 0.0, height: 0.0,);
|
||||||
}
|
}
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => entityWrapper.handleTap(),
|
onTap: () => entityWrapper.handleTap(),
|
||||||
onLongPress: () => entityWrapper.handleHold(),
|
onLongPress: () => entityWrapper.handleHold(),
|
||||||
child: Column(
|
child: FractionallySizedBox(
|
||||||
mainAxisSize: MainAxisSize.min,
|
widthFactor: 1,
|
||||||
children: <Widget>[
|
child: Column(
|
||||||
FractionallySizedBox(
|
children: <Widget>[
|
||||||
widthFactor: 0.4,
|
LayoutBuilder(
|
||||||
child: FittedBox(
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
fit: BoxFit.fitHeight,
|
return EntityIcon(
|
||||||
child: EntityIcon(
|
padding: EdgeInsets.fromLTRB(2.0, 6.0, 2.0, 2.0),
|
||||||
padding: EdgeInsets.fromLTRB(2.0, 6.0, 2.0, 2.0),
|
size: constraints.maxWidth / 2.5,
|
||||||
size: Sizes.iconSize,
|
);
|
||||||
)
|
}
|
||||||
),
|
),
|
||||||
),
|
_buildName()
|
||||||
_buildName()
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
153
lib/cards/widgets/gauge_card_body.dart
Normal file
153
lib/cards/widgets/gauge_card_body.dart
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class GaugeCardBody extends StatefulWidget {
|
||||||
|
|
||||||
|
final int min;
|
||||||
|
final int max;
|
||||||
|
final Map severity;
|
||||||
|
|
||||||
|
GaugeCardBody({Key key, this.min, this.max, this.severity}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_GaugeCardBodyState createState() => _GaugeCardBodyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GaugeCardBodyState extends State<GaugeCardBody> {
|
||||||
|
|
||||||
|
List<charts.Series> seriesList;
|
||||||
|
|
||||||
|
List<charts.Series<GaugeSegment, String>> _createData(double value) {
|
||||||
|
double fixedValue;
|
||||||
|
if (value > widget.max) {
|
||||||
|
fixedValue = widget.max.toDouble();
|
||||||
|
} else if (value < widget.min) {
|
||||||
|
fixedValue = widget.min.toDouble();
|
||||||
|
} else {
|
||||||
|
fixedValue = value;
|
||||||
|
}
|
||||||
|
double toShow = ((fixedValue - widget.min) / (widget.max - widget.min)) * 100;
|
||||||
|
Color mainColor;
|
||||||
|
if (widget.severity != null) {
|
||||||
|
if (widget.severity["red"] is int && fixedValue >= widget.severity["red"]) {
|
||||||
|
mainColor = Colors.red;
|
||||||
|
} else if (widget.severity["yellow"] is int && fixedValue >= widget.severity["yellow"]) {
|
||||||
|
mainColor = Colors.amber;
|
||||||
|
} else {
|
||||||
|
mainColor = Colors.green;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mainColor = Colors.green;
|
||||||
|
}
|
||||||
|
final data = [
|
||||||
|
GaugeSegment('Main', toShow, mainColor),
|
||||||
|
GaugeSegment('Rest', 100 - toShow, Colors.black45),
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
charts.Series<GaugeSegment, String>(
|
||||||
|
id: 'Segments',
|
||||||
|
domainFn: (GaugeSegment segment, _) => segment.segment,
|
||||||
|
measureFn: (GaugeSegment segment, _) => segment.value,
|
||||||
|
colorFn: (GaugeSegment segment, _) => segment.color,
|
||||||
|
// Set a label accessor to control the text of the arc label.
|
||||||
|
labelAccessorFn: (GaugeSegment segment, _) =>
|
||||||
|
segment.segment == 'Main' ? '${segment.value}' : null,
|
||||||
|
data: data,
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||||
|
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => entityWrapper.handleTap(),
|
||||||
|
onLongPress: () => entityWrapper.handleHold(),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 1.5,
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
overflow: Overflow.clip,
|
||||||
|
children: [
|
||||||
|
LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
double verticalOffset;
|
||||||
|
if(constraints.maxWidth > 150.0) {
|
||||||
|
verticalOffset = 0.2;
|
||||||
|
} else if (constraints.maxWidth > 100.0) {
|
||||||
|
verticalOffset = 0.3;
|
||||||
|
} else {
|
||||||
|
verticalOffset = 0.3;
|
||||||
|
}
|
||||||
|
return FractionallySizedBox(
|
||||||
|
heightFactor: 2,
|
||||||
|
widthFactor: 1,
|
||||||
|
alignment: FractionalOffset(0,verticalOffset),
|
||||||
|
child: charts.PieChart(
|
||||||
|
_createData(entityWrapper.entity.doubleState),
|
||||||
|
animate: false,
|
||||||
|
defaultRenderer: charts.ArcRendererConfig(
|
||||||
|
arcRatio: 0.4,
|
||||||
|
startAngle: pi,
|
||||||
|
arcLength: pi,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
double fontSize = constraints.maxHeight / 7;
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 2*fontSize),
|
||||||
|
child: SimpleEntityState(
|
||||||
|
//textAlign: TextAlign.center,
|
||||||
|
expanded: false,
|
||||||
|
maxLines: 1,
|
||||||
|
bold: true,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
fontSize: fontSize,
|
||||||
|
//padding: EdgeInsets.only(top: Sizes.rowPadding),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
double fontSize = constraints.maxHeight / 7;
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: fontSize),
|
||||||
|
child: EntityName(
|
||||||
|
fontSize: fontSize,
|
||||||
|
maxLines: 1,
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
textOverflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GaugeSegment {
|
||||||
|
final String segment;
|
||||||
|
final double value;
|
||||||
|
final charts.Color color;
|
||||||
|
|
||||||
|
GaugeSegment(this.segment, this.value, Color color)
|
||||||
|
: this.color = charts.Color(
|
||||||
|
r: color.red, g: color.green, b: color.blue, a: color.alpha);
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class GlanceEntityContainer extends StatelessWidget {
|
class GlanceCardEntityContainer extends StatelessWidget {
|
||||||
|
|
||||||
final bool showName;
|
final bool showName;
|
||||||
final bool showState;
|
final bool showState;
|
||||||
@ -9,7 +9,7 @@ class GlanceEntityContainer extends StatelessWidget {
|
|||||||
final double nameFontSize;
|
final double nameFontSize;
|
||||||
final bool wordsWrapInName;
|
final bool wordsWrapInName;
|
||||||
|
|
||||||
GlanceEntityContainer({
|
GlanceCardEntityContainer({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.showName,
|
@required this.showName,
|
||||||
@required this.showState,
|
@required this.showState,
|
||||||
@ -39,10 +39,10 @@ class GlanceEntityContainer extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.add(
|
result.add(
|
||||||
EntityIcon(
|
EntityIcon(
|
||||||
padding: EdgeInsets.all(0.0),
|
padding: EdgeInsets.all(0.0),
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if (!nameInTheBottom) {
|
if (!nameInTheBottom) {
|
||||||
if (showState) {
|
if (showState) {
|
||||||
@ -54,14 +54,9 @@ class GlanceEntityContainer extends StatelessWidget {
|
|||||||
|
|
||||||
return Center(
|
return Center(
|
||||||
child: InkResponse(
|
child: InkResponse(
|
||||||
child: ConstrainedBox(
|
child: Column(
|
||||||
constraints: BoxConstraints(minWidth: Sizes.iconSize * 2),
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Column(
|
children: result,
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
//mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
//crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: result,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
onTap: () => entityWrapper.handleTap(),
|
onTap: () => entityWrapper.handleTap(),
|
||||||
onLongPress: () => entityWrapper.handleHold(),
|
onLongPress: () => entityWrapper.handleHold(),
|
90
lib/cards/widgets/light_card_body.dart
Normal file
90
lib/cards/widgets/light_card_body.dart
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class LightCardBody extends StatefulWidget {
|
||||||
|
|
||||||
|
final int min;
|
||||||
|
final int max;
|
||||||
|
final Map severity;
|
||||||
|
|
||||||
|
LightCardBody({Key key, this.min, this.max, this.severity}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LightCardBodyState createState() => _LightCardBodyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LightCardBodyState extends State<LightCardBody> {
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||||
|
LightEntity entity = entityWrapper.entity;
|
||||||
|
Logger.d("Light brightness: ${entity.brightness}");
|
||||||
|
|
||||||
|
return FractionallySizedBox(
|
||||||
|
widthFactor: 0.5,
|
||||||
|
child: Container(
|
||||||
|
//color: Colors.redAccent,
|
||||||
|
child: SingleCircularSlider(
|
||||||
|
255,
|
||||||
|
entity.brightness ?? 0,
|
||||||
|
baseColor: Colors.white,
|
||||||
|
handlerColor: Colors.blue[200],
|
||||||
|
selectionColor: Colors.blue[100],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => entityWrapper.handleTap(),
|
||||||
|
onLongPress: () => entityWrapper.handleHold(),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 1.5,
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
overflow: Overflow.clip,
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
double fontSize = constraints.maxHeight / 7;
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 2*fontSize),
|
||||||
|
child: SimpleEntityState(
|
||||||
|
//textAlign: TextAlign.center,
|
||||||
|
expanded: false,
|
||||||
|
maxLines: 1,
|
||||||
|
bold: true,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
fontSize: fontSize,
|
||||||
|
//padding: EdgeInsets.only(top: Sizes.rowPadding),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
double fontSize = constraints.maxHeight / 7;
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: fontSize),
|
||||||
|
child: EntityName(
|
||||||
|
fontSize: fontSize,
|
||||||
|
maxLines: 1,
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
textOverflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,343 +0,0 @@
|
|||||||
part of 'main.dart';
|
|
||||||
|
|
||||||
class Connection {
|
|
||||||
|
|
||||||
static final Connection _instance = Connection._internal();
|
|
||||||
|
|
||||||
factory Connection() {
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
Connection._internal();
|
|
||||||
|
|
||||||
String displayHostname;
|
|
||||||
String _webSocketAPIEndpoint;
|
|
||||||
String httpWebHost;
|
|
||||||
String _token;
|
|
||||||
String _tempToken;
|
|
||||||
String oauthUrl;
|
|
||||||
bool get isAuthenticated => _token != null;
|
|
||||||
StreamSubscription _socketSubscription;
|
|
||||||
Duration connectTimeout = Duration(seconds: 15);
|
|
||||||
|
|
||||||
bool isConnected = false;
|
|
||||||
|
|
||||||
var onStateChangeCallback;
|
|
||||||
|
|
||||||
IOWebSocketChannel _socket;
|
|
||||||
|
|
||||||
int _currentMessageId = 0;
|
|
||||||
Map<String, Completer> _messageResolver = {};
|
|
||||||
|
|
||||||
Future init(onStateChange) async {
|
|
||||||
Completer completer = Completer();
|
|
||||||
onStateChangeCallback = onStateChange;
|
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
||||||
String domain = prefs.getString('hassio-domain');
|
|
||||||
String port = prefs.getString('hassio-port');
|
|
||||||
displayHostname = "$domain:$port";
|
|
||||||
_webSocketAPIEndpoint = "${prefs.getString('hassio-protocol')}://$domain:$port/api/websocket";
|
|
||||||
httpWebHost = "${prefs.getString('hassio-res-protocol')}://$domain:$port";
|
|
||||||
//_token = prefs.getString('hassio-token');
|
|
||||||
final storage = new FlutterSecureStorage();
|
|
||||||
try {
|
|
||||||
_token = await storage.read(key: "hacl_llt");
|
|
||||||
} catch (e) {
|
|
||||||
Logger.e("Cannt read secure storage. Need to relogin.");
|
|
||||||
_token = null;
|
|
||||||
await storage.delete(key: "hacl_llt");
|
|
||||||
}
|
|
||||||
if ((domain == null) || (port == null) ||
|
|
||||||
(domain.length == 0) || (port.length == 0)) {
|
|
||||||
completer.completeError({"errorCode": 5, "errorMessage": "Check connection settings"});
|
|
||||||
} else {
|
|
||||||
oauthUrl = "$httpWebHost/auth/authorize?client_id=${Uri.encodeComponent('http://ha-client.homemade.systems/')}&redirect_uri=${Uri.encodeComponent('http://ha-client.homemade.systems/service/auth_callback.html')}";
|
|
||||||
if (_token == null) {
|
|
||||||
await AuthManager().getTempToken(
|
|
||||||
httpWebHost: httpWebHost,
|
|
||||||
oauthUrl: oauthUrl
|
|
||||||
).then((token) {
|
|
||||||
Logger.d("Token from AuthManager recived");
|
|
||||||
_tempToken = token;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_connect().timeout(connectTimeout, onTimeout: () {
|
|
||||||
_disconnect().then((_) {
|
|
||||||
completer.completeError(
|
|
||||||
{"errorCode": 1, "errorMessage": "Connection timeout"});
|
|
||||||
});
|
|
||||||
}).then((_) => completer.complete()).catchError((e) {
|
|
||||||
completer.completeError(e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
Completer connecting;
|
|
||||||
|
|
||||||
Future _connect() async {
|
|
||||||
if (connecting != null && !connecting.isCompleted) {
|
|
||||||
Logger.w("");
|
|
||||||
return connecting.future;
|
|
||||||
}
|
|
||||||
connecting = Completer();
|
|
||||||
await _disconnect();
|
|
||||||
Logger.d( "Socket connecting...");
|
|
||||||
_socket = IOWebSocketChannel.connect(
|
|
||||||
_webSocketAPIEndpoint, pingInterval: Duration(seconds: 15));
|
|
||||||
_socketSubscription = _socket.stream.listen(
|
|
||||||
(message) {
|
|
||||||
isConnected = true;
|
|
||||||
var data = json.decode(message);
|
|
||||||
if (data["type"] == "auth_required") {
|
|
||||||
Logger.d("[Received] <== ${data.toString()}");
|
|
||||||
_authenticate().then((_) => connecting.complete()).catchError((e) {
|
|
||||||
if (!connecting.isCompleted) connecting.completeError(e);
|
|
||||||
});
|
|
||||||
} else if (data["type"] == "auth_ok") {
|
|
||||||
Logger.d("[Received] <== ${data.toString()}");
|
|
||||||
_messageResolver["auth"]?.complete();
|
|
||||||
_messageResolver.remove("auth");
|
|
||||||
if (!connecting.isCompleted) connecting.complete(sendSocketMessage(
|
|
||||||
type: "subscribe_events",
|
|
||||||
additionalData: {"event_type": "state_changed"},
|
|
||||||
));
|
|
||||||
} else if (data["type"] == "auth_invalid") {
|
|
||||||
Logger.d("[Received] <== ${data.toString()}");
|
|
||||||
_messageResolver["auth"]?.completeError({"errorCode": 62, "errorMessage": "${data["message"]}"});
|
|
||||||
_messageResolver.remove("auth");
|
|
||||||
logout().then((_) {
|
|
||||||
if (!connecting.isCompleted) connecting.completeError({"errorCode": 62, "errorMessage": "${data["message"]}"});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
_handleMessage(data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cancelOnError: true,
|
|
||||||
onDone: () => _handleSocketClose(connecting),
|
|
||||||
onError: (e) => _handleSocketError(e, connecting)
|
|
||||||
);
|
|
||||||
return connecting.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future _disconnect() async {
|
|
||||||
Logger.d( "Socket disconnecting...");
|
|
||||||
await _socketSubscription?.cancel();
|
|
||||||
await _socket?.sink?.close()?.timeout(Duration(seconds: 4),
|
|
||||||
onTimeout: () => Logger.d( "Socket sink close timeout")
|
|
||||||
);
|
|
||||||
Logger.d( "..Disconnected");
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleMessage(data) {
|
|
||||||
if (data["type"] == "result") {
|
|
||||||
if (data["id"] != null && data["success"]) {
|
|
||||||
Logger.d("[Received] <== Request id ${data['id']} was successful");
|
|
||||||
_messageResolver["${data["id"]}"]?.complete(data["result"]);
|
|
||||||
} else if (data["id"] != null) {
|
|
||||||
Logger.e("[Received] <== Error received on request id ${data['id']}: ${data['error']}");
|
|
||||||
_messageResolver["${data["id"]}"]?.completeError({"errorMessage": "${data['error']["message"]}"});
|
|
||||||
}
|
|
||||||
_messageResolver.remove("${data["id"]}");
|
|
||||||
} else if (data["type"] == "event") {
|
|
||||||
if ((data["event"] != null) && (data["event"]["event_type"] == "state_changed")) {
|
|
||||||
Logger.d("[Received] <== ${data['type']}.${data["event"]["event_type"]}: ${data["event"]["data"]["entity_id"]}");
|
|
||||||
onStateChangeCallback(data["event"]["data"]);
|
|
||||||
} else if (data["event"] != null) {
|
|
||||||
Logger.w("Unhandled event type: ${data["event"]["event_type"]}");
|
|
||||||
} else {
|
|
||||||
Logger.e("Event is null: $data");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Logger.d("[Received unhandled] <== ${data.toString()}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleSocketClose(Completer connectionCompleter) {
|
|
||||||
isConnected = false;
|
|
||||||
Logger.d("Socket disconnected.");
|
|
||||||
if (!connectionCompleter.isCompleted) {
|
|
||||||
connectionCompleter.completeError({"errorCode": 82, "errorMessage": "Disconnected"});
|
|
||||||
} else {
|
|
||||||
_disconnect().then((_) {
|
|
||||||
Timer(Duration(seconds: 5), () {
|
|
||||||
Logger.d("Trying to reconnect...");
|
|
||||||
_connect();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleSocketError(e, Completer connectionCompleter) {
|
|
||||||
isConnected = false;
|
|
||||||
Logger.e("Socket stream Error: $e");
|
|
||||||
if (!connectionCompleter.isCompleted) {
|
|
||||||
connectionCompleter.completeError({"errorCode": 81, "errorMessage": "Unable to connect to Home Assistant"});
|
|
||||||
} else {
|
|
||||||
_disconnect().then((_) {
|
|
||||||
Timer(Duration(seconds: 5), () {
|
|
||||||
Logger.d("Trying to reconnect...");
|
|
||||||
_connect();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future _authenticate() {
|
|
||||||
Completer completer = Completer();
|
|
||||||
if (_token != null) {
|
|
||||||
Logger.d( "Long-lived token exist");
|
|
||||||
Logger.d( "[Sending] ==> auth request");
|
|
||||||
sendSocketMessage(
|
|
||||||
type: "auth",
|
|
||||||
additionalData: {"access_token": "$_token"},
|
|
||||||
auth: true
|
|
||||||
).then((_) {
|
|
||||||
completer.complete();
|
|
||||||
}).catchError((e) => completer.completeError(e));
|
|
||||||
} else if (_tempToken != null) {
|
|
||||||
Logger.d("We have temp token. Loging in...");
|
|
||||||
sendSocketMessage(
|
|
||||||
type: "auth",
|
|
||||||
additionalData: {"access_token": "$_tempToken"},
|
|
||||||
auth: true
|
|
||||||
).then((_) {
|
|
||||||
Logger.d("Requesting long-lived token...");
|
|
||||||
_getLongLivedToken().then((_) {
|
|
||||||
completer.complete();
|
|
||||||
}).catchError((e) {
|
|
||||||
Logger.e("Can't get long-lived token: $e");
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
}).catchError((e) => completer.completeError(e));
|
|
||||||
} else {
|
|
||||||
completer.completeError({"errorCode": 63, "errorMessage": "General login error"});
|
|
||||||
}
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future logout() {
|
|
||||||
_token = null;
|
|
||||||
_tempToken = null;
|
|
||||||
final storage = new FlutterSecureStorage();
|
|
||||||
return storage.delete(key: "hacl_llt");
|
|
||||||
}
|
|
||||||
|
|
||||||
Future _getLongLivedToken() {
|
|
||||||
Completer completer = Completer();
|
|
||||||
sendSocketMessage(type: "auth/long_lived_access_token", additionalData: {"client_name": "HA Client app ${DateTime.now().millisecondsSinceEpoch}", "lifespan": 365}).then((data) {
|
|
||||||
Logger.d("Got long-lived token.");
|
|
||||||
_token = data;
|
|
||||||
_tempToken = null;
|
|
||||||
final storage = new FlutterSecureStorage();
|
|
||||||
storage.write(key: "hacl_llt", value: "$_token").then((_) {
|
|
||||||
completer.complete();
|
|
||||||
}).catchError((e) {
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
}).catchError((e) {
|
|
||||||
logout();
|
|
||||||
completer.completeError({"errorCode": 63, "errorMessage": "Authentication error: $e"});
|
|
||||||
});
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future sendSocketMessage({String type, Map additionalData, bool auth: false}) {
|
|
||||||
Completer _completer = Completer();
|
|
||||||
Map dataObject = {"type": "$type"};
|
|
||||||
String callbackName;
|
|
||||||
if (!auth) {
|
|
||||||
_incrementMessageId();
|
|
||||||
dataObject["id"] = _currentMessageId;
|
|
||||||
callbackName = "$_currentMessageId";
|
|
||||||
} else {
|
|
||||||
callbackName = "auth";
|
|
||||||
}
|
|
||||||
if (additionalData != null) {
|
|
||||||
dataObject.addAll(additionalData);
|
|
||||||
}
|
|
||||||
_messageResolver[callbackName] = _completer;
|
|
||||||
String rawMessage = json.encode(dataObject);
|
|
||||||
Logger.d("[Sending] ==> $rawMessage");
|
|
||||||
if (!isConnected) {
|
|
||||||
_connect().timeout(connectTimeout, onTimeout: (){
|
|
||||||
_completer.completeError({"errorCode": 8, "errorMessage": "No connection to Home Assistant"});
|
|
||||||
}).then((_) {
|
|
||||||
_socket.sink.add(rawMessage);
|
|
||||||
}).catchError((e) {
|
|
||||||
_completer.completeError(e);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
_socket.sink.add(rawMessage);
|
|
||||||
}
|
|
||||||
return _completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _incrementMessageId() {
|
|
||||||
_currentMessageId += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future callService({String domain, String service, String entityId, Map additionalServiceData}) {
|
|
||||||
Map serviceData = {};
|
|
||||||
if (entityId != null) {
|
|
||||||
serviceData["entity_id"] = entityId;
|
|
||||||
}
|
|
||||||
if (additionalServiceData != null && additionalServiceData.isNotEmpty) {
|
|
||||||
serviceData.addAll(additionalServiceData);
|
|
||||||
}
|
|
||||||
if (serviceData.isNotEmpty)
|
|
||||||
return sendSocketMessage(type: "call_service", additionalData: {"domain": domain, "service": service, "service_data": serviceData});
|
|
||||||
else
|
|
||||||
return sendSocketMessage(type: "call_service", additionalData: {"domain": domain, "service": service});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List> getHistory(String entityId) async {
|
|
||||||
DateTime now = DateTime.now();
|
|
||||||
//String endTime = formatDate(now, [yyyy, '-', mm, '-', dd, 'T', HH, ':', nn, ':', ss, z]);
|
|
||||||
String startTime = formatDate(now.subtract(Duration(hours: 24)), [yyyy, '-', mm, '-', dd, 'T', HH, ':', nn, ':', ss, z]);
|
|
||||||
String url = "$httpWebHost/api/history/period/$startTime?&filter_entity_id=$entityId";
|
|
||||||
Logger.d("[Sending] ==> $url");
|
|
||||||
http.Response historyResponse;
|
|
||||||
historyResponse = await http.get(url, headers: {
|
|
||||||
"authorization": "Bearer $_token",
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
});
|
|
||||||
var history = json.decode(historyResponse.body);
|
|
||||||
if (history is List) {
|
|
||||||
Logger.d( "[Received] <== ${history.first.length} history recors");
|
|
||||||
return history;
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future sendHTTPPost({String host, String endPoint, String data, String contentType: "application/json", bool includeAuthHeader: true, String authToken}) async {
|
|
||||||
Completer completer = Completer();
|
|
||||||
String url = "$host$endPoint";
|
|
||||||
Logger.d("[Sending] ==> $url");
|
|
||||||
Map<String, String> headers = {};
|
|
||||||
if (contentType != null) {
|
|
||||||
headers["Content-Type"] = contentType;
|
|
||||||
}
|
|
||||||
if (includeAuthHeader) {
|
|
||||||
headers["authorization"] = "Bearer $authToken";
|
|
||||||
}
|
|
||||||
http.post(
|
|
||||||
url,
|
|
||||||
headers: headers,
|
|
||||||
body: data
|
|
||||||
).then((response) {
|
|
||||||
Logger.d("[Received] <== ${response.statusCode}, ${response.body}");
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
completer.complete(response.body);
|
|
||||||
} else {
|
|
||||||
completer.completeError({"code": response.statusCode, "message": "${response.body}"});
|
|
||||||
}
|
|
||||||
}).catchError((e) {
|
|
||||||
completer.completeError(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of 'main.dart';
|
||||||
|
|
||||||
class EntityState {
|
class EntityState {
|
||||||
static const on = 'on';
|
static const on = 'on';
|
||||||
@ -29,6 +29,11 @@ class EntityState {
|
|||||||
static const ok = 'ok';
|
static const ok = 'ok';
|
||||||
static const problem = 'problem';
|
static const problem = 'problem';
|
||||||
static const active = 'active';
|
static const active = 'active';
|
||||||
|
static const cleaning = 'cleaning';
|
||||||
|
static const docked = 'docked';
|
||||||
|
static const returning = 'returning';
|
||||||
|
static const error = 'error';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class EntityUIAction {
|
class EntityUIAction {
|
||||||
@ -77,23 +82,44 @@ class EntityUIAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CardType {
|
class CardType {
|
||||||
static const horizontalStack = "horizontal-stack";
|
static const HORIZONTAL_STACK = "horizontal-stack";
|
||||||
static const verticalStack = "vertical-stack";
|
static const VERTICAL_STACK = "vertical-stack";
|
||||||
static const entities = "entities";
|
static const ENTITIES = "entities";
|
||||||
static const glance = "glance";
|
static const GLANCE = "glance";
|
||||||
static const mediaControl = "media-control";
|
static const MEDIA_CONTROL = "media-control";
|
||||||
static const weatherForecast = "weather-forecast";
|
static const WEATHER_FORECAST = "weather-forecast";
|
||||||
static const thermostat = "thermostat";
|
static const THERMOSTAT = "thermostat";
|
||||||
static const sensor = "sensor";
|
static const SENSOR = "sensor";
|
||||||
static const plantStatus = "plant-status";
|
static const PLANT_STATUS = "plant-status";
|
||||||
static const pictureEntity = "picture-entity";
|
static const PICTURE_ENTITY = "picture-entity";
|
||||||
static const pictureElements = "picture-elements";
|
static const PICTURE_ELEMENTS = "picture-elements";
|
||||||
static const picture = "picture";
|
static const PICTURE = "picture";
|
||||||
static const map = "map";
|
static const MAP = "map";
|
||||||
static const iframe = "iframe";
|
static const IFRAME = "iframe";
|
||||||
static const gauge = "gauge";
|
static const GAUGE = "gauge";
|
||||||
static const entityButton = "entity-button";
|
static const ENTITY_BUTTON = "entity-button";
|
||||||
static const conditional = "conditional";
|
static const CONDITIONAL = "conditional";
|
||||||
static const alarmPanel = "alarm-panel";
|
static const ALARM_PANEL = "alarm-panel";
|
||||||
static const markdown = "markdown";
|
static const MARKDOWN = "markdown";
|
||||||
|
static const LIGHT = "light";
|
||||||
|
}
|
||||||
|
|
||||||
|
class Sizes {
|
||||||
|
static const rightWidgetPadding = 10.0;
|
||||||
|
static const leftWidgetPadding = 10.0;
|
||||||
|
static const buttonPadding = 4.0;
|
||||||
|
static const extendedWidgetHeight = 50.0;
|
||||||
|
static const iconSize = 28.0;
|
||||||
|
static const largeIconSize = 46.0;
|
||||||
|
static const stateFontSize = 15.0;
|
||||||
|
static const nameFontSize = 15.0;
|
||||||
|
static const smallFontSize = 14.0;
|
||||||
|
static const largeFontSize = 24.0;
|
||||||
|
static const inputWidth = 160.0;
|
||||||
|
static const rowPadding = 10.0;
|
||||||
|
static const doubleRowPadding = rowPadding*2;
|
||||||
|
static const minViewColumnWidth = 350;
|
||||||
|
static const entityPageMaxWidth = 400.0;
|
||||||
|
static const mainPageScreenSeparatorWidth = 5.0;
|
||||||
|
static const tabletMinWidth = minViewColumnWidth + entityPageMaxWidth + 5;
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class AlarmControlPanelEntity extends Entity {
|
class AlarmControlPanelEntity extends Entity {
|
||||||
AlarmControlPanelEntity(Map rawData, String webHost) : super(rawData, webHost);
|
AlarmControlPanelEntity(Map rawData, String webHost) : super(rawData, webHost);
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class AlarmControlPanelControlsWidget extends StatefulWidget {
|
class AlarmControlPanelControlsWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -25,9 +25,12 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
|
|||||||
|
|
||||||
|
|
||||||
void _callService(AlarmControlPanelEntity entity, String service) {
|
void _callService(AlarmControlPanelEntity entity, String service) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, service, entity.entityId,
|
entity.domain,
|
||||||
{"code": "$code"}));
|
service,
|
||||||
|
entity.entityId,
|
||||||
|
{"code": "$code"}
|
||||||
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
code = "";
|
code = "";
|
||||||
});
|
});
|
||||||
@ -58,7 +61,12 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
|
|||||||
FlatButton(
|
FlatButton(
|
||||||
child: new Text("Yes"),
|
child: new Text("Yes"),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "alarm_trigger", entity.entityId, null));
|
ConnectionManager().callService(
|
||||||
|
entity.domain,
|
||||||
|
"alarm_trigger",
|
||||||
|
entity.entityId,
|
||||||
|
null
|
||||||
|
);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class AutomationEntity extends Entity {
|
class AutomationEntity extends Entity {
|
||||||
AutomationEntity(Map rawData, String webHost) : super(rawData, webHost);
|
AutomationEntity(Map rawData, String webHost) : super(rawData, webHost);
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../main.dart';
|
||||||
|
|
||||||
class BadgeWidget extends StatelessWidget {
|
class BadgeWidget extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
@ -35,25 +35,36 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "device_tracker":
|
case "device_tracker":
|
||||||
|
case "person":
|
||||||
{
|
{
|
||||||
badgeIcon = EntityIcon(
|
badgeIcon = EntityIcon(
|
||||||
padding: EdgeInsets.all(0.0),
|
padding: EdgeInsets.all(0.0),
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
color: Colors.black
|
color: Colors.black
|
||||||
);
|
);
|
||||||
onBadgeTextValue = entityModel.entityWrapper.entity.state;
|
onBadgeTextValue = entityModel.entityWrapper.entity.displayState;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
onBadgeTextValue = entityModel.entityWrapper.entity.unitOfMeasurement;
|
double stateFontSize;
|
||||||
|
if (entityModel.entityWrapper.entity.displayState.length <= 3) {
|
||||||
|
stateFontSize = 18.0;
|
||||||
|
} else if (entityModel.entityWrapper.entity.displayState.length <= 4) {
|
||||||
|
stateFontSize = 15.0;
|
||||||
|
} else if (entityModel.entityWrapper.entity.displayState.length <= 6) {
|
||||||
|
stateFontSize = 10.0;
|
||||||
|
} else if (entityModel.entityWrapper.entity.displayState.length <= 10) {
|
||||||
|
stateFontSize = 8.0;
|
||||||
|
}
|
||||||
|
onBadgeTextValue = entityModel.entityWrapper.unitOfMeasurement;
|
||||||
badgeIcon = Center(
|
badgeIcon = Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
"${entityModel.entityWrapper.entity.state}",
|
"${entityModel.entityWrapper.entity.displayState}",
|
||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.fade,
|
||||||
softWrap: false,
|
softWrap: false,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(fontSize: 17.0),
|
style: TextStyle(fontSize: stateFontSize),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@ -129,6 +140,6 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
eventBus.fire(new ShowEntityPageEvent(entityModel.entityWrapper.entity)));
|
eventBus.fire(new ShowEntityPageEvent(entity: entityModel.entityWrapper.entity)));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class ButtonEntity extends Entity {
|
class ButtonEntity extends Entity {
|
||||||
ButtonEntity(Map rawData, String webHost) : super(rawData, webHost);
|
ButtonEntity(Map rawData, String webHost) : super(rawData, webHost);
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class CameraEntity extends Entity {
|
class CameraEntity extends Entity {
|
||||||
|
|
58
lib/entities/camera/widgets/camera_stream_view.dart
Normal file
58
lib/entities/camera/widgets/camera_stream_view.dart
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
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;
|
||||||
|
bool started = false;
|
||||||
|
String streamUrl = "";
|
||||||
|
|
||||||
|
launchStream() {
|
||||||
|
Launcher.launchURLInCustomTab(
|
||||||
|
context: context,
|
||||||
|
url: streamUrl
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (!started) {
|
||||||
|
_entity = EntityModel
|
||||||
|
.of(context)
|
||||||
|
.entityWrapper
|
||||||
|
.entity;
|
||||||
|
started = true;
|
||||||
|
}
|
||||||
|
streamUrl = '${ConnectionManager().httpWebHost}/api/camera_proxy_stream/${_entity
|
||||||
|
.entityId}?token=${_entity.attributes['access_token']}';
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:monitor-screenshot"), color: Colors.amber),
|
||||||
|
iconSize: 50.0,
|
||||||
|
onPressed: () => launchStream(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class ClimateEntity extends Entity {
|
class ClimateEntity extends Entity {
|
||||||
|
|
||||||
@ -10,71 +10,57 @@ class ClimateEntity extends Entity {
|
|||||||
);
|
);
|
||||||
|
|
||||||
static const SUPPORT_TARGET_TEMPERATURE = 1;
|
static const SUPPORT_TARGET_TEMPERATURE = 1;
|
||||||
static const SUPPORT_TARGET_TEMPERATURE_HIGH = 2;
|
static const SUPPORT_TARGET_TEMPERATURE_RANGE = 2;
|
||||||
static const SUPPORT_TARGET_TEMPERATURE_LOW = 4;
|
static const SUPPORT_TARGET_HUMIDITY = 4;
|
||||||
static const SUPPORT_TARGET_HUMIDITY = 8;
|
static const SUPPORT_FAN_MODE = 8;
|
||||||
static const SUPPORT_TARGET_HUMIDITY_HIGH = 16;
|
static const SUPPORT_PRESET_MODE = 16;
|
||||||
static const SUPPORT_TARGET_HUMIDITY_LOW = 32;
|
static const SUPPORT_SWING_MODE = 32;
|
||||||
static const SUPPORT_FAN_MODE = 64;
|
static const SUPPORT_AUX_HEAT = 64;
|
||||||
static const SUPPORT_OPERATION_MODE = 128;
|
|
||||||
static const SUPPORT_HOLD_MODE = 256;
|
|
||||||
static const SUPPORT_SWING_MODE = 512;
|
//static const SUPPORT_OPERATION_MODE = 16;
|
||||||
static const SUPPORT_AWAY_MODE = 1024;
|
//static const SUPPORT_HOLD_MODE = 256;
|
||||||
static const SUPPORT_AUX_HEAT = 2048;
|
//static const SUPPORT_AWAY_MODE = 1024;
|
||||||
static const SUPPORT_ON_OFF = 4096;
|
//static const SUPPORT_ON_OFF = 4096;
|
||||||
|
|
||||||
ClimateEntity(Map rawData, String webHost) : super(rawData, webHost);
|
ClimateEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
bool get supportTargetTemperature => ((supportedFeatures &
|
bool get supportTargetTemperature => ((supportedFeatures &
|
||||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE) ==
|
ClimateEntity.SUPPORT_TARGET_TEMPERATURE) ==
|
||||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE);
|
ClimateEntity.SUPPORT_TARGET_TEMPERATURE);
|
||||||
bool get supportTargetTemperatureHigh => ((supportedFeatures &
|
bool get supportTargetTemperatureRange => ((supportedFeatures &
|
||||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH) ==
|
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_RANGE) ==
|
||||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH);
|
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_RANGE);
|
||||||
bool get supportTargetTemperatureLow => ((supportedFeatures &
|
|
||||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW) ==
|
|
||||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW);
|
|
||||||
bool get supportTargetHumidity => ((supportedFeatures &
|
bool get supportTargetHumidity => ((supportedFeatures &
|
||||||
ClimateEntity.SUPPORT_TARGET_HUMIDITY) ==
|
ClimateEntity.SUPPORT_TARGET_HUMIDITY) ==
|
||||||
ClimateEntity.SUPPORT_TARGET_HUMIDITY);
|
ClimateEntity.SUPPORT_TARGET_HUMIDITY);
|
||||||
bool get supportTargetHumidityHigh => ((supportedFeatures &
|
|
||||||
ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH) ==
|
|
||||||
ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH);
|
|
||||||
bool get supportTargetHumidityLow => ((supportedFeatures &
|
|
||||||
ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW) ==
|
|
||||||
ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW);
|
|
||||||
bool get supportFanMode =>
|
bool get supportFanMode =>
|
||||||
((supportedFeatures & ClimateEntity.SUPPORT_FAN_MODE) ==
|
((supportedFeatures & ClimateEntity.SUPPORT_FAN_MODE) ==
|
||||||
ClimateEntity.SUPPORT_FAN_MODE);
|
ClimateEntity.SUPPORT_FAN_MODE);
|
||||||
bool get supportOperationMode => ((supportedFeatures &
|
|
||||||
ClimateEntity.SUPPORT_OPERATION_MODE) ==
|
|
||||||
ClimateEntity.SUPPORT_OPERATION_MODE);
|
|
||||||
bool get supportHoldMode =>
|
|
||||||
((supportedFeatures & ClimateEntity.SUPPORT_HOLD_MODE) ==
|
|
||||||
ClimateEntity.SUPPORT_HOLD_MODE);
|
|
||||||
bool get supportSwingMode =>
|
bool get supportSwingMode =>
|
||||||
((supportedFeatures & ClimateEntity.SUPPORT_SWING_MODE) ==
|
((supportedFeatures & ClimateEntity.SUPPORT_SWING_MODE) ==
|
||||||
ClimateEntity.SUPPORT_SWING_MODE);
|
ClimateEntity.SUPPORT_SWING_MODE);
|
||||||
bool get supportAwayMode =>
|
bool get supportPresetMode =>
|
||||||
((supportedFeatures & ClimateEntity.SUPPORT_AWAY_MODE) ==
|
((supportedFeatures & ClimateEntity.SUPPORT_PRESET_MODE) ==
|
||||||
ClimateEntity.SUPPORT_AWAY_MODE);
|
ClimateEntity.SUPPORT_PRESET_MODE);
|
||||||
bool get supportAuxHeat =>
|
bool get supportAuxHeat =>
|
||||||
((supportedFeatures & ClimateEntity.SUPPORT_AUX_HEAT) ==
|
((supportedFeatures & ClimateEntity.SUPPORT_AUX_HEAT) ==
|
||||||
ClimateEntity.SUPPORT_AUX_HEAT);
|
ClimateEntity.SUPPORT_AUX_HEAT);
|
||||||
bool get supportOnOff =>
|
|
||||||
((supportedFeatures & ClimateEntity.SUPPORT_ON_OFF) ==
|
|
||||||
ClimateEntity.SUPPORT_ON_OFF);
|
|
||||||
|
|
||||||
List<String> get operationList => attributes["operation_list"] != null
|
List<String> get hvacModes => attributes["hvac_modes"] != null
|
||||||
? (attributes["operation_list"] as List).cast<String>()
|
? (attributes["hvac_modes"] as List).cast<String>()
|
||||||
: null;
|
: null;
|
||||||
List<String> get fanList => attributes["fan_list"] != null
|
List<String> get fanModes => attributes["fan_modes"] != null
|
||||||
? (attributes["fan_list"] as List).cast<String>()
|
? (attributes["fan_modes"] as List).cast<String>()
|
||||||
: null;
|
: null;
|
||||||
List<String> get swingList => attributes["swing_list"] != null
|
List<String> get presetModes => attributes["preset_modes"] != null
|
||||||
? (attributes["swing_list"] as List).cast<String>()
|
? (attributes["preset_modes"] as List).cast<String>()
|
||||||
|
: null;
|
||||||
|
List<String> get swingModes => attributes["swing_modes"] != null
|
||||||
|
? (attributes["swing_modes"] as List).cast<String>()
|
||||||
: null;
|
: null;
|
||||||
double get temperature => _getDoubleAttributeValue('temperature');
|
double get temperature => _getDoubleAttributeValue('temperature');
|
||||||
|
double get currentTemperature => _getDoubleAttributeValue('current_temperature');
|
||||||
double get targetHigh => _getDoubleAttributeValue('target_temp_high');
|
double get targetHigh => _getDoubleAttributeValue('target_temp_high');
|
||||||
double get targetLow => _getDoubleAttributeValue('target_temp_low');
|
double get targetLow => _getDoubleAttributeValue('target_temp_low');
|
||||||
double get maxTemp => _getDoubleAttributeValue('max_temp') ?? 100.0;
|
double get maxTemp => _getDoubleAttributeValue('max_temp') ?? 100.0;
|
||||||
@ -83,11 +69,12 @@ class ClimateEntity extends Entity {
|
|||||||
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;
|
double get temperatureStep => _getDoubleAttributeValue('target_temp_step') ?? 0.5;
|
||||||
String get operationMode => attributes['operation_mode'];
|
String get hvacAction => attributes['hvac_action'];
|
||||||
String get fanMode => attributes['fan_mode'];
|
String get fanMode => attributes['fan_mode'];
|
||||||
|
String get presetMode => attributes['preset_mode'];
|
||||||
String get swingMode => attributes['swing_mode'];
|
String get swingMode => attributes['swing_mode'];
|
||||||
bool get awayMode => attributes['away_mode'] == "on";
|
bool get awayMode => attributes['away_mode'] == "on";
|
||||||
bool get isOff => state == EntityState.off;
|
//bool get isOff => state == EntityState.off;
|
||||||
bool get auxHeat => attributes['aux_heat'] == "on";
|
bool get auxHeat => attributes['aux_heat'] == "on";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -96,10 +83,8 @@ class ClimateEntity extends Entity {
|
|||||||
if (supportTargetTemperature) {
|
if (supportTargetTemperature) {
|
||||||
historyConfig.numericAttributesToShow.add("temperature");
|
historyConfig.numericAttributesToShow.add("temperature");
|
||||||
}
|
}
|
||||||
if (supportTargetTemperatureHigh) {
|
if (supportTargetTemperatureRange) {
|
||||||
historyConfig.numericAttributesToShow.add("target_temp_high");
|
historyConfig.numericAttributesToShow.add("target_temp_high");
|
||||||
}
|
|
||||||
if (supportTargetTemperatureLow) {
|
|
||||||
historyConfig.numericAttributesToShow.add("target_temp_low");
|
historyConfig.numericAttributesToShow.add("target_temp_low");
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class ClimateControlWidget extends StatefulWidget {
|
class ClimateControlWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -19,22 +19,22 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
double _tmpTargetLow = 0.0;
|
double _tmpTargetLow = 0.0;
|
||||||
double _tmpTargetHigh = 0.0;
|
double _tmpTargetHigh = 0.0;
|
||||||
double _tmpTargetHumidity = 0.0;
|
double _tmpTargetHumidity = 0.0;
|
||||||
String _tmpOperationMode;
|
String _tmpHVACMode;
|
||||||
String _tmpFanMode;
|
String _tmpFanMode;
|
||||||
String _tmpSwingMode;
|
String _tmpSwingMode;
|
||||||
bool _tmpAwayMode = false;
|
String _tmpPresetMode;
|
||||||
bool _tmpIsOff = false;
|
//bool _tmpIsOff = false;
|
||||||
bool _tmpAuxHeat = false;
|
bool _tmpAuxHeat = false;
|
||||||
|
|
||||||
void _resetVars(ClimateEntity entity) {
|
void _resetVars(ClimateEntity entity) {
|
||||||
_tmpTemperature = entity.temperature;
|
_tmpTemperature = entity.temperature;
|
||||||
_tmpTargetHigh = entity.targetHigh;
|
_tmpTargetHigh = entity.targetHigh;
|
||||||
_tmpTargetLow = entity.targetLow;
|
_tmpTargetLow = entity.targetLow;
|
||||||
_tmpOperationMode = entity.operationMode;
|
_tmpHVACMode = entity.state;
|
||||||
_tmpFanMode = entity.fanMode;
|
_tmpFanMode = entity.fanMode;
|
||||||
_tmpSwingMode = entity.swingMode;
|
_tmpSwingMode = entity.swingMode;
|
||||||
_tmpAwayMode = entity.awayMode;
|
_tmpPresetMode = entity.presetMode;
|
||||||
_tmpIsOff = entity.isOff;
|
//_tmpIsOff = entity.isOff;
|
||||||
_tmpAuxHeat = entity.auxHeat;
|
_tmpAuxHeat = entity.auxHeat;
|
||||||
_tmpTargetHumidity = entity.targetHumidity;
|
_tmpTargetHumidity = entity.targetHumidity;
|
||||||
|
|
||||||
@ -83,7 +83,12 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
_tempThrottleTimer = Timer(Duration(seconds: 2), () {
|
_tempThrottleTimer = Timer(Duration(seconds: 2), () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"temperature": "${_tmpTemperature.toStringAsFixed(1)}"}));
|
ConnectionManager().callService(
|
||||||
|
entity.domain,
|
||||||
|
"set_temperature",
|
||||||
|
entity.entityId,
|
||||||
|
{"temperature": "${_tmpTemperature.toStringAsFixed(1)}"}
|
||||||
|
);
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -101,7 +106,12 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
_targetTempThrottleTimer = Timer(Duration(seconds: 2), () {
|
_targetTempThrottleTimer = Timer(Duration(seconds: 2), () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"target_temp_high": "${_tmpTargetHigh.toStringAsFixed(1)}", "target_temp_low": "${_tmpTargetLow.toStringAsFixed(1)}"}));
|
ConnectionManager().callService(
|
||||||
|
entity.domain,
|
||||||
|
"set_temperature",
|
||||||
|
entity.entityId,
|
||||||
|
{"target_temp_high": "${_tmpTargetHigh.toStringAsFixed(1)}", "target_temp_low": "${_tmpTargetLow.toStringAsFixed(1)}"}
|
||||||
|
);
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -111,16 +121,16 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpTargetHumidity = value.roundToDouble();
|
_tmpTargetHumidity = value.roundToDouble();
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_humidity", entity.entityId,{"humidity": "$_tmpTargetHumidity"}));
|
ConnectionManager().callService(entity.domain, "set_humidity", entity.entityId, {"humidity": "$_tmpTargetHumidity"});
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setOperationMode(ClimateEntity entity, value) {
|
void _setHVACMode(ClimateEntity entity, value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_tmpOperationMode = value;
|
_tmpHVACMode = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_operation_mode", entity.entityId,{"operation_mode": "$_tmpOperationMode"}));
|
ConnectionManager().callService(entity.domain, "set_hvac_mode", entity.entityId, {"hvac_mode": "$_tmpHVACMode"});
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -129,7 +139,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpSwingMode = value;
|
_tmpSwingMode = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_swing_mode", entity.entityId,{"swing_mode": "$_tmpSwingMode"}));
|
ConnectionManager().callService(entity.domain, "set_swing_mode", entity.entityId, {"swing_mode": "$_tmpSwingMode"});
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -138,34 +148,34 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpFanMode = value;
|
_tmpFanMode = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_fan_mode", entity.entityId,{"fan_mode": "$_tmpFanMode"}));
|
ConnectionManager().callService(entity.domain, "set_fan_mode", entity.entityId, {"fan_mode": "$_tmpFanMode"});
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setAwayMode(ClimateEntity entity, value) {
|
void _setPresetMode(ClimateEntity entity, value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_tmpAwayMode = value;
|
_tmpPresetMode = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_away_mode", entity.entityId,{"away_mode": "${_tmpAwayMode ? 'on' : 'off'}"}));
|
ConnectionManager().callService(entity.domain, "set_preset_mode", entity.entityId, {"preset_mode": "$_tmpPresetMode"});
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setOnOf(ClimateEntity entity, value) {
|
/*void _setOnOf(ClimateEntity entity, value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_tmpIsOff = !value;
|
_tmpIsOff = !value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "${_tmpIsOff ? 'turn_off' : 'turn_on'}", entity.entityId, null));
|
eventBus.fire(new ServiceCallEvent(entity.domain, "${_tmpIsOff ? 'turn_off' : 'turn_on'}", entity.entityId, null));
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
}
|
}*/
|
||||||
|
|
||||||
void _setAuxHeat(ClimateEntity entity, value) {
|
void _setAuxHeat(ClimateEntity entity, value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_tmpAuxHeat = value;
|
_tmpAuxHeat = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_aux_heat", entity.entityId, {"aux_heat": "$_tmpAuxHeat"}));
|
ConnectionManager().callService(entity.domain, "set_aux_heat", entity.entityId, {"aux_heat": "$_tmpAuxHeat"});
|
||||||
_resetStateTimer(entity);
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -196,33 +206,34 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildOnOffControl(entity),
|
//_buildOnOffControl(entity),
|
||||||
_buildTemperatureControls(entity),
|
_buildTemperatureControls(entity),
|
||||||
_buildTargetTemperatureControls(entity),
|
_buildTargetTemperatureControls(entity),
|
||||||
_buildHumidityControls(entity),
|
_buildHumidityControls(entity),
|
||||||
_buildOperationControl(entity),
|
_buildOperationControl(entity),
|
||||||
_buildFanControl(entity),
|
_buildFanControl(entity),
|
||||||
_buildSwingControl(entity),
|
_buildSwingControl(entity),
|
||||||
_buildAwayModeControl(entity),
|
_buildPresetModeControl(entity),
|
||||||
_buildAuxHeatControl(entity)
|
_buildAuxHeatControl(entity)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAwayModeControl(ClimateEntity entity) {
|
Widget _buildPresetModeControl(ClimateEntity entity) {
|
||||||
if (entity.supportAwayMode) {
|
if (entity.supportPresetMode) {
|
||||||
return ModeSwitchWidget(
|
return ModeSelectorWidget(
|
||||||
caption: "Away mode",
|
options: entity.presetModes,
|
||||||
onChange: (value) => _setAwayMode(entity, value),
|
onChange: (mode) => _setPresetMode(entity, mode),
|
||||||
value: _tmpAwayMode,
|
caption: "Preset",
|
||||||
|
value: _tmpPresetMode,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Container(height: 0.0, width: 0.0,);
|
return Container(height: 0.0, width: 0.0,);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildOnOffControl(ClimateEntity entity) {
|
/*Widget _buildOnOffControl(ClimateEntity entity) {
|
||||||
if (entity.supportOnOff) {
|
if (entity.supportOnOff) {
|
||||||
return ModeSwitchWidget(
|
return ModeSwitchWidget(
|
||||||
onChange: (value) => _setOnOf(entity, value),
|
onChange: (value) => _setOnOf(entity, value),
|
||||||
@ -232,7 +243,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
} else {
|
} else {
|
||||||
return Container(height: 0.0, width: 0.0,);
|
return Container(height: 0.0, width: 0.0,);
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
Widget _buildAuxHeatControl(ClimateEntity entity) {
|
Widget _buildAuxHeatControl(ClimateEntity entity) {
|
||||||
if (entity.supportAuxHeat ) {
|
if (entity.supportAuxHeat ) {
|
||||||
@ -247,12 +258,12 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildOperationControl(ClimateEntity entity) {
|
Widget _buildOperationControl(ClimateEntity entity) {
|
||||||
if (entity.supportOperationMode) {
|
if (entity.hvacModes != null) {
|
||||||
return ModeSelectorWidget(
|
return ModeSelectorWidget(
|
||||||
onChange: (mode) => _setOperationMode(entity, mode),
|
onChange: (mode) => _setHVACMode(entity, mode),
|
||||||
options: entity.operationList,
|
options: entity.hvacModes,
|
||||||
caption: "Operation",
|
caption: "Operation",
|
||||||
value: _tmpOperationMode,
|
value: _tmpHVACMode,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Container(height: 0.0, width: 0.0);
|
return Container(height: 0.0, width: 0.0);
|
||||||
@ -262,7 +273,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
Widget _buildFanControl(ClimateEntity entity) {
|
Widget _buildFanControl(ClimateEntity entity) {
|
||||||
if (entity.supportFanMode) {
|
if (entity.supportFanMode) {
|
||||||
return ModeSelectorWidget(
|
return ModeSelectorWidget(
|
||||||
options: entity.fanList,
|
options: entity.fanModes,
|
||||||
onChange: (mode) => _setFanMode(entity, mode),
|
onChange: (mode) => _setFanMode(entity, mode),
|
||||||
caption: "Fan mode",
|
caption: "Fan mode",
|
||||||
value: _tmpFanMode,
|
value: _tmpFanMode,
|
||||||
@ -276,7 +287,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
if (entity.supportSwingMode) {
|
if (entity.supportSwingMode) {
|
||||||
return ModeSelectorWidget(
|
return ModeSelectorWidget(
|
||||||
onChange: (mode) => _setSwingMode(entity, mode),
|
onChange: (mode) => _setSwingMode(entity, mode),
|
||||||
options: entity.swingList,
|
options: entity.swingModes,
|
||||||
value: _tmpSwingMode,
|
value: _tmpSwingMode,
|
||||||
caption: "Swing mode"
|
caption: "Swing mode"
|
||||||
);
|
);
|
||||||
@ -308,7 +319,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
|
|
||||||
Widget _buildTargetTemperatureControls(ClimateEntity entity) {
|
Widget _buildTargetTemperatureControls(ClimateEntity entity) {
|
||||||
List<Widget> controls = [];
|
List<Widget> controls = [];
|
||||||
if ((entity.supportTargetTemperatureLow) && (entity.targetLow != null)) {
|
if ((entity.supportTargetTemperatureRange) && (entity.targetLow != null)) {
|
||||||
controls.addAll(<Widget>[
|
controls.addAll(<Widget>[
|
||||||
TemperatureControlWidget(
|
TemperatureControlWidget(
|
||||||
value: _tmpTargetLow,
|
value: _tmpTargetLow,
|
||||||
@ -321,7 +332,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if ((entity.supportTargetTemperatureHigh) && (entity.targetHigh != null)) {
|
if ((entity.supportTargetTemperatureRange) && (entity.targetHigh != null)) {
|
||||||
controls.add(
|
controls.add(
|
||||||
TemperatureControlWidget(
|
TemperatureControlWidget(
|
||||||
value: _tmpTargetHigh,
|
value: _tmpTargetHigh,
|
||||||
@ -408,52 +419,3 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TemperatureControlWidget extends StatelessWidget {
|
|
||||||
final double value;
|
|
||||||
final double fontSize;
|
|
||||||
final Color fontColor;
|
|
||||||
final onInc;
|
|
||||||
final onDec;
|
|
||||||
|
|
||||||
TemperatureControlWidget(
|
|
||||||
{Key key,
|
|
||||||
@required this.value,
|
|
||||||
@required this.onInc,
|
|
||||||
@required this.onDec,
|
|
||||||
this.fontSize,
|
|
||||||
this.fontColor})
|
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
"$value",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: fontSize ?? 24.0,
|
|
||||||
color: fontColor ?? Colors.black
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
|
||||||
'mdi:chevron-up')),
|
|
||||||
iconSize: 30.0,
|
|
||||||
onPressed: () => onInc(),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
|
||||||
'mdi:chevron-down')),
|
|
||||||
iconSize: 30.0,
|
|
||||||
onPressed: () => onDec(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class ClimateStateWidget extends StatelessWidget {
|
class ClimateStateWidget extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
@ -8,13 +8,19 @@ class ClimateStateWidget extends StatelessWidget {
|
|||||||
String targetTemp = "-";
|
String targetTemp = "-";
|
||||||
if ((entity.supportTargetTemperature) && (entity.temperature != null)) {
|
if ((entity.supportTargetTemperature) && (entity.temperature != null)) {
|
||||||
targetTemp = "${entity.temperature}";
|
targetTemp = "${entity.temperature}";
|
||||||
} else if ((entity.supportTargetTemperatureLow) &&
|
} else if ((entity.supportTargetTemperatureRange) &&
|
||||||
(entity.targetLow != null)) {
|
(entity.targetLow != null) &&
|
||||||
targetTemp = "${entity.targetLow}";
|
(entity.targetHigh != null)) {
|
||||||
if ((entity.supportTargetTemperatureHigh) &&
|
targetTemp = "${entity.targetLow} - ${entity.targetHigh}";
|
||||||
(entity.targetHigh != null)) {
|
}
|
||||||
targetTemp += " - ${entity.targetHigh}";
|
String displayState = '';
|
||||||
}
|
if (entity.hvacAction != null) {
|
||||||
|
displayState = "${entity.hvacAction} (${entity.displayState})";
|
||||||
|
} else {
|
||||||
|
displayState = "${entity.displayState}";
|
||||||
|
}
|
||||||
|
if (entity.presetMode != null) {
|
||||||
|
displayState += " - ${entity.presetMode}";
|
||||||
}
|
}
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
@ -25,7 +31,7 @@ class ClimateStateWidget extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Row(
|
Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text("${entity.state}",
|
Text("$displayState",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: new TextStyle(
|
style: new TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -38,8 +44,8 @@ class ClimateStateWidget extends StatelessWidget {
|
|||||||
))
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
entity.attributes["current_temperature"] != null ?
|
entity.currentTemperature != null ?
|
||||||
Text("Currently: ${entity.attributes["current_temperature"]}",
|
Text("Currently: ${entity.currentTemperature}",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: new TextStyle(
|
style: new TextStyle(
|
||||||
fontSize: Sizes.stateFontSize,
|
fontSize: Sizes.stateFontSize,
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class ModeSelectorWidget extends StatelessWidget {
|
class ModeSelectorWidget extends StatelessWidget {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class ModeSwitchWidget extends StatelessWidget {
|
class ModeSwitchWidget extends StatelessWidget {
|
||||||
|
|
50
lib/entities/climate/widgets/temperature_control_widget.dart
Normal file
50
lib/entities/climate/widgets/temperature_control_widget.dart
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
part of '../../../main.dart';
|
||||||
|
|
||||||
|
class TemperatureControlWidget extends StatelessWidget {
|
||||||
|
final double value;
|
||||||
|
final double fontSize;
|
||||||
|
final Color fontColor;
|
||||||
|
final onInc;
|
||||||
|
final onDec;
|
||||||
|
|
||||||
|
TemperatureControlWidget(
|
||||||
|
{Key key,
|
||||||
|
@required this.value,
|
||||||
|
@required this.onInc,
|
||||||
|
@required this.onDec,
|
||||||
|
this.fontSize,
|
||||||
|
this.fontColor})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
"$value",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: fontSize ?? 24.0,
|
||||||
|
color: fontColor ?? Colors.black
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||||
|
'mdi:chevron-up')),
|
||||||
|
iconSize: 30.0,
|
||||||
|
onPressed: () => onInc(),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||||
|
'mdi:chevron-down')),
|
||||||
|
iconSize: 30.0,
|
||||||
|
onPressed: () => onDec(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class CoverEntity extends Entity {
|
class CoverEntity extends Entity {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class CoverControlWidget extends StatefulWidget {
|
class CoverControlWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpPosition = position.roundToDouble();
|
_tmpPosition = position.roundToDouble();
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_cover_position", entity.entityId,{"position": _tmpPosition.round()}));
|
ConnectionManager().callService(entity.domain, "set_cover_position", entity.entityId,{"position": _tmpPosition.round()});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpTiltPosition = position.roundToDouble();
|
_tmpTiltPosition = position.roundToDouble();
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_cover_tilt_position", entity.entityId,{"tilt_position": _tmpTiltPosition.round()}));
|
ConnectionManager().callService(entity.domain, "set_cover_tilt_position", entity.entityId,{"tilt_position": _tmpTiltPosition.round()});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,18 +135,18 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
|||||||
|
|
||||||
class CoverTiltControlsWidget extends StatelessWidget {
|
class CoverTiltControlsWidget extends StatelessWidget {
|
||||||
void _open(CoverEntity entity) {
|
void _open(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "open_cover_tilt", entity.entityId, null));
|
entity.domain, "open_cover_tilt", entity.entityId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _close(CoverEntity entity) {
|
void _close(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "close_cover_tilt", entity.entityId, null));
|
entity.domain, "close_cover_tilt", entity.entityId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _stop(CoverEntity entity) {
|
void _stop(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "stop_cover_tilt", entity.entityId, null));
|
entity.domain, "stop_cover_tilt", entity.entityId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
@ -1,19 +1,19 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class CoverStateWidget extends StatelessWidget {
|
class CoverStateWidget extends StatelessWidget {
|
||||||
void _open(CoverEntity entity) {
|
void _open(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "open_cover", entity.entityId, null));
|
entity.domain, "open_cover", entity.entityId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _close(CoverEntity entity) {
|
void _close(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "close_cover", entity.entityId, null));
|
entity.domain, "close_cover", entity.entityId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _stop(CoverEntity entity) {
|
void _stop(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "stop_cover", entity.entityId, null));
|
entity.domain, "stop_cover", entity.entityId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class DateTimeEntity extends Entity {
|
class DateTimeEntity extends Entity {
|
||||||
DateTimeEntity(Map rawData, String webHost) : super(rawData, webHost);
|
DateTimeEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
@ -35,8 +35,7 @@ class DateTimeEntity extends Entity {
|
|||||||
return formattedState;
|
return formattedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setNewState(newValue) {
|
void setNewState(Map newValue) {
|
||||||
eventBus
|
ConnectionManager().callService(domain, "set_datetime", entityId, newValue);
|
||||||
.fire(new ServiceCallEvent(domain, "set_datetime", entityId, newValue));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class DateTimeStateWidget extends StatelessWidget {
|
class DateTimeStateWidget extends StatelessWidget {
|
||||||
@override
|
@override
|
@ -15,14 +15,18 @@ class DefaultEntityContainer extends StatelessWidget {
|
|||||||
return MissedEntityWidget();
|
return MissedEntityWidget();
|
||||||
}
|
}
|
||||||
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.DIVIDER) {
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.DIVIDER) {
|
||||||
return Divider();
|
return Divider(
|
||||||
|
color: Colors.black45,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.SECTION) {
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.SECTION) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Divider(),
|
Divider(
|
||||||
|
color: Colors.black45,
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
"${entityModel.entityWrapper.entity.displayName}",
|
"${entityModel.entityWrapper.entity.displayName}",
|
||||||
style: TextStyle(color: Colors.blue),
|
style: TextStyle(color: Colors.blue),
|
||||||
@ -30,32 +34,37 @@ class DefaultEntityContainer extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return InkWell(
|
Widget result = Row(
|
||||||
onLongPress: () {
|
mainAxisSize: MainAxisSize.max,
|
||||||
if (entityModel.handleTap) {
|
children: <Widget>[
|
||||||
entityModel.entityWrapper.handleHold();
|
EntityIcon(),
|
||||||
}
|
|
||||||
},
|
|
||||||
onTap: () {
|
|
||||||
if (entityModel.handleTap) {
|
|
||||||
entityModel.entityWrapper.handleTap();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: <Widget>[
|
|
||||||
EntityIcon(),
|
|
||||||
|
|
||||||
Flexible(
|
Flexible(
|
||||||
fit: FlexFit.tight,
|
fit: FlexFit.tight,
|
||||||
flex: 3,
|
flex: 3,
|
||||||
child: EntityName(
|
child: EntityName(
|
||||||
padding: EdgeInsets.fromLTRB(10.0, 2.0, 10.0, 2.0),
|
padding: EdgeInsets.fromLTRB(10.0, 2.0, 10.0, 2.0),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
state
|
),
|
||||||
],
|
state
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
|
if (entityModel.handleTap) {
|
||||||
|
return InkWell(
|
||||||
|
onLongPress: () {
|
||||||
|
if (entityModel.handleTap) {
|
||||||
|
entityModel.entityWrapper.handleHold();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTap: () {
|
||||||
|
if (entityModel.handleTap) {
|
||||||
|
entityModel.entityWrapper.handleTap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: result,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -27,7 +27,7 @@ class Entity {
|
|||||||
"cold.on": "Cold",
|
"cold.on": "Cold",
|
||||||
"cold.off": "Normal",
|
"cold.off": "Normal",
|
||||||
"connectivity.on": "Connected",
|
"connectivity.on": "Connected",
|
||||||
"connectivity.off": "Diconnected",
|
"connectivity.off": "Disconnected",
|
||||||
"door.on": "Open",
|
"door.on": "Open",
|
||||||
"door.off": "Closed",
|
"door.off": "Closed",
|
||||||
"garage_door.on": "Open",
|
"garage_door.on": "Open",
|
||||||
@ -154,7 +154,7 @@ class Entity {
|
|||||||
entityId = rawData["entity_id"];
|
entityId = rawData["entity_id"];
|
||||||
deviceClass = attributes["device_class"];
|
deviceClass = attributes["device_class"];
|
||||||
state = rawData["state"];
|
state = rawData["state"];
|
||||||
displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? state;
|
displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? (state.toLowerCase() == 'unknown' ? '-' : state);
|
||||||
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
|
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
|
||||||
entityPicture = _getEntityPictureUrl(webHost);
|
entityPicture = _getEntityPictureUrl(webHost);
|
||||||
}
|
}
|
||||||
@ -211,31 +211,6 @@ class Entity {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildEntityPageWidget(BuildContext context) {
|
|
||||||
return EntityModel(
|
|
||||||
entityWrapper: EntityWrapper(entity: this),
|
|
||||||
child: EntityPageContainer(children: <Widget>[
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(top: Sizes.rowPadding),
|
|
||||||
child: DefaultEntityContainer(state: _buildStatePartForPage(context)),
|
|
||||||
),
|
|
||||||
LastUpdatedWidget(),
|
|
||||||
Divider(),
|
|
||||||
_buildAdditionalControlsForPage(context),
|
|
||||||
Divider(),
|
|
||||||
buildHistoryWidget(),
|
|
||||||
EntityAttributesList()
|
|
||||||
]),
|
|
||||||
handleTap: false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildHistoryWidget() {
|
|
||||||
return EntityHistoryWidget(
|
|
||||||
config: historyConfig,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildBadgeWidget(BuildContext context) {
|
Widget buildBadgeWidget(BuildContext context) {
|
||||||
return EntityModel(
|
return EntityModel(
|
||||||
entityWrapper: EntityWrapper(entity: this),
|
entityWrapper: EntityWrapper(entity: this),
|
@ -14,9 +14,12 @@ class EntityColor {
|
|||||||
"auto": Colors.amber,
|
"auto": Colors.amber,
|
||||||
EntityState.active: Colors.amber,
|
EntityState.active: Colors.amber,
|
||||||
EntityState.playing: Colors.amber,
|
EntityState.playing: Colors.amber,
|
||||||
|
EntityState.paused: 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.cleaning: Colors.amber,
|
||||||
|
EntityState.returning: Colors.amber,
|
||||||
EntityState.off: defaultStateColor,
|
EntityState.off: defaultStateColor,
|
||||||
EntityState.closed: defaultStateColor,
|
EntityState.closed: defaultStateColor,
|
||||||
"below_horizon": defaultStateColor,
|
"below_horizon": defaultStateColor,
|
@ -20,23 +20,3 @@ class EntityModel extends InheritedWidget {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HomeAssistantModel extends InheritedWidget {
|
|
||||||
|
|
||||||
const HomeAssistantModel({
|
|
||||||
Key key,
|
|
||||||
@required this.homeAssistant,
|
|
||||||
@required Widget child,
|
|
||||||
}) : super(key: key, child: child);
|
|
||||||
|
|
||||||
final HomeAssistant homeAssistant;
|
|
||||||
|
|
||||||
static HomeAssistantModel of(BuildContext context) {
|
|
||||||
return context.inheritFromWidgetOfExactType(HomeAssistantModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool updateShouldNotify(InheritedWidget oldWidget) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
70
lib/entities/entity_page_layout.widget.dart
Normal file
70
lib/entities/entity_page_layout.widget.dart
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class EntityPageLayout extends StatelessWidget {
|
||||||
|
|
||||||
|
final bool showClose;
|
||||||
|
final Entity entity;
|
||||||
|
|
||||||
|
EntityPageLayout({Key key, this.showClose: false, this.entity}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return EntityModel(
|
||||||
|
entityWrapper: EntityWrapper(entity: entity),
|
||||||
|
child: ListView(
|
||||||
|
padding: EdgeInsets.all(0),
|
||||||
|
children: <Widget>[
|
||||||
|
showClose ?
|
||||||
|
Container(
|
||||||
|
color: Colors.blue[300],
|
||||||
|
height: 40,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(left: 8),
|
||||||
|
child: Text(
|
||||||
|
entity.displayName,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 22
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
padding: EdgeInsets.all(0),
|
||||||
|
icon: Icon(Icons.close),
|
||||||
|
color: Colors.white,
|
||||||
|
iconSize: 36.0,
|
||||||
|
onPressed: () {
|
||||||
|
eventBus.fire(ShowEntityPageEvent());
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
) :
|
||||||
|
Container(height: 0, width: 0,),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: Sizes.rowPadding, left: Sizes.leftWidgetPadding),
|
||||||
|
child: DefaultEntityContainer(state: entity._buildStatePartForPage(context)),
|
||||||
|
),
|
||||||
|
LastUpdatedWidget(),
|
||||||
|
Divider(),
|
||||||
|
entity._buildAdditionalControlsForPage(context),
|
||||||
|
Divider(),
|
||||||
|
SpoilerCard(
|
||||||
|
title: "State history",
|
||||||
|
body: EntityHistoryWidget(),
|
||||||
|
),
|
||||||
|
SpoilerCard(
|
||||||
|
title: "Attributes",
|
||||||
|
body: EntityAttributesList(),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
handleTap: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ class EntityWrapper {
|
|||||||
|
|
||||||
String displayName;
|
String displayName;
|
||||||
String icon;
|
String icon;
|
||||||
|
String unitOfMeasurement;
|
||||||
String entityPicture;
|
String entityPicture;
|
||||||
EntityUIAction uiAction;
|
EntityUIAction uiAction;
|
||||||
Entity entity;
|
Entity entity;
|
||||||
@ -24,23 +25,22 @@ class EntityWrapper {
|
|||||||
if (uiAction == null) {
|
if (uiAction == null) {
|
||||||
uiAction = EntityUIAction();
|
uiAction = EntityUIAction();
|
||||||
}
|
}
|
||||||
|
unitOfMeasurement = entity.unitOfMeasurement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleTap() {
|
void handleTap() {
|
||||||
switch (uiAction.tapAction) {
|
switch (uiAction.tapAction) {
|
||||||
case EntityUIAction.toggle: {
|
case EntityUIAction.toggle: {
|
||||||
eventBus.fire(
|
ConnectionManager().callService("homeassistant", "toggle", entity.entityId, null);
|
||||||
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EntityUIAction.callService: {
|
case EntityUIAction.callService: {
|
||||||
if (uiAction.tapService != null) {
|
if (uiAction.tapService != null) {
|
||||||
eventBus.fire(
|
ConnectionManager().callService(uiAction.tapService.split(".")[0],
|
||||||
ServiceCallEvent(uiAction.tapService.split(".")[0],
|
|
||||||
uiAction.tapService.split(".")[1], null,
|
uiAction.tapService.split(".")[1], null,
|
||||||
uiAction.tapServiceData));
|
uiAction.tapServiceData);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@ class EntityWrapper {
|
|||||||
|
|
||||||
case EntityUIAction.moreInfo: {
|
case EntityUIAction.moreInfo: {
|
||||||
eventBus.fire(
|
eventBus.fire(
|
||||||
new ShowEntityPageEvent(entity));
|
new ShowEntityPageEvent(entity: entity));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ class EntityWrapper {
|
|||||||
//TODO handle local urls
|
//TODO handle local urls
|
||||||
Logger.w("Local urls is not supported yet");
|
Logger.w("Local urls is not supported yet");
|
||||||
} else {
|
} else {
|
||||||
HAUtils.launchURL(uiAction.tapService);
|
Launcher.launchURL(uiAction.tapService);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -74,24 +74,22 @@ class EntityWrapper {
|
|||||||
void handleHold() {
|
void handleHold() {
|
||||||
switch (uiAction.holdAction) {
|
switch (uiAction.holdAction) {
|
||||||
case EntityUIAction.toggle: {
|
case EntityUIAction.toggle: {
|
||||||
eventBus.fire(
|
ConnectionManager().callService("homeassistant", "toggle", entity.entityId, null);
|
||||||
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EntityUIAction.callService: {
|
case EntityUIAction.callService: {
|
||||||
if (uiAction.holdService != null) {
|
if (uiAction.holdService != null) {
|
||||||
eventBus.fire(
|
ConnectionManager().callService(uiAction.holdService.split(".")[0],
|
||||||
ServiceCallEvent(uiAction.holdService.split(".")[0],
|
|
||||||
uiAction.holdService.split(".")[1], null,
|
uiAction.holdService.split(".")[1], null,
|
||||||
uiAction.holdServiceData));
|
uiAction.holdServiceData);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EntityUIAction.moreInfo: {
|
case EntityUIAction.moreInfo: {
|
||||||
eventBus.fire(
|
eventBus.fire(
|
||||||
new ShowEntityPageEvent(entity));
|
new ShowEntityPageEvent(entity: entity));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +98,7 @@ class EntityWrapper {
|
|||||||
//TODO handle local urls
|
//TODO handle local urls
|
||||||
Logger.w("Local urls is not supported yet");
|
Logger.w("Local urls is not supported yet");
|
||||||
} else {
|
} else {
|
||||||
HAUtils.launchURL(uiAction.holdService);
|
Launcher.launchURL(uiAction.holdService);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class FanEntity extends Entity {
|
class FanEntity extends Entity {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class FanControlsWidget extends StatefulWidget {
|
class FanControlsWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -24,9 +24,9 @@ class _FanControlsWidgetState extends State<FanControlsWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpOscillate = oscillate;
|
_tmpOscillate = oscillate;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
"fan", "oscillate", entity.entityId,
|
"fan", "oscillate", entity.entityId,
|
||||||
{"oscillating": oscillate}));
|
{"oscillating": oscillate});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,9 +34,9 @@ class _FanControlsWidgetState extends State<FanControlsWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpDirectionForward = forward;
|
_tmpDirectionForward = forward;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
"fan", "set_direction", entity.entityId,
|
"fan", "set_direction", entity.entityId,
|
||||||
{"direction": forward ? "forward" : "reverse"}));
|
{"direction": forward ? "forward" : "reverse"});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,9 +44,9 @@ class _FanControlsWidgetState extends State<FanControlsWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpSpeed = value;
|
_tmpSpeed = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
"fan", "set_speed", entity.entityId,
|
"fan", "set_speed", entity.entityId,
|
||||||
{"speed": value}));
|
{"speed": value});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../main.dart';
|
||||||
|
|
||||||
class FlatServiceButton extends StatelessWidget {
|
class FlatServiceButton extends StatelessWidget {
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ class FlatServiceButton extends StatelessWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
void _setNewState() {
|
void _setNewState() {
|
||||||
eventBus.fire(new ServiceCallEvent(serviceDomain, serviceName, entityId, null));
|
ConnectionManager().callService(serviceDomain, serviceName, entityId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class GroupEntity extends Entity {
|
class GroupEntity extends Entity {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class LightEntity extends Entity {
|
class LightEntity extends Entity {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class LightColorPicker extends StatefulWidget {
|
class LightColorPicker extends StatefulWidget {
|
||||||
|
|
||||||
@ -27,7 +27,6 @@ class LightColorPickerState extends State<LightColorPicker> {
|
|||||||
List<Widget> colorRows = [];
|
List<Widget> colorRows = [];
|
||||||
Border border;
|
Border border;
|
||||||
bool isSomethingSelected = false;
|
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))) {
|
for (double saturation = 1.0; saturation >= (0.0 + widget.saturationStep); saturation = double.parse((saturation - widget.saturationStep).toStringAsFixed(2))) {
|
||||||
List<Widget> rowChildren = [];
|
List<Widget> rowChildren = [];
|
||||||
//Logger.d("$saturation");
|
//Logger.d("$saturation");
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class LightControlsWidget extends StatefulWidget {
|
class LightControlsWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
String _tmpEffect;
|
String _tmpEffect;
|
||||||
|
|
||||||
void _resetState(LightEntity entity) {
|
void _resetState(LightEntity entity) {
|
||||||
_tmpBrightness = entity.brightness ?? 0;
|
_tmpBrightness = entity.brightness ?? 1;
|
||||||
_tmpWhiteValue = entity.whiteValue ?? 0;
|
_tmpWhiteValue = entity.whiteValue ?? 0;
|
||||||
_tmpColorTemp = entity.colorTemp ?? entity.minMireds?.toInt();
|
_tmpColorTemp = entity.colorTemp ?? entity.minMireds?.toInt();
|
||||||
_tmpColor = entity.color ?? _tmpColor;
|
_tmpColor = entity.color ?? _tmpColor;
|
||||||
@ -28,15 +28,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpBrightness = value.round();
|
_tmpBrightness = value.round();
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
if (_tmpBrightness > 0) {
|
ConnectionManager().callService(
|
||||||
eventBus.fire(new ServiceCallEvent(
|
entity.domain, "turn_on", entity.entityId,
|
||||||
entity.domain, "turn_on", entity.entityId,
|
{"brightness": _tmpBrightness});
|
||||||
{"brightness": _tmpBrightness}));
|
|
||||||
} else {
|
|
||||||
eventBus.fire(new ServiceCallEvent(
|
|
||||||
entity.domain, "turn_off", entity.entityId,
|
|
||||||
null));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,9 +38,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpWhiteValue = value.round();
|
_tmpWhiteValue = value.round();
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "turn_on", entity.entityId,
|
entity.domain, "turn_on", entity.entityId,
|
||||||
{"white_value": _tmpWhiteValue}));
|
{"white_value": _tmpWhiteValue});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -55,9 +49,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpColorTemp = value.round();
|
_tmpColorTemp = value.round();
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "turn_on", entity.entityId,
|
entity.domain, "turn_on", entity.entityId,
|
||||||
{"color_temp": _tmpColorTemp}));
|
{"color_temp": _tmpColorTemp});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,9 +60,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
_tmpColor = color;
|
_tmpColor = color;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
Logger.d( "HS Color: [${color.hue}, ${color.saturation}]");
|
Logger.d( "HS Color: [${color.hue}, ${color.saturation}]");
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "turn_on", entity.entityId,
|
entity.domain, "turn_on", entity.entityId,
|
||||||
{"hs_color": [color.hue, color.saturation*100]}));
|
{"hs_color": [color.hue, color.saturation*100]});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,9 +71,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
_tmpEffect = value;
|
_tmpEffect = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
if (_tmpEffect != null) {
|
if (_tmpEffect != null) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "turn_on", entity.entityId,
|
entity.domain, "turn_on", entity.entityId,
|
||||||
{"effect": "$value"}));
|
{"effect": "$value"});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -106,7 +100,19 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBrightnessControl(LightEntity entity) {
|
Widget _buildBrightnessControl(LightEntity entity) {
|
||||||
if ((entity.supportBrightness) && (_tmpBrightness != null)) {
|
if (entity.supportBrightness) {
|
||||||
|
double val;
|
||||||
|
if (_tmpBrightness != null) {
|
||||||
|
if (_tmpBrightness > 255) {
|
||||||
|
val = 255;
|
||||||
|
} else if (_tmpBrightness < 1) {
|
||||||
|
val = 1;
|
||||||
|
} else {
|
||||||
|
val = _tmpBrightness.toDouble();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val = 1;
|
||||||
|
}
|
||||||
return UniversalSlider(
|
return UniversalSlider(
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -114,10 +120,10 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
_tmpBrightness = value.round();
|
_tmpBrightness = value.round();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
min: 0.0,
|
min: 1.0,
|
||||||
max: 255.0,
|
max: 255.0,
|
||||||
onChangeEnd: (value) => _setBrightness(entity, value),
|
onChangeEnd: (value) => _setBrightness(entity, value),
|
||||||
value: _tmpBrightness == null ? 0.0 : _tmpBrightness.toDouble(),
|
value: val,
|
||||||
leading: Icon(Icons.brightness_5),
|
leading: Icon(Icons.brightness_5),
|
||||||
title: "Brightness",
|
title: "Brightness",
|
||||||
);
|
);
|
||||||
@ -149,10 +155,22 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
|
|
||||||
Widget _buildColorTempControl(LightEntity entity) {
|
Widget _buildColorTempControl(LightEntity entity) {
|
||||||
if (entity.supportColorTemp) {
|
if (entity.supportColorTemp) {
|
||||||
|
double val;
|
||||||
|
if (_tmpColorTemp != null) {
|
||||||
|
if (_tmpColorTemp > entity.maxMireds) {
|
||||||
|
val = entity.maxMireds;
|
||||||
|
} else if (_tmpColorTemp < entity.minMireds) {
|
||||||
|
val = entity.minMireds;
|
||||||
|
} else {
|
||||||
|
val = _tmpColorTemp.toDouble();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val = entity.minMireds;
|
||||||
|
}
|
||||||
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 == null ? entity.maxMireds : _tmpColorTemp.toDouble(),
|
value: val,
|
||||||
onChangeEnd: (value) => _setColorTemp(entity, value),
|
onChangeEnd: (value) => _setColorTemp(entity, value),
|
||||||
max: entity.maxMireds,
|
max: entity.maxMireds,
|
||||||
min: entity.minMireds,
|
min: entity.minMireds,
|
||||||
@ -171,7 +189,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
|
|
||||||
Widget _buildColorControl(LightEntity entity) {
|
Widget _buildColorControl(LightEntity entity) {
|
||||||
if (entity.supportColor) {
|
if (entity.supportColor) {
|
||||||
HSVColor savedColor = HomeAssistantModel.of(context)?.homeAssistant?.savedColor;
|
HSVColor savedColor = HomeAssistant().savedColor;
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -187,10 +205,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
child: Text('Copy color'),
|
child: Text('Copy color'),
|
||||||
onPressed: _tmpColor == null ? null : () {
|
onPressed: _tmpColor == null ? null : () {
|
||||||
setState(() {
|
setState(() {
|
||||||
HomeAssistantModel
|
HomeAssistant().savedColor = _tmpColor;
|
||||||
.of(context)
|
|
||||||
.homeAssistant
|
|
||||||
.savedColor = _tmpColor;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -212,10 +227,14 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
|
|
||||||
Widget _buildEffectControl(LightEntity entity) {
|
Widget _buildEffectControl(LightEntity entity) {
|
||||||
if ((entity.supportEffect) && (entity.effectList != null)) {
|
if ((entity.supportEffect) && (entity.effectList != null)) {
|
||||||
|
List<String> list = List.from(entity.effectList);
|
||||||
|
if (_tmpEffect!= null && !list.contains(_tmpEffect)) {
|
||||||
|
list.insert(0, _tmpEffect);
|
||||||
|
}
|
||||||
return ModeSelectorWidget(
|
return ModeSelectorWidget(
|
||||||
onChange: (effect) => _setEffect(entity, effect),
|
onChange: (effect) => _setEffect(entity, effect),
|
||||||
caption: "Effect",
|
caption: "Effect",
|
||||||
options: entity.effectList,
|
options: list,
|
||||||
value: _tmpEffect
|
value: _tmpEffect
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class LockEntity extends Entity {
|
class LockEntity extends Entity {
|
||||||
LockEntity(Map rawData, String webHost) : super(rawData, webHost);
|
LockEntity(Map rawData, String webHost) : super(rawData, webHost);
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class LockStateWidget extends StatelessWidget {
|
class LockStateWidget extends StatelessWidget {
|
||||||
|
|
||||||
@ -7,11 +7,11 @@ class LockStateWidget extends StatelessWidget {
|
|||||||
const LockStateWidget({Key key, this.assumedState: false}) : super(key: key);
|
const LockStateWidget({Key key, this.assumedState: false}) : super(key: key);
|
||||||
|
|
||||||
void _lock(Entity entity) {
|
void _lock(Entity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent("lock", "lock", entity.entityId, null));
|
ConnectionManager().callService("lock", "lock", entity.entityId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _unlock(Entity entity) {
|
void _unlock(Entity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent("lock", "unlock", entity.entityId, null));
|
ConnectionManager().callService("lock", "unlock", entity.entityId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class MediaPlayerEntity extends Entity {
|
class MediaPlayerEntity extends Entity {
|
||||||
|
|
||||||
@ -74,10 +74,37 @@ class MediaPlayerEntity extends Entity {
|
|||||||
|
|
||||||
List<String> get soundModeList => getStringListAttributeValue("sound_mode_list");
|
List<String> get soundModeList => getStringListAttributeValue("sound_mode_list");
|
||||||
List<String> get sourceList => getStringListAttributeValue("source_list");
|
List<String> get sourceList => getStringListAttributeValue("source_list");
|
||||||
|
DateTime get positionLastUpdated => DateTime.tryParse("${attributes["media_position_updated_at"]}")?.toLocal();
|
||||||
|
int get durationSeconds => _getIntAttributeValue("media_duration");
|
||||||
|
int get positionSeconds => _getIntAttributeValue("media_position");
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||||
return MediaPlayerControls();
|
return MediaPlayerControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool canCalculateActualPosition() {
|
||||||
|
return positionLastUpdated != null && durationSeconds != null && positionSeconds != null && durationSeconds >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getActualPosition() {
|
||||||
|
double result = 0;
|
||||||
|
if (canCalculateActualPosition()) {
|
||||||
|
Duration durationD;
|
||||||
|
Duration positionD;
|
||||||
|
durationD = Duration(seconds: durationSeconds);
|
||||||
|
positionD = Duration(
|
||||||
|
seconds: positionSeconds);
|
||||||
|
result = positionD.inSeconds.toDouble();
|
||||||
|
int differenceInSeconds = DateTime
|
||||||
|
.now()
|
||||||
|
.difference(positionLastUpdated)
|
||||||
|
.inSeconds;
|
||||||
|
result = ((result + differenceInSeconds) <= durationD.inSeconds) ? (result + differenceInSeconds) : durationD.inSeconds.toDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
part of '../../../main.dart';
|
||||||
|
|
||||||
|
class MediaPlayerProgressBar extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_MediaPlayerProgressBarState createState() => _MediaPlayerProgressBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MediaPlayerProgressBarState extends State<MediaPlayerProgressBar> {
|
||||||
|
|
||||||
|
Timer _timer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
initState() {
|
||||||
|
super.initState();
|
||||||
|
_timer = Timer.periodic(Duration(seconds: 1), (_) {
|
||||||
|
setState(() {
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final EntityModel entityModel = EntityModel.of(context);
|
||||||
|
final MediaPlayerEntity entity = entityModel.entityWrapper.entity;
|
||||||
|
double progress;
|
||||||
|
int currentPosition;
|
||||||
|
if (entity.canCalculateActualPosition()) {
|
||||||
|
currentPosition = entity.getActualPosition().toInt();
|
||||||
|
progress = (currentPosition <= entity.durationSeconds) ? currentPosition / entity.durationSeconds : 100;
|
||||||
|
} else {
|
||||||
|
progress = 0;
|
||||||
|
}
|
||||||
|
return LinearProgressIndicator(
|
||||||
|
value: progress,
|
||||||
|
backgroundColor: Colors.black45,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(EntityColor.stateColor(EntityState.on)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
part of '../../../main.dart';
|
||||||
|
|
||||||
|
class MediaPlayerSeekBar extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_MediaPlayerSeekBarState createState() => _MediaPlayerSeekBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
|
||||||
|
|
||||||
|
Timer _timer;
|
||||||
|
bool _seekStarted = false;
|
||||||
|
bool _changedHere = false;
|
||||||
|
double _currentPosition = 0;
|
||||||
|
int _savedPosition = 0;
|
||||||
|
|
||||||
|
final TextStyle _seekTextStyle = TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
color: Colors.blue,
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
initState() {
|
||||||
|
super.initState();
|
||||||
|
_timer = Timer.periodic(Duration(seconds: 1), (_) {
|
||||||
|
if (!_seekStarted && !_changedHere) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final EntityModel entityModel = EntityModel.of(context);
|
||||||
|
final MediaPlayerEntity entity = entityModel.entityWrapper.entity;
|
||||||
|
|
||||||
|
if (entity.canCalculateActualPosition() && entity.state != EntityState.idle) {
|
||||||
|
if (HomeAssistant().sendToPlayerId == entity.entityId && HomeAssistant().savedPlayerPosition != null) {
|
||||||
|
_savedPosition = HomeAssistant().savedPlayerPosition;
|
||||||
|
HomeAssistant().savedPlayerPosition = null;
|
||||||
|
HomeAssistant().sendToPlayerId = null;
|
||||||
|
}
|
||||||
|
if (entity.state == EntityState.playing && !_seekStarted &&
|
||||||
|
!_changedHere) {
|
||||||
|
_currentPosition = entity.getActualPosition();
|
||||||
|
} else if (entity.state == EntityState.paused) {
|
||||||
|
_currentPosition = entity.positionSeconds.toDouble();
|
||||||
|
} else if (_changedHere) {
|
||||||
|
_changedHere = false;
|
||||||
|
}
|
||||||
|
List<Widget> buttons = [];
|
||||||
|
if (_savedPosition > 0) {
|
||||||
|
buttons.add(
|
||||||
|
RaisedButton(
|
||||||
|
child: Text("Jump to ${Duration(seconds: _savedPosition).toString().split('.')[0]}"),
|
||||||
|
color: Colors.orange,
|
||||||
|
focusColor: Colors.white,
|
||||||
|
onPressed: () {
|
||||||
|
ConnectionManager().callService(
|
||||||
|
"media_player",
|
||||||
|
"media_seek",
|
||||||
|
"${entity.entityId}",
|
||||||
|
{"seek_position": _savedPosition}
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
_savedPosition = 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, 20, Sizes.rightWidgetPadding, 0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: <Widget>[
|
||||||
|
Text("00:00"),
|
||||||
|
Expanded(
|
||||||
|
child: Text("${Duration(seconds: _currentPosition.toInt()).toString().split(".")[0]}",textAlign: TextAlign.center, style: _seekTextStyle),
|
||||||
|
),
|
||||||
|
Text("${Duration(seconds: entity.durationSeconds).toString().split(".")[0]}")
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Container(height: 10,),
|
||||||
|
Slider(
|
||||||
|
min: 0,
|
||||||
|
activeColor: Colors.amber,
|
||||||
|
inactiveColor: Colors.black26,
|
||||||
|
max: entity.durationSeconds.toDouble(),
|
||||||
|
value: _currentPosition,
|
||||||
|
onChangeStart: (val) {
|
||||||
|
_seekStarted = true;
|
||||||
|
},
|
||||||
|
onChanged: (val) {
|
||||||
|
setState(() {
|
||||||
|
_currentPosition = val;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onChangeEnd: (val) {
|
||||||
|
_seekStarted = false;
|
||||||
|
Timer(Duration(milliseconds: 500), () {
|
||||||
|
if (!_seekStarted) {
|
||||||
|
ConnectionManager().callService(
|
||||||
|
"media_player",
|
||||||
|
"media_seek",
|
||||||
|
"${entity.entityId}",
|
||||||
|
{"seek_position": val}
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
_changedHere = true;
|
||||||
|
_currentPosition = val;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ButtonBar(
|
||||||
|
children: buttons,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Container(width: 0, height: 0,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class MediaPlayerWidget extends StatelessWidget {
|
class MediaPlayerWidget extends StatelessWidget {
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ class MediaPlayerWidget extends StatelessWidget {
|
|||||||
bottom: 0.0,
|
bottom: 0.0,
|
||||||
left: 0.0,
|
left: 0.0,
|
||||||
right: 0.0,
|
right: 0.0,
|
||||||
child: MediaPlayerProgressWidget()
|
child: MediaPlayerProgressBar()
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -121,23 +121,23 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
|
|||||||
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) {
|
||||||
Logger.d("${entity.entityId} turn_on");
|
Logger.d("${entity.entityId} turn_on");
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "turn_on", entity.entityId,
|
entity.domain, "turn_on", entity.entityId,
|
||||||
null));
|
null);
|
||||||
} else {
|
} else {
|
||||||
Logger.d("${entity.entityId} turn_off");
|
Logger.d("${entity.entityId} turn_off");
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "turn_off", entity.entityId,
|
entity.domain, "turn_off", entity.entityId,
|
||||||
null));
|
null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _callAction(MediaPlayerEntity entity, String action) {
|
void _callAction(MediaPlayerEntity entity, String action) {
|
||||||
Logger.d("${entity.entityId} $action");
|
Logger.d("${entity.entityId} $action");
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "$action", entity.entityId,
|
entity.domain, "$action", entity.entityId,
|
||||||
null));
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -229,7 +229,7 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||||
"mdi:dots-vertical")),
|
"mdi:dots-vertical")),
|
||||||
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity))
|
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity: entity))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else if (entity.supportStop && entity.state != EntityState.off && entity.state != EntityState.unavailable) {
|
} else if (entity.supportStop && entity.state != EntityState.off && entity.state != EntityState.unavailable) {
|
||||||
@ -264,27 +264,27 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
_newVolumeLevel = value;
|
_newVolumeLevel = value;
|
||||||
eventBus.fire(ServiceCallEvent("media_player", "volume_set", entityId, {"volume_level": value}));
|
ConnectionManager().callService("media_player", "volume_set", entityId, {"volume_level": value});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setVolumeMute(bool isMuted, String entityId) {
|
void _setVolumeMute(bool isMuted, String entityId) {
|
||||||
eventBus.fire(ServiceCallEvent("media_player", "volume_mute", entityId, {"is_volume_muted": isMuted}));
|
ConnectionManager().callService("media_player", "volume_mute", entityId, {"is_volume_muted": isMuted});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setVolumeUp(String entityId) {
|
void _setVolumeUp(String entityId) {
|
||||||
eventBus.fire(ServiceCallEvent("media_player", "volume_up", entityId, null));
|
ConnectionManager().callService("media_player", "volume_up", entityId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setVolumeDown(String entityId) {
|
void _setVolumeDown(String entityId) {
|
||||||
eventBus.fire(ServiceCallEvent("media_player", "volume_down", entityId, null));
|
ConnectionManager().callService("media_player", "volume_down", entityId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setSoundMode(String value, String entityId) {
|
void _setSoundMode(String value, String entityId) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_newSoundMode = value;
|
_newSoundMode = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(ServiceCallEvent("media_player", "select_sound_mode", entityId, {"sound_mode": "$value"}));
|
ConnectionManager().callService("media_player", "select_sound_mode", entityId, {"sound_mode": "$value"});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +292,7 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_newSource = source;
|
_newSource = source;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
eventBus.fire(ServiceCallEvent("media_player", "select_source", entityId, {"source": "$source"}));
|
ConnectionManager().callService("media_player", "select_source", entityId, {"source": "$source"});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,6 +305,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) {
|
||||||
|
if (entity.supportSeek) {
|
||||||
|
children.add(MediaPlayerSeekBar());
|
||||||
|
} else {
|
||||||
|
children.add(MediaPlayerProgressBar());
|
||||||
|
}
|
||||||
Widget muteWidget;
|
Widget muteWidget;
|
||||||
Widget volumeStepWidget;
|
Widget volumeStepWidget;
|
||||||
if (entity.supportVolumeMute || entity.attributes["is_volume_muted"] != null) {
|
if (entity.supportVolumeMute || entity.attributes["is_volume_muted"] != null) {
|
||||||
@ -398,69 +403,43 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (entity.state == EntityState.playing || entity.state == EntityState.paused) {
|
||||||
|
children.add(
|
||||||
|
ButtonBar(
|
||||||
|
children: <Widget>[
|
||||||
|
RaisedButton(
|
||||||
|
child: Text("Duplicate to"),
|
||||||
|
color: Colors.blue,
|
||||||
|
textColor: Colors.white,
|
||||||
|
onPressed: () => _duplicateTo(entity),
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
child: Text("Switch to"),
|
||||||
|
color: Colors.blue,
|
||||||
|
textColor: Colors.white,
|
||||||
|
onPressed: () => _switchTo(entity),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Column(
|
return Column(
|
||||||
children: children,
|
children: children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
void _duplicateTo(entity) {
|
||||||
|
HomeAssistant().savedPlayerPosition = entity.getActualPosition().toInt();
|
||||||
class MediaPlayerProgressWidget extends StatefulWidget {
|
Navigator.of(context).pushNamed("/play-media", arguments: {
|
||||||
@override
|
"url": entity.attributes["media_content_id"],
|
||||||
_MediaPlayerProgressWidgetState createState() => _MediaPlayerProgressWidgetState();
|
"type": entity.attributes["media_content_type"]
|
||||||
}
|
});
|
||||||
|
|
||||||
class _MediaPlayerProgressWidgetState extends State<MediaPlayerProgressWidget> {
|
|
||||||
|
|
||||||
Timer _timer;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final EntityModel entityModel = EntityModel.of(context);
|
|
||||||
final MediaPlayerEntity entity = entityModel.entityWrapper.entity;
|
|
||||||
double progress;
|
|
||||||
try {
|
|
||||||
DateTime lastUpdated = DateTime.parse(
|
|
||||||
entity.attributes["media_position_updated_at"]).toLocal();
|
|
||||||
Duration duration = Duration(seconds: entity._getIntAttributeValue("media_duration") ?? 1);
|
|
||||||
Duration position = Duration(seconds: entity._getIntAttributeValue("media_position") ?? 0);
|
|
||||||
int currentPosition = position.inSeconds;
|
|
||||||
if (entity.state == EntityState.playing) {
|
|
||||||
_timer?.cancel();
|
|
||||||
_timer = Timer(Duration(seconds: 1), () {
|
|
||||||
setState(() {
|
|
||||||
});
|
|
||||||
});
|
|
||||||
int differenceInSeconds = DateTime
|
|
||||||
.now()
|
|
||||||
.difference(lastUpdated)
|
|
||||||
.inSeconds;
|
|
||||||
currentPosition = currentPosition + differenceInSeconds;
|
|
||||||
} else {
|
|
||||||
_timer?.cancel();
|
|
||||||
}
|
|
||||||
progress = currentPosition / duration.inSeconds;
|
|
||||||
return LinearProgressIndicator(
|
|
||||||
value: progress,
|
|
||||||
backgroundColor: Colors.black45,
|
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(EntityColor.stateColor(EntityState.on)),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
_timer?.cancel();
|
|
||||||
progress = 0.0;
|
|
||||||
}
|
|
||||||
return LinearProgressIndicator(
|
|
||||||
value: progress,
|
|
||||||
backgroundColor: Colors.black45,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void _switchTo(entity) {
|
||||||
void dispose() {
|
HomeAssistant().sendFromPlayerId = entity.entityId;
|
||||||
_timer?.cancel();
|
_duplicateTo(entity);
|
||||||
super.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class SelectEntity extends Entity {
|
class SelectEntity extends Entity {
|
||||||
List<String> get listOptions => attributes["options"] != null
|
List<String> get listOptions => attributes["options"] != null
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class SelectStateWidget extends StatefulWidget {
|
class SelectStateWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -11,8 +11,8 @@ class SelectStateWidget extends StatefulWidget {
|
|||||||
class _SelectStateWidgetState extends State<SelectStateWidget> {
|
class _SelectStateWidgetState extends State<SelectStateWidget> {
|
||||||
|
|
||||||
void setNewState(domain, entityId, newValue) {
|
void setNewState(domain, entityId, newValue) {
|
||||||
eventBus.fire(new ServiceCallEvent(domain, "select_option", entityId,
|
ConnectionManager().callService(domain, "select_option", entityId,
|
||||||
{"option": "$newValue"}));
|
{"option": "$newValue"});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
@ -1,8 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class SunEntity extends Entity {
|
|
||||||
SunEntity(Map rawData, String webHost) : super(rawData, webHost);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SensorEntity extends Entity {
|
class SensorEntity extends Entity {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../main.dart';
|
||||||
|
|
||||||
class SimpleEntityState extends StatelessWidget {
|
class SimpleEntityState extends StatelessWidget {
|
||||||
|
|
||||||
@ -7,8 +7,10 @@ class SimpleEntityState extends StatelessWidget {
|
|||||||
final EdgeInsetsGeometry padding;
|
final EdgeInsetsGeometry padding;
|
||||||
final int maxLines;
|
final int maxLines;
|
||||||
final String customValue;
|
final String customValue;
|
||||||
|
final double fontSize;
|
||||||
|
final bool bold;
|
||||||
|
|
||||||
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);
|
const SimpleEntityState({Key key,this.bold: false, this.maxLines: 10, this.fontSize: Sizes.stateFontSize, 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) {
|
||||||
@ -21,18 +23,22 @@ class SimpleEntityState extends StatelessWidget {
|
|||||||
state = customValue;
|
state = customValue;
|
||||||
}
|
}
|
||||||
TextStyle textStyle = TextStyle(
|
TextStyle textStyle = TextStyle(
|
||||||
fontSize: Sizes.stateFontSize,
|
fontSize: this.fontSize,
|
||||||
|
fontWeight: FontWeight.normal
|
||||||
);
|
);
|
||||||
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.CALL_SERVICE) {
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.CALL_SERVICE) {
|
||||||
textStyle = textStyle.apply(color: Colors.blue);
|
textStyle = textStyle.apply(color: Colors.blue);
|
||||||
}
|
}
|
||||||
|
if (this.bold) {
|
||||||
|
textStyle = textStyle.apply(fontWeightDelta: 100);
|
||||||
|
}
|
||||||
while (state.contains(" ")){
|
while (state.contains(" ")){
|
||||||
state = state.replaceAll(" ", " ");
|
state = state.replaceAll(" ", " ");
|
||||||
}
|
}
|
||||||
Widget result = Padding(
|
Widget result = Padding(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
child: Text(
|
child: Text(
|
||||||
"$state ${entityModel.entityWrapper.entity.unitOfMeasurement}",
|
"$state ${entityModel.entityWrapper.unitOfMeasurement}",
|
||||||
textAlign: textAlign,
|
textAlign: textAlign,
|
||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class SliderEntity extends Entity {
|
class SliderEntity extends Entity {
|
||||||
SliderEntity(Map rawData, String webHost) : super(rawData, webHost);
|
SliderEntity(Map rawData, String webHost) : super(rawData, webHost);
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class SliderControlsWidget extends StatefulWidget {
|
class SliderControlsWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -18,8 +18,8 @@ class _SliderControlsWidgetState extends State<SliderControlsWidget> {
|
|||||||
_newValue = newValue;
|
_newValue = newValue;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
});
|
});
|
||||||
eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId,
|
ConnectionManager().callService(domain, "set_value", entityId,
|
||||||
{"value": "${newValue.toString()}"}));
|
{"value": "${newValue.toString()}"});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
5
lib/entities/sun/sun_entity.class.dart
Normal file
5
lib/entities/sun/sun_entity.class.dart
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class SunEntity extends Entity {
|
||||||
|
SunEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class SwitchEntity extends Entity {
|
class SwitchEntity extends Entity {
|
||||||
SwitchEntity(Map rawData, String webHost) : super(rawData, webHost);
|
SwitchEntity(Map rawData, String webHost) : super(rawData, webHost);
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class SwitchStateWidget extends StatefulWidget {
|
class SwitchStateWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -38,8 +38,8 @@ class _SwitchStateWidgetState extends State<SwitchStateWidget> {
|
|||||||
} else {
|
} else {
|
||||||
domain = entity.domain;
|
domain = entity.domain;
|
||||||
}
|
}
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
domain, (newValue as bool) ? "turn_on" : "turn_off", entity.entityId, null));
|
domain, (newValue as bool) ? "turn_on" : "turn_off", entity.entityId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class TextEntity extends Entity {
|
class TextEntity extends Entity {
|
||||||
TextEntity(Map rawData, String webHost) : super(rawData, webHost);
|
TextEntity(Map rawData, String webHost) : super(rawData, webHost);
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class TextInputStateWidget extends StatefulWidget {
|
class TextInputStateWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -26,8 +26,8 @@ class _TextInputStateWidgetState extends State<TextInputStateWidget> {
|
|||||||
|
|
||||||
void setNewState(newValue, domain, entityId) {
|
void setNewState(newValue, domain, entityId) {
|
||||||
if (validate(newValue, _minLength, _maxLength)) {
|
if (validate(newValue, _minLength, _maxLength)) {
|
||||||
eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId,
|
ConnectionManager().callService(domain, "set_value", entityId,
|
||||||
{"value": "$newValue"}));
|
{"value": "$newValue"});
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
_tmpValue = _entityState;
|
_tmpValue = _entityState;
|
||||||
@ -73,13 +73,7 @@ class _TextInputStateWidgetState extends State<TextInputStateWidget> {
|
|||||||
child: TextField(
|
child: TextField(
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
obscureText: entity.isPasswordField,
|
obscureText: entity.isPasswordField,
|
||||||
controller: new TextEditingController.fromValue(
|
controller: TextEditingController.fromValue(TextEditingValue(text: _tmpValue)),
|
||||||
new TextEditingValue(
|
|
||||||
text: _tmpValue,
|
|
||||||
selection:
|
|
||||||
new TextSelection.collapsed(offset: _tmpValue.length)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
_tmpValue = value;
|
_tmpValue = value;
|
||||||
}),
|
}),
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class TimerEntity extends Entity {
|
class TimerEntity extends Entity {
|
||||||
TimerEntity(Map rawData, String webHost) : super(rawData, webHost);
|
TimerEntity(Map rawData, String webHost) : super(rawData, webHost);
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class TimerState extends StatefulWidget {
|
class TimerState extends StatefulWidget {
|
||||||
//final bool expanded;
|
//final bool expanded;
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../main.dart';
|
||||||
|
|
||||||
class UniversalSlider extends StatelessWidget {
|
class UniversalSlider extends StatelessWidget {
|
||||||
|
|
98
lib/entities/vacuum/vacuum_entity.class.dart
Normal file
98
lib/entities/vacuum/vacuum_entity.class.dart
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class VacuumEntity extends Entity {
|
||||||
|
|
||||||
|
static const SUPPORT_TURN_ON = 1;
|
||||||
|
static const SUPPORT_TURN_OFF = 2;
|
||||||
|
static const SUPPORT_PAUSE = 4;
|
||||||
|
static const SUPPORT_STOP = 8;
|
||||||
|
static const SUPPORT_RETURN_HOME = 16;
|
||||||
|
static const SUPPORT_FAN_SPEED = 32;
|
||||||
|
static const SUPPORT_BATTERY = 64;
|
||||||
|
static const SUPPORT_STATUS = 128;
|
||||||
|
static const SUPPORT_SEND_COMMAND = 256;
|
||||||
|
static const SUPPORT_LOCATE = 512;
|
||||||
|
static const SUPPORT_CLEAN_SPOT = 1024;
|
||||||
|
static const SUPPORT_MAP = 2048;
|
||||||
|
static const SUPPORT_STATE = 4096;
|
||||||
|
static const SUPPORT_START = 8192;
|
||||||
|
|
||||||
|
VacuumEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
|
bool get supportTurnOn => ((supportedFeatures &
|
||||||
|
VacuumEntity.SUPPORT_TURN_ON) ==
|
||||||
|
VacuumEntity.SUPPORT_TURN_ON);
|
||||||
|
bool get supportTurnOff => ((supportedFeatures &
|
||||||
|
VacuumEntity.SUPPORT_TURN_OFF) ==
|
||||||
|
VacuumEntity.SUPPORT_TURN_OFF);
|
||||||
|
bool get supportPause => ((supportedFeatures &
|
||||||
|
VacuumEntity.SUPPORT_PAUSE) ==
|
||||||
|
VacuumEntity.SUPPORT_PAUSE);
|
||||||
|
bool get supportStop => ((supportedFeatures &
|
||||||
|
VacuumEntity.SUPPORT_STOP) ==
|
||||||
|
VacuumEntity.SUPPORT_STOP);
|
||||||
|
bool get supportReturnHome => ((supportedFeatures &
|
||||||
|
VacuumEntity.SUPPORT_RETURN_HOME) ==
|
||||||
|
VacuumEntity.SUPPORT_RETURN_HOME);
|
||||||
|
bool get supportFanSpeed => ((supportedFeatures &
|
||||||
|
VacuumEntity.SUPPORT_FAN_SPEED) ==
|
||||||
|
VacuumEntity.SUPPORT_FAN_SPEED);
|
||||||
|
bool get supportBattery => ((supportedFeatures &
|
||||||
|
VacuumEntity.SUPPORT_BATTERY) ==
|
||||||
|
VacuumEntity.SUPPORT_BATTERY);
|
||||||
|
bool get supportStatus => ((supportedFeatures &
|
||||||
|
VacuumEntity.SUPPORT_STATUS) ==
|
||||||
|
VacuumEntity.SUPPORT_STATUS);
|
||||||
|
bool get supportSendCommand => ((supportedFeatures &
|
||||||
|
VacuumEntity.SUPPORT_SEND_COMMAND) ==
|
||||||
|
VacuumEntity.SUPPORT_SEND_COMMAND);
|
||||||
|
bool get supportLocate => ((supportedFeatures &
|
||||||
|
VacuumEntity.SUPPORT_LOCATE) ==
|
||||||
|
VacuumEntity.SUPPORT_LOCATE);
|
||||||
|
bool get supportCleanSpot => ((supportedFeatures &
|
||||||
|
VacuumEntity.SUPPORT_CLEAN_SPOT) ==
|
||||||
|
VacuumEntity.SUPPORT_CLEAN_SPOT);
|
||||||
|
bool get supportMap => ((supportedFeatures &
|
||||||
|
VacuumEntity.SUPPORT_MAP) ==
|
||||||
|
VacuumEntity.SUPPORT_MAP);
|
||||||
|
bool get supportState => ((supportedFeatures &
|
||||||
|
VacuumEntity.SUPPORT_STATE) ==
|
||||||
|
VacuumEntity.SUPPORT_STATE);
|
||||||
|
bool get supportStart => ((supportedFeatures &
|
||||||
|
VacuumEntity.SUPPORT_START) ==
|
||||||
|
VacuumEntity.SUPPORT_START);
|
||||||
|
|
||||||
|
List<String> get fanSpeedList => getStringListAttributeValue("fan_speed_list");
|
||||||
|
String get fanSpeed => getAttribute("fan_speed");
|
||||||
|
String get status => getAttribute("status");
|
||||||
|
int get batteryLevel => _getIntAttributeValue("battery_level");
|
||||||
|
String get batteryIcon => getAttribute("battery_icon");
|
||||||
|
double get cleanedArea => _getDoubleAttributeValue("cleaned_area");
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget _buildStatePart(BuildContext context) {
|
||||||
|
if (supportTurnOn || supportTurnOff) {
|
||||||
|
return SwitchStateWidget(
|
||||||
|
domainForService: "vacuum",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return SimpleEntityState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget _buildStatePartForPage(BuildContext context) {
|
||||||
|
return EntityModel(
|
||||||
|
entityWrapper: EntityWrapper(
|
||||||
|
entity: this
|
||||||
|
),
|
||||||
|
child: VacuumStateButton(),
|
||||||
|
handleTap: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||||
|
return VacuumControls();
|
||||||
|
}
|
||||||
|
}
|
239
lib/entities/vacuum/widgets/vacuum_controls.dart
Normal file
239
lib/entities/vacuum/widgets/vacuum_controls.dart
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
part of '../../../main.dart';
|
||||||
|
|
||||||
|
class VacuumControls extends StatelessWidget {
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
VacuumEntity entity = EntityModel.of(context).entityWrapper.entity;
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(left: Sizes.leftWidgetPadding, right: Sizes.rightWidgetPadding),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
_buildStatusAndBattery(entity),
|
||||||
|
_buildCommands(entity),
|
||||||
|
_buildFanSpeed(entity),
|
||||||
|
_buildAdditionalInfo(entity)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStatusAndBattery(VacuumEntity entity) {
|
||||||
|
List<Widget> result = [];
|
||||||
|
if (entity.supportStatus) {
|
||||||
|
result.addAll(
|
||||||
|
<Widget>[
|
||||||
|
Text("Status:", style: TextStyle(fontSize: Sizes.stateFontSize),),
|
||||||
|
Container(width: 6,),
|
||||||
|
Expanded(
|
||||||
|
//flex: 1,
|
||||||
|
child: Text(
|
||||||
|
"${entity.status}",
|
||||||
|
maxLines: 1,
|
||||||
|
softWrap: true,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: Sizes.stateFontSize,
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (entity.supportBattery && entity.batteryLevel != null) {
|
||||||
|
String iconName = entity.batteryIcon ?? "mdi:battery";
|
||||||
|
int batteryLevel = entity.batteryLevel ?? 100;
|
||||||
|
result.addAll(<Widget>[
|
||||||
|
Icon(MaterialDesignIcons.getIconDataFromIconName(iconName)),
|
||||||
|
Container(width: 6,),
|
||||||
|
Text("$batteryLevel %", style: TextStyle(fontSize: Sizes.stateFontSize))
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.isEmpty) {
|
||||||
|
return Container(width: 0, height: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: Sizes.doubleRowPadding),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: result,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCommands(VacuumEntity entity) {
|
||||||
|
List<Widget> commandButtons = [];
|
||||||
|
double iconSize = 32;
|
||||||
|
if (entity.supportStart) {
|
||||||
|
commandButtons.add(
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:play")),
|
||||||
|
iconSize: iconSize,
|
||||||
|
onPressed: () => ConnectionManager().callService(
|
||||||
|
"vacuum",
|
||||||
|
entity.entityId,
|
||||||
|
"start",
|
||||||
|
null
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (entity.supportPause && !entity.supportStart) {
|
||||||
|
commandButtons.add(
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:play-pause")),
|
||||||
|
iconSize: iconSize,
|
||||||
|
onPressed: () => ConnectionManager().callService(
|
||||||
|
"vacuum",
|
||||||
|
entity.entityId,
|
||||||
|
"start_pause",
|
||||||
|
null
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (entity.supportPause) {
|
||||||
|
commandButtons.add(
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:pause")),
|
||||||
|
iconSize: iconSize,
|
||||||
|
onPressed: () => ConnectionManager().callService(
|
||||||
|
"vacuum",
|
||||||
|
entity.entityId,
|
||||||
|
"pause",
|
||||||
|
null
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (entity.supportStop) {
|
||||||
|
commandButtons.add(
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:stop")),
|
||||||
|
iconSize: iconSize,
|
||||||
|
onPressed: () => ConnectionManager().callService(
|
||||||
|
"vacuum",
|
||||||
|
entity.entityId,
|
||||||
|
"stop",
|
||||||
|
null
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (entity.supportCleanSpot) {
|
||||||
|
commandButtons.add(
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:broom")),
|
||||||
|
iconSize: iconSize,
|
||||||
|
onPressed: () => ConnectionManager().callService(
|
||||||
|
"vacuum",
|
||||||
|
entity.entityId,
|
||||||
|
"clean_spot",
|
||||||
|
null
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (entity.supportLocate) {
|
||||||
|
commandButtons.add(
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:map-marker")),
|
||||||
|
iconSize: iconSize,
|
||||||
|
onPressed: () => ConnectionManager().callService(
|
||||||
|
"vacuum",
|
||||||
|
entity.entityId,
|
||||||
|
"locate",
|
||||||
|
null
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (entity.supportReturnHome) {
|
||||||
|
commandButtons.add(
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:home-map-marker")),
|
||||||
|
iconSize: iconSize,
|
||||||
|
onPressed: () => ConnectionManager().callService(
|
||||||
|
"vacuum",
|
||||||
|
entity.entityId,
|
||||||
|
"return_to_base",
|
||||||
|
null
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commandButtons.isEmpty) {
|
||||||
|
return Container(width: 0, height: 0,);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: Sizes.doubleRowPadding),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text("Vacuum cleaner commands:", style: TextStyle(fontSize: Sizes.stateFontSize)),
|
||||||
|
Container(height: Sizes.rowPadding,),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: commandButtons.map((button) => Expanded(
|
||||||
|
child: button,
|
||||||
|
)).toList(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFanSpeed(VacuumEntity entity) {
|
||||||
|
if (entity.supportFanSpeed) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: Sizes.doubleRowPadding),
|
||||||
|
child: ModeSelectorWidget(
|
||||||
|
caption: "Fan speed",
|
||||||
|
options: entity.fanSpeedList,
|
||||||
|
value: entity.fanSpeed,
|
||||||
|
onChange: (val) => ConnectionManager().callService(
|
||||||
|
"vacuum",
|
||||||
|
entity.entityId,
|
||||||
|
"set_fan_speed",
|
||||||
|
{"fan_speed": val}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Container(width: 0, height: 0,);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAdditionalInfo(VacuumEntity entity) {
|
||||||
|
List<Widget> rows = [];
|
||||||
|
if (entity.cleanedArea != null) {
|
||||||
|
rows.add(
|
||||||
|
Text("Cleaned area: ${entity.cleanedArea}")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rows.isEmpty) {
|
||||||
|
return Container(width: 0, height: 0,);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: Sizes.doubleRowPadding),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: rows,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
40
lib/entities/vacuum/widgets/vacuum_state_button.dart
Normal file
40
lib/entities/vacuum/widgets/vacuum_state_button.dart
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
part of '../../../main.dart';
|
||||||
|
|
||||||
|
class VacuumStateButton extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget result;
|
||||||
|
VacuumEntity entity = EntityModel.of(context).entityWrapper.entity;
|
||||||
|
if (entity.supportTurnOn && entity.supportTurnOff) {
|
||||||
|
result = FlatServiceButton(
|
||||||
|
serviceDomain: "vacuum",
|
||||||
|
serviceName: entity.state == EntityState.on ? "turn_off" : "turn_on",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
text: entity.state == EntityState.on ? "TURN OFF" : "TURN ON"
|
||||||
|
);
|
||||||
|
} else if (entity.supportStart && (entity.state == EntityState.docked || entity.state == EntityState.idle)) {
|
||||||
|
result = FlatServiceButton(
|
||||||
|
serviceDomain: "vacuum",
|
||||||
|
serviceName: "start",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
text: "START CLEANING"
|
||||||
|
);
|
||||||
|
} else if (entity.supportReturnHome && entity.state == EntityState.cleaning) {
|
||||||
|
result = FlatServiceButton(
|
||||||
|
serviceDomain: "vacuum",
|
||||||
|
serviceName: "return_to_base",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
text: "RETURN TO DOCK"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
result = Text(entity.state.toUpperCase(), style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.grey
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(right: 15),
|
||||||
|
child: result,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -101,6 +101,9 @@ class EntityCollection {
|
|||||||
case "timer": {
|
case "timer": {
|
||||||
return TimerEntity(rawEntityData, homeAssistantWebHost);
|
return TimerEntity(rawEntityData, homeAssistantWebHost);
|
||||||
}
|
}
|
||||||
|
case "vacuum": {
|
||||||
|
return VacuumEntity(rawEntityData, homeAssistantWebHost);
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return Entity(rawEntityData, homeAssistantWebHost);
|
return Entity(rawEntityData, homeAssistantWebHost);
|
||||||
}
|
}
|
||||||
@ -149,6 +152,13 @@ class EntityCollection {
|
|||||||
return _allEntities[entityId] != null;
|
return _allEntities[entityId] != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Entity> getByDomains({List<String> domains, List<String> stateFiler}) {
|
||||||
|
return _allEntities.values.where((entity) {
|
||||||
|
return domains.contains(entity.domain) &&
|
||||||
|
((stateFiler != null && stateFiler.contains(entity.state)) || stateFiler == null);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
List<Entity> filterEntitiesForDefaultView() {
|
List<Entity> filterEntitiesForDefaultView() {
|
||||||
List<Entity> result = [];
|
List<Entity> result = [];
|
||||||
List<Entity> groups = [];
|
List<Entity> groups = [];
|
||||||
|
@ -1,177 +0,0 @@
|
|||||||
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;
|
|
||||||
String _webHost;
|
|
||||||
|
|
||||||
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 = '$_webHost/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;
|
|
||||||
_webHost = HomeAssistantModel.of(context).homeAssistant.connection.httpWebHost;
|
|
||||||
_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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class EntityPageContainer extends StatelessWidget {
|
|
||||||
EntityPageContainer({Key key, @required this.children}) : super(key: key);
|
|
||||||
|
|
||||||
final List<Widget> children;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ListView(
|
|
||||||
children: children,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,20 +2,24 @@ part of 'main.dart';
|
|||||||
|
|
||||||
class HomeAssistant {
|
class HomeAssistant {
|
||||||
|
|
||||||
final Connection connection = Connection();
|
static final HomeAssistant _instance = HomeAssistant._internal();
|
||||||
|
|
||||||
bool _useLovelace = false;
|
|
||||||
//bool isSettingsLoaded = false;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
factory HomeAssistant() {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
EntityCollection entities;
|
EntityCollection entities;
|
||||||
HomeAssistantUI ui;
|
HomeAssistantUI ui;
|
||||||
Map _instanceConfig = {};
|
Map _instanceConfig = {};
|
||||||
|
Map services;
|
||||||
String _userName;
|
String _userName;
|
||||||
String hostname;
|
bool childMode;
|
||||||
HSVColor savedColor;
|
HSVColor savedColor;
|
||||||
|
int savedPlayerPosition;
|
||||||
|
String sendToPlayerId;
|
||||||
|
String sendFromPlayerId;
|
||||||
|
|
||||||
|
String fcmToken;
|
||||||
|
|
||||||
Map _rawLovelaceData;
|
Map _rawLovelaceData;
|
||||||
|
|
||||||
@ -24,7 +28,7 @@ class HomeAssistant {
|
|||||||
Duration fetchTimeout = Duration(seconds: 30);
|
Duration fetchTimeout = Duration(seconds: 30);
|
||||||
|
|
||||||
String get locationName {
|
String get locationName {
|
||||||
if (_useLovelace) {
|
if (ConnectionManager().useLovelace) {
|
||||||
return ui?.title ?? "";
|
return ui?.title ?? "";
|
||||||
} else {
|
} else {
|
||||||
return _instanceConfig["location_name"] ?? "";
|
return _instanceConfig["location_name"] ?? "";
|
||||||
@ -34,49 +38,43 @@ class HomeAssistant {
|
|||||||
String get userAvatarText => userName.length > 0 ? userName[0] : "";
|
String get userAvatarText => userName.length > 0 ? userName[0] : "";
|
||||||
bool get isNoEntities => entities == null || entities.isEmpty;
|
bool get isNoEntities => entities == null || entities.isEmpty;
|
||||||
bool get isNoViews => ui == null || ui.isEmpty;
|
bool get isNoViews => ui == null || ui.isEmpty;
|
||||||
//int get viewsCount => entities.views.length ?? 0;
|
bool get isMobileAppEnabled => _instanceConfig["components"] != null && (_instanceConfig["components"] as List).contains("mobile_app");
|
||||||
|
|
||||||
HomeAssistant();
|
HomeAssistant._internal() {
|
||||||
|
ConnectionManager().onStateChangeCallback = _handleEntityStateChange;
|
||||||
Completer _connectCompleter;
|
DeviceInfoManager().loadDeviceInfo();
|
||||||
|
|
||||||
Future init() {
|
|
||||||
if (_connectCompleter != null && !_connectCompleter.isCompleted) {
|
|
||||||
Logger.w("Previous connection pending...");
|
|
||||||
return _connectCompleter.future;
|
|
||||||
}
|
|
||||||
Logger.d("init...");
|
|
||||||
_connectCompleter = Completer();
|
|
||||||
connection.init(_handleEntityStateChange).then((_) {
|
|
||||||
SharedPreferences.getInstance().then((prefs) {
|
|
||||||
if (entities == null) entities = EntityCollection(connection.httpWebHost);
|
|
||||||
_useLovelace = prefs.getBool('use-lovelace') ?? true;
|
|
||||||
_connectCompleter.complete();
|
|
||||||
}).catchError((e) => _connectCompleter.completeError(e));
|
|
||||||
}).catchError((e) => _connectCompleter.completeError(e));
|
|
||||||
return _connectCompleter.future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Completer _fetchCompleter;
|
Completer _fetchCompleter;
|
||||||
|
|
||||||
Future fetch() {
|
Future fetchData() {
|
||||||
if (_fetchCompleter != null && !_fetchCompleter.isCompleted) {
|
if (_fetchCompleter != null && !_fetchCompleter.isCompleted) {
|
||||||
Logger.w("Previous data fetch is not completed yet");
|
Logger.w("Previous data fetch is not completed yet");
|
||||||
return _fetchCompleter.future;
|
return _fetchCompleter.future;
|
||||||
}
|
}
|
||||||
|
if (entities == null) entities = EntityCollection(ConnectionManager().httpWebHost);
|
||||||
_fetchCompleter = Completer();
|
_fetchCompleter = Completer();
|
||||||
List<Future> futures = [];
|
List<Future> futures = [];
|
||||||
futures.add(_getStates());
|
futures.add(_getStates());
|
||||||
if (_useLovelace) {
|
if (ConnectionManager().useLovelace) {
|
||||||
futures.add(_getLovelace());
|
futures.add(_getLovelace());
|
||||||
}
|
}
|
||||||
futures.add(_getConfig());
|
futures.add(_getConfig());
|
||||||
futures.add(_getServices());
|
futures.add(_getServices());
|
||||||
futures.add(_getUserInfo());
|
futures.add(_getUserInfo());
|
||||||
futures.add(_getPanels());
|
futures.add(_getPanels());
|
||||||
|
futures.add(ConnectionManager().sendSocketMessage(
|
||||||
|
type: "subscribe_events",
|
||||||
|
additionalData: {"event_type": "state_changed"},
|
||||||
|
));
|
||||||
Future.wait(futures).then((_) {
|
Future.wait(futures).then((_) {
|
||||||
_createUI();
|
if (isMobileAppEnabled) {
|
||||||
_fetchCompleter.complete();
|
if (!childMode) _createUI();
|
||||||
|
_fetchCompleter.complete();
|
||||||
|
MobileAppIntegrationManager.checkAppRegistration();
|
||||||
|
} else {
|
||||||
|
_fetchCompleter.completeError(HAError("Mobile app component not found", actions: [HAErrorAction.tryAgain(), HAErrorAction(type: HAErrorActionType.URL ,title: "Help",url: "http://ha-client.homemade.systems/docs#mobile-app-integration")]));
|
||||||
|
}
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
_fetchCompleter.completeError(e);
|
_fetchCompleter.completeError(e);
|
||||||
});
|
});
|
||||||
@ -85,50 +83,58 @@ class HomeAssistant {
|
|||||||
|
|
||||||
Future logout() async {
|
Future logout() async {
|
||||||
Logger.d("Logging out...");
|
Logger.d("Logging out...");
|
||||||
await connection.logout().then((_) {
|
await ConnectionManager().logout().then((_) {
|
||||||
ui?.clear();
|
ui?.clear();
|
||||||
entities?.clear();
|
entities?.clear();
|
||||||
|
panels?.clear();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getConfig() async {
|
Future _getConfig() async {
|
||||||
await connection.sendSocketMessage(type: "get_config").then((data) {
|
await ConnectionManager().sendSocketMessage(type: "get_config").then((data) {
|
||||||
_instanceConfig = Map.from(data);
|
_instanceConfig = Map.from(data);
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
throw {"errorCode": 1, "errorMessage": "Error getting config: $e"};
|
throw HAError("Error getting config: ${e}");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getStates() async {
|
Future _getStates() async {
|
||||||
await connection.sendSocketMessage(type: "get_states").then(
|
await ConnectionManager().sendSocketMessage(type: "get_states").then(
|
||||||
(data) => entities.parse(data)
|
(data) => entities.parse(data)
|
||||||
).catchError((e) {
|
).catchError((e) {
|
||||||
throw {"errorCode": 1, "errorMessage": "Error getting states: $e"};
|
throw HAError("Error getting states: $e");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getLovelace() async {
|
Future _getLovelace() async {
|
||||||
await connection.sendSocketMessage(type: "lovelace/config").then((data) => _rawLovelaceData = data).catchError((e) {
|
await ConnectionManager().sendSocketMessage(type: "lovelace/config").then((data) => _rawLovelaceData = data).catchError((e) {
|
||||||
throw {"errorCode": 1, "errorMessage": "Error getting lovelace config: $e"};
|
throw HAError("Error getting lovelace config: $e");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getUserInfo() async {
|
Future _getUserInfo() async {
|
||||||
_userName = null;
|
_userName = null;
|
||||||
await connection.sendSocketMessage(type: "auth/current_user").then((data) => _userName = data["name"]).catchError((e) {
|
await ConnectionManager().sendSocketMessage(type: "auth/current_user").then((data) {
|
||||||
Logger.w("Can't get user info: ${e}");
|
_userName = data["name"];
|
||||||
|
childMode = _userName.startsWith("[child]");
|
||||||
|
}).catchError((e) {
|
||||||
|
Logger.w("Can't get user info: $e");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getServices() async {
|
Future _getServices() async {
|
||||||
await connection.sendSocketMessage(type: "get_services").then((data) => Logger.d("Services received")).catchError((e) {
|
await ConnectionManager().sendSocketMessage(type: "get_services").then((data) {
|
||||||
Logger.w("Can't get services: ${e}");
|
Logger.d("Got ${data.length} services");
|
||||||
|
Logger.d("Media extractor: ${data["media_extractor"]}");
|
||||||
|
services = data;
|
||||||
|
}).catchError((e) {
|
||||||
|
Logger.w("Can't get services: $e");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getPanels() async {
|
Future _getPanels() async {
|
||||||
panels.clear();
|
panels.clear();
|
||||||
await connection.sendSocketMessage(type: "get_panels").then((data) {
|
await ConnectionManager().sendSocketMessage(type: "get_panels").then((data) {
|
||||||
data.forEach((k,v) {
|
data.forEach((k,v) {
|
||||||
String title = v['title'] == null ? "${k[0].toUpperCase()}${k.substring(1)}" : "${v['title'][0].toUpperCase()}${v['title'].substring(1)}";
|
String title = v['title'] == null ? "${k[0].toUpperCase()}${k.substring(1)}" : "${v['title'][0].toUpperCase()}${v['title'].substring(1)}";
|
||||||
panels.add(Panel(
|
panels.add(Panel(
|
||||||
@ -142,17 +148,19 @@ class HomeAssistant {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
throw {"errorCode": 1, "errorMessage": "Error getting panels list: $e"};
|
throw HAError("Error getting panels list: $e");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
if (_fetchCompleter.isCompleted) {
|
||||||
eventBus.fire(new StateChangedEvent(
|
Map data = Map.from(eventData);
|
||||||
entityId: data["entity_id"],
|
eventBus.fire(new StateChangedEvent(
|
||||||
needToRebuildUI: entities.updateState(data)
|
entityId: data["entity_id"],
|
||||||
));
|
needToRebuildUI: entities.updateState(data)
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _parseLovelace() {
|
void _parseLovelace() {
|
||||||
@ -166,14 +174,23 @@ class HomeAssistant {
|
|||||||
count: viewCounter,
|
count: viewCounter,
|
||||||
id: "${rawView['id']}",
|
id: "${rawView['id']}",
|
||||||
name: rawView['title'],
|
name: rawView['title'],
|
||||||
iconName: rawView['icon']
|
iconName: rawView['icon'],
|
||||||
|
panel: rawView['panel'] ?? false,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (rawView['badges'] != null && rawView['badges'] is List) {
|
if (rawView['badges'] != null && rawView['badges'] is List) {
|
||||||
rawView['badges'].forEach((entity) {
|
rawView['badges'].forEach((entity) {
|
||||||
if (entities.isExist(entity)) {
|
if (entity is String) {
|
||||||
Entity e = entities.get(entity);
|
if (entities.isExist(entity)) {
|
||||||
view.badges.add(e);
|
Entity e = entities.get(entity);
|
||||||
|
view.badges.add(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String eId = '${entity['entity']}';
|
||||||
|
if (entities.isExist(eId)) {
|
||||||
|
Entity e = entities.get(eId);
|
||||||
|
view.badges.add(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -190,31 +207,30 @@ class HomeAssistant {
|
|||||||
List<HACard> result = [];
|
List<HACard> result = [];
|
||||||
rawCards.forEach((rawCard){
|
rawCards.forEach((rawCard){
|
||||||
try {
|
try {
|
||||||
bool isThereCardOptionsInside = rawCard["card"] != null;
|
//bool isThereCardOptionsInside = rawCard["card"] != null;
|
||||||
|
var rawCardInfo = rawCard["card"] ?? rawCard;
|
||||||
HACard card = HACard(
|
HACard card = HACard(
|
||||||
id: "card",
|
id: "card",
|
||||||
name: isThereCardOptionsInside ? rawCard["card"]["title"] ??
|
name: rawCardInfo["title"] ?? rawCardInfo["name"],
|
||||||
rawCard["card"]["name"] : rawCard["title"] ?? rawCard["name"],
|
type: rawCardInfo['type'] ?? CardType.ENTITIES,
|
||||||
type: isThereCardOptionsInside
|
columnsCount: rawCardInfo['columns'] ?? 4,
|
||||||
? rawCard["card"]['type']
|
showName: rawCardInfo['show_name'] ?? true,
|
||||||
: rawCard['type'],
|
showState: rawCardInfo['show_state'] ?? true,
|
||||||
columnsCount: isThereCardOptionsInside
|
showEmpty: rawCardInfo['show_empty'] ?? true,
|
||||||
? rawCard["card"]['columns'] ?? 4
|
stateFilter: rawCardInfo['state_filter'] ?? [],
|
||||||
: rawCard['columns'] ?? 4,
|
states: rawCardInfo['states'],
|
||||||
showName: isThereCardOptionsInside ? rawCard["card"]['show_name'] ??
|
conditions: rawCard['conditions'] ?? [],
|
||||||
true : rawCard['show_name'] ?? true,
|
content: rawCardInfo['content'],
|
||||||
showState: isThereCardOptionsInside
|
min: rawCardInfo['min'] ?? 0,
|
||||||
? rawCard["card"]['show_state'] ?? true
|
max: rawCardInfo['max'] ?? 100,
|
||||||
: rawCard['show_state'] ?? true,
|
unit: rawCardInfo['unit'],
|
||||||
showEmpty: rawCard['show_empty'] ?? true,
|
severity: rawCardInfo['severity']
|
||||||
stateFilter: rawCard['state_filter'] ?? [],
|
|
||||||
states: rawCard['states'],
|
|
||||||
content: rawCard['content']
|
|
||||||
);
|
);
|
||||||
if (rawCard["cards"] != null) {
|
if (rawCardInfo["cards"] != null) {
|
||||||
card.childCards = _createLovelaceCards(rawCard["cards"]);
|
card.childCards = _createLovelaceCards(rawCardInfo["cards"]);
|
||||||
}
|
}
|
||||||
rawCard["entities"]?.forEach((rawEntity) {
|
var rawEntities = rawCard["entities"] ?? rawCardInfo["entities"];
|
||||||
|
rawEntities?.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)));
|
||||||
@ -277,15 +293,16 @@ class HomeAssistant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (rawCard["entity"] != null) {
|
var rawSingleEntity = rawCard["entity"] ?? rawCardInfo["entity"];
|
||||||
var en = rawCard["entity"];
|
if (rawSingleEntity != null) {
|
||||||
|
var en = rawSingleEntity;
|
||||||
if (en is String) {
|
if (en is String) {
|
||||||
if (entities.isExist(en)) {
|
if (entities.isExist(en)) {
|
||||||
Entity e = entities.get(en);
|
Entity e = entities.get(en);
|
||||||
card.linkedEntityWrapper = EntityWrapper(
|
card.linkedEntityWrapper = EntityWrapper(
|
||||||
entity: e,
|
entity: e,
|
||||||
icon: rawCard["icon"],
|
icon: rawCardInfo["icon"],
|
||||||
displayName: rawCard["name"],
|
displayName: rawCardInfo["name"],
|
||||||
uiAction: EntityUIAction(rawEntityData: rawCard)
|
uiAction: EntityUIAction(rawEntityData: rawCard)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -315,7 +332,7 @@ class HomeAssistant {
|
|||||||
|
|
||||||
void _createUI() {
|
void _createUI() {
|
||||||
ui = HomeAssistantUI();
|
ui = HomeAssistantUI();
|
||||||
if ((_useLovelace) && (_rawLovelaceData != null)) {
|
if ((ConnectionManager().useLovelace) && (_rawLovelaceData != null)) {
|
||||||
Logger.d("Creating Lovelace UI");
|
Logger.d("Creating Lovelace UI");
|
||||||
_parseLovelace();
|
_parseLovelace();
|
||||||
} else {
|
} else {
|
||||||
|
843
lib/main.dart
843
lib/main.dart
@ -1,6 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:typed_data';
|
import 'dart:math';
|
||||||
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';
|
||||||
@ -15,99 +15,137 @@ import 'package:http/http.dart' as http;
|
|||||||
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:progress_indicators/progress_indicators.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
|
||||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
|
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:device_info/device_info.dart';
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
import 'package:in_app_purchase/in_app_purchase.dart';
|
||||||
|
import 'plugins/circular_slider/single_circular_slider.dart';
|
||||||
|
import 'package:share/receive_share_state.dart';
|
||||||
|
import 'package:share/share.dart';
|
||||||
|
import 'plugins/dynamic_multi_column_layout.dart';
|
||||||
|
import 'plugins/spoiler_card.dart';
|
||||||
|
import 'package:uni_links/uni_links.dart';
|
||||||
|
import 'package:workmanager/workmanager.dart' as workManager;
|
||||||
|
import 'package:geolocator/geolocator.dart';
|
||||||
|
import 'package:battery/battery.dart';
|
||||||
|
|
||||||
part 'entity_class/const.dart';
|
import 'utils/logger.dart';
|
||||||
part 'entity_class/entity.class.dart';
|
|
||||||
part 'entity_class/entity_wrapper.class.dart';
|
part 'const.dart';
|
||||||
part 'entity_class/timer_entity.dart';
|
part 'utils/launcher.dart';
|
||||||
part 'entity_class/switch_entity.class.dart';
|
part 'entities/entity.class.dart';
|
||||||
part 'entity_class/button_entity.class.dart';
|
part 'entities/entity_wrapper.class.dart';
|
||||||
part 'entity_class/text_entity.class.dart';
|
part 'entities/timer/timer_entity.class.dart';
|
||||||
part 'entity_class/climate_entity.class.dart';
|
part 'entities/switch/switch_entity.class.dart';
|
||||||
part 'entity_class/cover_entity.class.dart';
|
part 'entities/button/button_entity.class.dart';
|
||||||
part 'entity_class/date_time_entity.class.dart';
|
part 'entities/text/text_entity.class.dart';
|
||||||
part 'entity_class/light_entity.class.dart';
|
part 'entities/climate/climate_entity.class.dart';
|
||||||
part 'entity_class/select_entity.class.dart';
|
part 'entities/cover/cover_entity.class.dart';
|
||||||
part 'entity_class/other_entity.class.dart';
|
part 'entities/date_time/date_time_entity.class.dart';
|
||||||
part 'entity_class/slider_entity.dart';
|
part 'entities/light/light_entity.class.dart';
|
||||||
part 'entity_class/media_player_entity.class.dart';
|
part 'entities/select/select_entity.class.dart';
|
||||||
part 'entity_class/lock_entity.class.dart';
|
part 'entities/sun/sun_entity.class.dart';
|
||||||
part 'entity_class/group_entity.class.dart';
|
part 'entities/sensor/sensor_entity.class.dart';
|
||||||
part 'entity_class/fan_entity.class.dart';
|
part 'entities/slider/slider_entity.dart';
|
||||||
part 'entity_class/automation_entity.dart';
|
part 'entities/media_player/media_player_entity.class.dart';
|
||||||
part 'entity_class/camera_entity.class.dart';
|
part 'entities/lock/lock_entity.class.dart';
|
||||||
part 'entity_class/alarm_control_panel.class.dart';
|
part 'entities/group/group_entity.class.dart';
|
||||||
part 'entity_widgets/common/badge.dart';
|
part 'entities/fan/fan_entity.class.dart';
|
||||||
part 'entity_widgets/model_widgets.dart';
|
part 'entities/automation/automation_entity.class.dart';
|
||||||
part 'entity_widgets/default_entity_container.dart';
|
part 'entities/camera/camera_entity.class.dart';
|
||||||
part 'entity_widgets/missed_entity.dart';
|
part 'entities/alarm_control_panel/alarm_control_panel_entity.class.dart';
|
||||||
part 'entity_widgets/glance_entity_container.dart';
|
part 'entities/badge.widget.dart';
|
||||||
part 'entity_widgets/button_entity_container.dart';
|
part 'entities/entity_model.widget.dart';
|
||||||
part 'entity_widgets/common/entity_attributes_list.dart';
|
part 'entities/default_entity_container.widget.dart';
|
||||||
part 'entity_widgets/entity_icon.dart';
|
part 'entities/missed_entity.widget.dart';
|
||||||
part 'entity_widgets/entity_name.dart';
|
part 'cards/widgets/glance_card_entity_container.dart';
|
||||||
part 'entity_widgets/common/last_updated.dart';
|
part 'cards/widgets/entity_button_card_body.widget.dart';
|
||||||
part 'entity_widgets/common/mode_swicth.dart';
|
part 'pages/widgets/entity_attributes_list.dart';
|
||||||
part 'entity_widgets/common/mode_selector.dart';
|
part 'entities/entity_icon.widget.dart';
|
||||||
part 'entity_widgets/common/universal_slider.dart';
|
part 'entities/entity_name.widget.dart';
|
||||||
part 'entity_widgets/common/flat_service_button.dart';
|
part 'pages/widgets/last_updated.dart';
|
||||||
part 'entity_widgets/common/light_color_picker.dart';
|
part 'entities/climate/widgets/mode_swicth.dart';
|
||||||
part 'entity_widgets/common/camera_stream_view.dart';
|
part 'entities/climate/widgets/mode_selector.dart';
|
||||||
part 'entity_widgets/entity_colors.class.dart';
|
part 'entities/universal_slider.widget.dart';
|
||||||
part 'entity_widgets/entity_page_container.dart';
|
part 'entities/flat_service_button.widget.dart';
|
||||||
part 'entity_widgets/history_chart/entity_history.dart';
|
part 'entities/light/widgets/light_color_picker.dart';
|
||||||
part 'entity_widgets/history_chart/simple_state_history_chart.dart';
|
part 'entities/camera/widgets/camera_stream_view.dart';
|
||||||
part 'entity_widgets/history_chart/numeric_state_history_chart.dart';
|
part 'entities/entity_colors.class.dart';
|
||||||
part 'entity_widgets/history_chart/combined_history_chart.dart';
|
part 'plugins/history_chart/entity_history.dart';
|
||||||
part 'entity_widgets/history_chart/history_control_widget.dart';
|
part 'plugins/history_chart/simple_state_history_chart.dart';
|
||||||
part 'entity_widgets/history_chart/entity_history_moment.dart';
|
part 'plugins/history_chart/numeric_state_history_chart.dart';
|
||||||
part 'entity_widgets/state/switch_state.dart';
|
part 'plugins/history_chart/combined_history_chart.dart';
|
||||||
part 'entity_widgets/controls/slider_controls.dart';
|
part 'plugins/history_chart/history_control_widget.dart';
|
||||||
part 'entity_widgets/state/text_input_state.dart';
|
part 'plugins/history_chart/entity_history_moment.dart';
|
||||||
part 'entity_widgets/state/select_state.dart';
|
part 'entities/switch/widget/switch_state.dart';
|
||||||
part 'entity_widgets/state/simple_state.dart';
|
part 'entities/slider/widgets/slider_controls.dart';
|
||||||
part 'entity_widgets/state/timer_state.dart';
|
part 'entities/text/widgets/text_input_state.dart';
|
||||||
part 'entity_widgets/state/climate_state.dart';
|
part 'entities/select/widgets/select_state.dart';
|
||||||
part 'entity_widgets/state/cover_state.dart';
|
part 'entities/simple_state.widget.dart';
|
||||||
part 'entity_widgets/state/date_time_state.dart';
|
part 'entities/timer/widgets/timer_state.dart';
|
||||||
part 'entity_widgets/state/lock_state.dart';
|
part 'entities/climate/widgets/climate_state.widget.dart';
|
||||||
part 'entity_widgets/controls/climate_controls.dart';
|
part 'entities/cover/widgets/cover_state.dart';
|
||||||
part 'entity_widgets/controls/cover_controls.dart';
|
part 'entities/date_time/widgets/date_time_state.dart';
|
||||||
part 'entity_widgets/controls/light_controls.dart';
|
part 'entities/lock/widgets/lock_state.dart';
|
||||||
part 'entity_widgets/controls/media_player_widgets.dart';
|
part 'entities/climate/widgets/climate_controls.dart';
|
||||||
part 'entity_widgets/controls/fan_controls.dart';
|
part 'entities/climate/widgets/temperature_control_widget.dart';
|
||||||
part 'entity_widgets/controls/alarm_control_panel_controls.dart';
|
part 'entities/cover/widgets/cover_controls.widget.dart';
|
||||||
part 'settings.page.dart';
|
part 'entities/light/widgets/light_controls.dart';
|
||||||
part 'panel.page.dart';
|
part 'entities/media_player/widgets/media_player_widgets.dart';
|
||||||
|
part 'entities/fan/widgets/fan_controls.dart';
|
||||||
|
part 'entities/alarm_control_panel/widgets/alarm_control_panel_controls.widget.dart';
|
||||||
|
part 'entities/vacuum/vacuum_entity.class.dart';
|
||||||
|
part 'entities/vacuum/widgets/vacuum_controls.dart';
|
||||||
|
part 'entities/vacuum/widgets/vacuum_state_button.dart';
|
||||||
|
part 'pages/settings.page.dart';
|
||||||
|
part 'pages/purchase.page.dart';
|
||||||
|
part 'pages/widgets/product_purchase.widget.dart';
|
||||||
|
part 'pages/widgets/page_loading_indicator.dart';
|
||||||
|
part 'pages/widgets/page_loading_error.dart';
|
||||||
|
part 'pages/panel.page.dart';
|
||||||
|
part 'pages/main.page.dart';
|
||||||
|
part 'pages/integration_settings.page.dart';
|
||||||
part 'home_assistant.class.dart';
|
part 'home_assistant.class.dart';
|
||||||
part 'log.page.dart';
|
part 'pages/log.page.dart';
|
||||||
part 'entity.page.dart';
|
part 'pages/entity.page.dart';
|
||||||
part 'utils.class.dart';
|
part 'utils/mdi.class.dart';
|
||||||
part 'mdi.class.dart';
|
|
||||||
part 'entity_collection.class.dart';
|
part 'entity_collection.class.dart';
|
||||||
part 'auth_manager.class.dart';
|
part 'managers/auth_manager.class.dart';
|
||||||
part 'connection.class.dart';
|
part 'managers/location_manager.class.dart';
|
||||||
part 'ui_class/ui.dart';
|
part 'managers/mobile_app_integration_manager.class.dart';
|
||||||
part 'ui_class/view.class.dart';
|
part 'managers/connection_manager.class.dart';
|
||||||
part 'ui_class/card.class.dart';
|
part 'managers/device_info_manager.class.dart';
|
||||||
part 'ui_class/sizes_class.dart';
|
part 'managers/startup_user_messages_manager.class.dart';
|
||||||
part 'ui_class/panel_class.dart';
|
part 'ui.dart';
|
||||||
part 'ui_widgets/view.dart';
|
part 'view.class.dart';
|
||||||
part 'ui_widgets/card_widget.dart';
|
part 'cards/card.class.dart';
|
||||||
part 'ui_widgets/card_header_widget.dart';
|
part 'panels/panel_class.dart';
|
||||||
part 'ui_widgets/config_panel_widget.dart';
|
part 'viewWidget.widget.dart';
|
||||||
|
part 'cards/card_widget.dart';
|
||||||
|
part 'cards/widgets/card_header.widget.dart';
|
||||||
|
part 'panels/config_panel_widget.dart';
|
||||||
|
part 'panels/widgets/link_to_web_config.dart';
|
||||||
|
part 'types/ha_error.dart';
|
||||||
|
part 'types/event_bus_events.dart';
|
||||||
|
part 'cards/widgets/gauge_card_body.dart';
|
||||||
|
part 'cards/widgets/light_card_body.dart';
|
||||||
|
part 'pages/play_media.page.dart';
|
||||||
|
part 'entities/entity_page_layout.widget.dart';
|
||||||
|
part 'entities/media_player/widgets/media_player_seek_bar.widget.dart';
|
||||||
|
part 'entities/media_player/widgets/media_player_progress_bar.widget.dart';
|
||||||
|
part 'pages/whats_new.page.dart';
|
||||||
|
|
||||||
EventBus eventBus = new EventBus();
|
EventBus eventBus = new EventBus();
|
||||||
|
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
|
||||||
|
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
|
||||||
const String appName = "HA Client";
|
const String appName = "HA Client";
|
||||||
const appVersion = "0.6.0-alpha1";
|
const appVersionNumber = "0.7.2";
|
||||||
|
const appVersionAdd = "";
|
||||||
|
const appVersion = "$appVersionNumber-$appVersionAdd";
|
||||||
|
|
||||||
void main() {
|
void main() async {
|
||||||
FlutterError.onError = (errorDetails) {
|
FlutterError.onError = (errorDetails) {
|
||||||
Logger.e( "${errorDetails.exception}");
|
Logger.e( "${errorDetails.exception}");
|
||||||
if (Logger.isInDebugMode) {
|
if (Logger.isInDebugMode) {
|
||||||
@ -116,7 +154,12 @@ void main() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
runZoned(() {
|
runZoned(() {
|
||||||
runApp(new HAClientApp());
|
workManager.Workmanager.initialize(
|
||||||
|
updateDeviceLocationIsolate,
|
||||||
|
isInDebugMode: false
|
||||||
|
);
|
||||||
|
runApp(new HAClientApp());
|
||||||
|
|
||||||
}, onError: (error, stack) {
|
}, onError: (error, stack) {
|
||||||
Logger.e("$error");
|
Logger.e("$error");
|
||||||
Logger.e("$stack");
|
Logger.e("$stack");
|
||||||
@ -128,7 +171,6 @@ void main() {
|
|||||||
|
|
||||||
class HAClientApp extends StatelessWidget {
|
class HAClientApp extends StatelessWidget {
|
||||||
|
|
||||||
final HomeAssistant homeAssistant = HomeAssistant();
|
|
||||||
// This widget is the root of your application.
|
// This widget is the root of your application.
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -139,628 +181,17 @@ class HAClientApp extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
initialRoute: "/",
|
initialRoute: "/",
|
||||||
routes: {
|
routes: {
|
||||||
"/": (context) => MainPage(title: 'HA Client', homeAssistant: homeAssistant,),
|
"/": (context) => MainPage(title: 'HA Client'),
|
||||||
"/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"),
|
"/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"),
|
||||||
"/configuration": (context) => PanelPage(title: "Configuration"),
|
"/integration-settings": (context) => IntegrationSettingsPage(title: "Integration settings"),
|
||||||
"/log-view": (context) => LogViewPage(title: "Log")
|
"/putchase": (context) => PurchasePage(title: "Support app development"),
|
||||||
|
"/play-media": (context) => PlayMediaPage(
|
||||||
|
mediaUrl: "${ModalRoute.of(context).settings.arguments != null ? (ModalRoute.of(context).settings.arguments as Map)['url'] : ''}",
|
||||||
|
mediaType: "${ModalRoute.of(context).settings.arguments != null ? (ModalRoute.of(context).settings.arguments as Map)['type'] ?? '' : ''}",
|
||||||
|
),
|
||||||
|
"/log-view": (context) => LogViewPage(title: "Log"),
|
||||||
|
"/whats-new": (context) => WhatsNewPage()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainPage extends StatefulWidget {
|
|
||||||
MainPage({Key key, this.title, this.homeAssistant}) : super(key: key);
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
final HomeAssistant homeAssistant;
|
|
||||||
|
|
||||||
@override
|
|
||||||
_MainPageState createState() => new _MainPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MainPageState extends State<MainPage> with WidgetsBindingObserver, TickerProviderStateMixin {
|
|
||||||
|
|
||||||
StreamSubscription _stateSubscription;
|
|
||||||
StreamSubscription _settingsSubscription;
|
|
||||||
StreamSubscription _serviceCallSubscription;
|
|
||||||
StreamSubscription _showEntityPageSubscription;
|
|
||||||
StreamSubscription _showErrorSubscription;
|
|
||||||
StreamSubscription _startAuthSubscription;
|
|
||||||
StreamSubscription _reloadUISubscription;
|
|
||||||
int _previousViewCount;
|
|
||||||
//final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
//widget.homeAssistant = HomeAssistant();
|
|
||||||
//_settingsLoaded = false;
|
|
||||||
WidgetsBinding.instance.addObserver(this);
|
|
||||||
|
|
||||||
_settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) {
|
|
||||||
Logger.d("Settings change event: reconnect=${event.reconnect}");
|
|
||||||
if (event.reconnect) {
|
|
||||||
_reLoad();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_initialLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _initialLoad() {
|
|
||||||
_showInfoBottomBar(progress: true,);
|
|
||||||
_subscribe();
|
|
||||||
widget.homeAssistant.init().then((_){
|
|
||||||
_fetchData();
|
|
||||||
}, onError: (e) {
|
|
||||||
_setErrorState(e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _reLoad() {
|
|
||||||
_hideBottomBar();
|
|
||||||
_showInfoBottomBar(progress: true,);
|
|
||||||
widget.homeAssistant.init().then((_){
|
|
||||||
_fetchData();
|
|
||||||
}, onError: (e) {
|
|
||||||
_setErrorState(e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_fetchData() async {
|
|
||||||
await widget.homeAssistant.fetch().then((_) {
|
|
||||||
_hideBottomBar();
|
|
||||||
int currentViewCount = widget.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) {
|
|
||||||
_setErrorState(e);
|
|
||||||
});
|
|
||||||
eventBus.fire(RefreshDataFinishedEvent());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
||||||
Logger.d("$state");
|
|
||||||
if (state == AppLifecycleState.resumed) {
|
|
||||||
_reLoad();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_subscribe() {
|
|
||||||
if (_stateSubscription == null) {
|
|
||||||
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
|
|
||||||
if (event.needToRebuildUI) {
|
|
||||||
Logger.d("New entity. Need to rebuild UI");
|
|
||||||
_reLoad();
|
|
||||||
} else {
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (_reloadUISubscription == null) {
|
|
||||||
_reloadUISubscription = eventBus.on<ReloadUIEvent>().listen((event){
|
|
||||||
_reLoad();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (_serviceCallSubscription == null) {
|
|
||||||
_serviceCallSubscription =
|
|
||||||
eventBus.on<ServiceCallEvent>().listen((event) {
|
|
||||||
_callService(event.domain, event.service, event.entityId,
|
|
||||||
event.additionalParams);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_showEntityPageSubscription == null) {
|
|
||||||
_showEntityPageSubscription =
|
|
||||||
eventBus.on<ShowEntityPageEvent>().listen((event) {
|
|
||||||
_showEntityPage(event.entity.entityId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_showErrorSubscription == null) {
|
|
||||||
_showErrorSubscription = eventBus.on<ShowErrorEvent>().listen((event){
|
|
||||||
_showErrorBottomBar(message: event.text, errorCode: event.errorCode);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_startAuthSubscription == null) {
|
|
||||||
_startAuthSubscription = eventBus.on<StartAuthEvent>().listen((event){
|
|
||||||
_showOAuth();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*_firebaseMessaging.getToken().then((String token) {
|
|
||||||
//Logger.d("FCM token: $token");
|
|
||||||
widget.homeAssistant.sendHTTPPost(
|
|
||||||
endPoint: '/api/notify.fcm-android',
|
|
||||||
jsonData: '{"token": "$token"}'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
_firebaseMessaging.configure(
|
|
||||||
onLaunch: (data) {
|
|
||||||
Logger.d("Notification [onLaunch]: $data");
|
|
||||||
},
|
|
||||||
onMessage: (data) {
|
|
||||||
Logger.d("Notification [onMessage]: $data");
|
|
||||||
},
|
|
||||||
onResume: (data) {
|
|
||||||
Logger.d("Notification [onResume]: $data");
|
|
||||||
}
|
|
||||||
);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showOAuth() {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => WebviewScaffold(
|
|
||||||
url: "${widget.homeAssistant.connection.oauthUrl}",
|
|
||||||
appBar: new AppBar(
|
|
||||||
leading: IconButton(
|
|
||||||
icon: Icon(Icons.help),
|
|
||||||
onPressed: () => HAUtils.launchURLInCustomTab(context, "http://ha-client.homemade.systems/docs#authentication")
|
|
||||||
),
|
|
||||||
title: new Text("Login to your Home Assistant"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_setErrorState(e) {
|
|
||||||
if (e is Error) {
|
|
||||||
Logger.e(e.toString());
|
|
||||||
Logger.e("${e.stackTrace}");
|
|
||||||
_showErrorBottomBar(
|
|
||||||
message: "Unknown error",
|
|
||||||
errorCode: 13
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
_showErrorBottomBar(
|
|
||||||
message: e != null ? e["errorMessage"] ?? "$e" : "Unknown error",
|
|
||||||
errorCode: e["errorCode"] != null ? e["errorCode"] : 99
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _callService(String domain, String service, String entityId, Map additionalParams) {
|
|
||||||
_showInfoBottomBar(
|
|
||||||
message: "Calling $domain.$service",
|
|
||||||
duration: Duration(seconds: 3)
|
|
||||||
);
|
|
||||||
widget.homeAssistant.connection.callService(domain: domain, service: service, entityId: entityId, additionalServiceData: additionalParams).catchError((e) => _setErrorState(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showEntityPage(String entityId) {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => EntityViewPage(entityId: entityId, homeAssistant: widget.homeAssistant),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Tab> buildUIViewTabs() {
|
|
||||||
List<Tab> result = [];
|
|
||||||
|
|
||||||
if (widget.homeAssistant.ui.views.isNotEmpty) {
|
|
||||||
widget.homeAssistant.ui.views.forEach((HAView view) {
|
|
||||||
result.add(view.buildTab());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Drawer _buildAppDrawer() {
|
|
||||||
List<Widget> menuItems = [];
|
|
||||||
menuItems.add(
|
|
||||||
UserAccountsDrawerHeader(
|
|
||||||
accountName: Text(widget.homeAssistant.userName),
|
|
||||||
accountEmail: Text(widget.homeAssistant.hostname ?? "Not configured"),
|
|
||||||
/*onDetailsPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_accountMenuExpanded = !_accountMenuExpanded;
|
|
||||||
});
|
|
||||||
},*/
|
|
||||||
currentAccountPicture: CircleAvatar(
|
|
||||||
child: Text(
|
|
||||||
widget.homeAssistant.userAvatarText,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 32.0
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (widget.homeAssistant.panels.isNotEmpty) {
|
|
||||||
widget.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)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//TODO check for loaded
|
|
||||||
menuItems.add(
|
|
||||||
new ListTile(
|
|
||||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:home-assistant")),
|
|
||||||
title: Text("Open Web UI"),
|
|
||||||
onTap: () => HAUtils.launchURL(widget.homeAssistant.connection.httpWebHost),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
menuItems.addAll([
|
|
||||||
Divider(),
|
|
||||||
ListTile(
|
|
||||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:login-variant")),
|
|
||||||
title: Text("Connection settings"),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Navigator.of(context).pushNamed('/connection-settings', arguments: {"homeAssistant", widget.homeAssistant});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
menuItems.addAll([
|
|
||||||
Divider(),
|
|
||||||
new ListTile(
|
|
||||||
leading: Icon(Icons.insert_drive_file),
|
|
||||||
title: Text("Log"),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Navigator.of(context).pushNamed('/log-view');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
new ListTile(
|
|
||||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:github-circle")),
|
|
||||||
title: Text("Report an issue"),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
HAUtils.launchURL("https://github.com/estevez-dev/ha_client/issues/new");
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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(
|
|
||||||
aboutBoxChildren: <Widget>[
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
HAUtils.launchURL("http://ha-client.homemade.systems/");
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
"ha-client.homemade.systems",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.blue,
|
|
||||||
decoration: TextDecoration.underline
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
applicationName: appName,
|
|
||||||
applicationVersion: appVersion
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
return new Drawer(
|
|
||||||
child: ListView(
|
|
||||||
children: menuItems,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _hideBottomBar() {
|
|
||||||
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
|
||||||
setState(() {
|
|
||||||
_showBottomBar = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _bottomBarAction;
|
|
||||||
bool _showBottomBar = false;
|
|
||||||
String _bottomBarText;
|
|
||||||
bool _bottomBarProgress;
|
|
||||||
Color _bottomBarColor;
|
|
||||||
Timer _bottomBarTimer;
|
|
||||||
|
|
||||||
void _showInfoBottomBar({String message, bool progress: false, Duration duration}) {
|
|
||||||
_bottomBarTimer?.cancel();
|
|
||||||
_bottomBarAction = Container(height: 0.0, width: 0.0,);
|
|
||||||
_bottomBarColor = Colors.grey.shade50;
|
|
||||||
setState(() {
|
|
||||||
_bottomBarText = message;
|
|
||||||
_bottomBarProgress = progress;
|
|
||||||
_showBottomBar = true;
|
|
||||||
});
|
|
||||||
if (duration != null) {
|
|
||||||
_bottomBarTimer = Timer(duration, () {
|
|
||||||
_hideBottomBar();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showErrorBottomBar({Key key, @required String message, @required int errorCode}) {
|
|
||||||
TextStyle textStyle = TextStyle(
|
|
||||||
color: Colors.blue,
|
|
||||||
fontSize: Sizes.nameFontSize
|
|
||||||
);
|
|
||||||
_bottomBarColor = Colors.red.shade100;
|
|
||||||
switch (errorCode) {
|
|
||||||
case 9:
|
|
||||||
case 11:
|
|
||||||
case 7:
|
|
||||||
case 1: {
|
|
||||||
_bottomBarAction = FlatButton(
|
|
||||||
child: Text("Retry", style: textStyle),
|
|
||||||
onPressed: () {
|
|
||||||
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
|
||||||
_reLoad();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 5: {
|
|
||||||
message = "Check connection settings";
|
|
||||||
_bottomBarAction = FlatButton(
|
|
||||||
child: Text("Open", style: textStyle),
|
|
||||||
onPressed: () {
|
|
||||||
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
|
||||||
Navigator.pushNamed(context, '/connection-settings');
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 60: {
|
|
||||||
_bottomBarAction = FlatButton(
|
|
||||||
child: Text("Login", style: textStyle),
|
|
||||||
onPressed: () {
|
|
||||||
_reLoad();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 63:
|
|
||||||
case 61: {
|
|
||||||
_bottomBarAction = FlatButton(
|
|
||||||
child: Text("Try again", style: textStyle),
|
|
||||||
onPressed: () {
|
|
||||||
_reLoad();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 62: {
|
|
||||||
_bottomBarAction = FlatButton(
|
|
||||||
child: Text("Login again", style: textStyle),
|
|
||||||
onPressed: () {
|
|
||||||
_reLoad();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 10: {
|
|
||||||
_bottomBarAction = FlatButton(
|
|
||||||
child: Text("Refresh", style: textStyle),
|
|
||||||
onPressed: () {
|
|
||||||
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
|
||||||
_reLoad();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 82:
|
|
||||||
case 81:
|
|
||||||
case 8: {
|
|
||||||
_bottomBarAction = FlatButton(
|
|
||||||
child: Text("Reconnect", style: textStyle),
|
|
||||||
onPressed: () {
|
|
||||||
_reLoad();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
_bottomBarAction = Container(width: 0.0, height: 0.0,);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_bottomBarProgress = false;
|
|
||||||
_bottomBarText = "$message";
|
|
||||||
_showBottomBar = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
|
||||||
|
|
||||||
Widget _buildScaffoldBody(bool empty) {
|
|
||||||
List<PopupMenuItem<String>> popupMenuItems = [];
|
|
||||||
popupMenuItems.add(PopupMenuItem<String>(
|
|
||||||
child: new Text("Reload"),
|
|
||||||
value: "reload",
|
|
||||||
));
|
|
||||||
if (widget.homeAssistant.connection.isAuthenticated) {
|
|
||||||
popupMenuItems.add(
|
|
||||||
PopupMenuItem<String>(
|
|
||||||
child: new Text("Logout"),
|
|
||||||
value: "logout",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return NestedScrollView(
|
|
||||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
|
||||||
return <Widget>[
|
|
||||||
SliverAppBar(
|
|
||||||
floating: true,
|
|
||||||
pinned: true,
|
|
||||||
primary: true,
|
|
||||||
title: Text(widget.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: popupMenuItems
|
|
||||||
).then((String val) {
|
|
||||||
if (val == "reload") {
|
|
||||||
_reLoad();
|
|
||||||
} else if (val == "logout") {
|
|
||||||
widget.homeAssistant.logout().then((_) {
|
|
||||||
_reLoad();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
)
|
|
||||||
],
|
|
||||||
leading: IconButton(
|
|
||||||
icon: Icon(Icons.menu),
|
|
||||||
onPressed: () {
|
|
||||||
_scaffoldKey.currentState.openDrawer();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
bottom: empty ? null : TabBar(
|
|
||||||
controller: _viewsTabController,
|
|
||||||
tabs: buildUIViewTabs(),
|
|
||||||
isScrollable: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
];
|
|
||||||
},
|
|
||||||
body: empty ?
|
|
||||||
Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
MaterialDesignIcons.getIconDataFromIconName("mdi:border-none-variant"),
|
|
||||||
size: 100.0,
|
|
||||||
color: Colors.black26,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
:
|
|
||||||
widget.homeAssistant.buildViews(context, _viewsTabController),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TabController _viewsTabController;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
Widget bottomBar;
|
|
||||||
if (_showBottomBar) {
|
|
||||||
List<Widget> bottomBarChildren = [];
|
|
||||||
if (_bottomBarText != null) {
|
|
||||||
bottomBarChildren.add(
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(
|
|
||||||
Sizes.leftWidgetPadding, Sizes.rowPadding, 0.0,
|
|
||||||
Sizes.rowPadding),
|
|
||||||
child: Text(
|
|
||||||
"$_bottomBarText",
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
softWrap: true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (_bottomBarProgress) {
|
|
||||||
bottomBarChildren.add(
|
|
||||||
CollectionScaleTransition(
|
|
||||||
children: <Widget>[
|
|
||||||
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.on),),
|
|
||||||
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.unavailable),),
|
|
||||||
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.off),),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (bottomBarChildren.isNotEmpty) {
|
|
||||||
bottomBar = Container(
|
|
||||||
color: _bottomBarColor,
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: _bottomBarProgress ? CrossAxisAlignment.center : CrossAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: bottomBarChildren,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_bottomBarAction
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This method is rerun every time setState is called.
|
|
||||||
if (widget.homeAssistant.isNoViews) {
|
|
||||||
return Scaffold(
|
|
||||||
key: _scaffoldKey,
|
|
||||||
primary: false,
|
|
||||||
drawer: _buildAppDrawer(),
|
|
||||||
bottomNavigationBar: bottomBar,
|
|
||||||
body: _buildScaffoldBody(true)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Scaffold(
|
|
||||||
key: _scaffoldKey,
|
|
||||||
drawer: _buildAppDrawer(),
|
|
||||||
primary: false,
|
|
||||||
bottomNavigationBar: bottomBar,
|
|
||||||
body: HomeAssistantModel(
|
|
||||||
child: _buildScaffoldBody(false),
|
|
||||||
homeAssistant: widget.homeAssistant
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
final flutterWebviewPlugin = new FlutterWebviewPlugin();
|
|
||||||
flutterWebviewPlugin.dispose();
|
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
|
||||||
_viewsTabController?.dispose();
|
|
||||||
_stateSubscription?.cancel();
|
|
||||||
_settingsSubscription?.cancel();
|
|
||||||
_serviceCallSubscription?.cancel();
|
|
||||||
_showEntityPageSubscription?.cancel();
|
|
||||||
_showErrorSubscription?.cancel();
|
|
||||||
_startAuthSubscription?.cancel();
|
|
||||||
_reloadUISubscription?.cancel();
|
|
||||||
//TODO disconnect
|
|
||||||
//widget.homeAssistant?.disconnect();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
54
lib/managers/auth_manager.class.dart
Normal file
54
lib/managers/auth_manager.class.dart
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class AuthManager {
|
||||||
|
|
||||||
|
static final AuthManager _instance = AuthManager._internal();
|
||||||
|
|
||||||
|
factory AuthManager() {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthManager._internal();
|
||||||
|
StreamSubscription deepLinksSubscription;
|
||||||
|
|
||||||
|
Future start({String oauthUrl}) {
|
||||||
|
Completer completer = Completer();
|
||||||
|
deepLinksSubscription?.cancel();
|
||||||
|
deepLinksSubscription = getUriLinksStream().listen((Uri uri) {
|
||||||
|
Logger.d("[LINKED AUTH] We got something private");
|
||||||
|
_getTempToken(oauthUrl, uri.queryParameters["code"])
|
||||||
|
.then((tempToken) => completer.complete(tempToken))
|
||||||
|
.catchError((_){
|
||||||
|
completer.completeError(HAError("Auth error"));
|
||||||
|
});
|
||||||
|
}, onError: (err) {
|
||||||
|
Logger.e("[LINKED AUTH] Error handling linked auth: $e");
|
||||||
|
completer.completeError(HAError("Auth error"));
|
||||||
|
});
|
||||||
|
Logger.d("Launching OAuth");
|
||||||
|
eventBus.fire(StartAuthEvent(oauthUrl, true));
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _getTempToken(String oauthUrl,String authCode) {
|
||||||
|
Completer completer = Completer();
|
||||||
|
ConnectionManager().sendHTTPPost(
|
||||||
|
endPoint: "/auth/token",
|
||||||
|
contentType: "application/x-www-form-urlencoded",
|
||||||
|
includeAuthHeader: false,
|
||||||
|
data: "grant_type=authorization_code&code=$authCode&client_id=${Uri.encodeComponent('http://ha-client.homemade.systems')}"
|
||||||
|
).then((response) {
|
||||||
|
Logger.d("Got temp token");
|
||||||
|
String tempToken = json.decode(response)['access_token'];
|
||||||
|
eventBus.fire(StartAuthEvent(oauthUrl, false));
|
||||||
|
completer.complete(tempToken);
|
||||||
|
}).catchError((e) {
|
||||||
|
//flutterWebviewPlugin.close();
|
||||||
|
Logger.e("Error getting temp token: ${e.toString()}");
|
||||||
|
eventBus.fire(StartAuthEvent(oauthUrl, false));
|
||||||
|
completer.completeError(HAError("Error getting temp token"));
|
||||||
|
});
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user