Compare commits
474 Commits
0.5.3
...
beta/0.8.5
Author | SHA1 | Date | |
---|---|---|---|
7d746fd546 | |||
3ff55f181e | |||
187e12dd79 | |||
10daf2d952 | |||
31c6509d13 | |||
cb74108814 | |||
9efded2139 | |||
96b3e7c739 | |||
b029146bf3 | |||
d715aaf5e5 | |||
0dc12963f0 | |||
4da3b40d55 | |||
f7d05a57ad | |||
df01599fe0 | |||
2c3335ebf3 | |||
05c1427aa8 | |||
02bfaf7db6 | |||
f488c0810b | |||
8dbfb91234 | |||
aee99e3925 | |||
50d3280803 | |||
a90eb5c4db | |||
16c06a2d48 | |||
513bf85cae | |||
82d7aeba02 | |||
12f7cb86de | |||
b65c885467 | |||
2a828a1289 | |||
291f12ba97 | |||
6afbd37d71 | |||
0e8869878f | |||
7c2cfe3215 | |||
c376c0e952 | |||
da5f663396 | |||
0e92418a33 | |||
2eef7cfe5e | |||
de4e0bfb3a | |||
8bf2d31e72 | |||
2125c46143 | |||
5402eb84df | |||
ad5aa0898f | |||
040d40b614 | |||
8e58f22c56 | |||
c91695fbe5 | |||
c43741da49 | |||
f2563a0397 | |||
fba4017819 | |||
5f23e108a1 | |||
68d14bd13d | |||
022622522f | |||
89513ca4e5 | |||
a934ee2335 | |||
49aeea634f | |||
e18b9ebe14 | |||
08ee3f3d80 | |||
62d07bf8b9 | |||
ab398cbdc3 | |||
007d12719c | |||
524d195800 | |||
405de64249 | |||
f53554702e | |||
379e1a4a7e | |||
d6f7096055 | |||
37c721e4f6 | |||
d94235ef6d | |||
eb4184713f | |||
a0a0cb4612 | |||
f448a20784 | |||
36eff26862 | |||
5b2a1163b9 | |||
e627a8b963 | |||
4432124e8c | |||
b8ba3c59e9 | |||
c40a496b6b | |||
a7c3b46061 | |||
dfbaaeb06b | |||
f6ab20c6e8 | |||
7625099d74 | |||
32c8e76855 | |||
0aa2c974d5 | |||
9524c8587b | |||
c075db8b1a | |||
d0b7cc1929 | |||
d8df32f140 | |||
293b5e0242 | |||
2f517a3ad5 | |||
56d8e389db | |||
1377843350 | |||
8e31eaf8bb | |||
5ced01463f | |||
a3548455eb | |||
c40fceea4f | |||
6ad3938a91 | |||
bc642f81ad | |||
14ce608bbb | |||
c4c67747c5 | |||
5b3ceecb0e | |||
bf53e4b9df | |||
7e09d92fdf | |||
1ba9106d0b | |||
d727a29991 | |||
c5d617477f | |||
244a1984cc | |||
b00b745f27 | |||
959ff21b9b | |||
e6a7fd2dfe | |||
216276e5f3 | |||
3e6229cf3e | |||
fc4cb80b74 | |||
b907ff1e82 | |||
7536a52771 | |||
73a8c111d1 | |||
86a19eeec2 | |||
fba4459977 | |||
06f994a827 | |||
35d8607484 | |||
2f4c06e9b5 | |||
92e008a380 | |||
14c272af92 | |||
710de9f2b8 | |||
d9ad3b3083 | |||
b2686cb105 | |||
959e89de2b | |||
6e448d3458 | |||
6695756727 | |||
ed732e9b77 | |||
f495a6affc | |||
c8d7e1a95f | |||
e1ca2638e3 | |||
01226cb9eb | |||
8a80d0c5d1 | |||
f26f3e87c7 | |||
b750417415 | |||
2c35dd7c21 | |||
cff4a4feed | |||
62174b0651 | |||
d3ea4210c1 | |||
1c782bf64d | |||
bc96dab339 | |||
0f7179b944 | |||
1e3bfa8ff7 | |||
2bce86f905 | |||
0be00acc3a | |||
4e61adaeb1 | |||
49a8f08153 | |||
ce15658462 | |||
16d73ba7dd | |||
9f3e3c1917 | |||
f29e382a19 | |||
073562373a | |||
4298ebcd66 | |||
a121295bef | |||
9303e4c0a5 | |||
831fc98ab1 | |||
2003005e56 | |||
fda8fb7182 | |||
cf6039b279 | |||
41e552dce5 | |||
90043b5806 | |||
9eb74b5a8d | |||
9cc60a136b | |||
78eb1e779c | |||
8db2d8508e | |||
3f1ece26ec | |||
d1912a44c6 | |||
36a05eb390 | |||
4f39ea1ad8 | |||
a241cc1d61 | |||
8b4df98cb9 | |||
7d30c2f9d5 | |||
44acabadfe | |||
6f3a2bb78d | |||
dd5f8b155d | |||
cd81fc72fd | |||
890da650dc | |||
9897b6a44b | |||
7969f54d3b | |||
7c18454de3 | |||
dcf5efddd1 | |||
a6541134e0 | |||
90504047b4 | |||
ca1eec6602 | |||
edc01d14b7 | |||
6cb5463b13 | |||
63a789ebfb | |||
a0994e9a60 | |||
8d1b728194 | |||
1a9fec8b98 | |||
e634253282 | |||
64b23ec7cc | |||
afe207a878 | |||
4bac0c092f | |||
74c8ae35a1 | |||
7856637456 | |||
965f80a6ca | |||
198c2ba49a | |||
4b9ec5ca6e | |||
5792652619 | |||
2c900333a5 | |||
1f782d7cd3 | |||
89cc1833de | |||
1262d8c9aa | |||
85b0c4f814 | |||
551a8dfa31 | |||
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 | |||
a7cda2a35e | |||
102b10ade0 | |||
4e96b9adbb | |||
b9581d3762 | |||
7c010359c3 | |||
4a75243994 | |||
d29d7e5b3b | |||
5ebd25e0d1 | |||
b7d5a53e86 | |||
20d3498bfd | |||
67d7bb45f5 | |||
6a03105d01 | |||
5ae580ecf1 | |||
0efef33e53 | |||
ccb88884a7 | |||
d70ba0a55a | |||
5140840d3a | |||
14759fd3c9 | |||
fed35be517 |
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help improve HA Client
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**HA Client version:** [Main menu -> About HA Client]
|
||||||
|
|
||||||
|
**Home Assistant version:**
|
||||||
|
|
||||||
|
**Device name:**
|
||||||
|
|
||||||
|
**Android version:**
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
[Replace with description]
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
[Replace with screenshots]
|
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.
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -9,5 +9,14 @@ build/
|
|||||||
.flutter-plugins
|
.flutter-plugins
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.theia/
|
||||||
|
.project/
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
flutter_export_environment.sh
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
|
||||||
key.properties
|
key.properties
|
||||||
|
.secrets.dart
|
||||||
|
pubspec.lock
|
||||||
|
8
.gitpod.dockerfile
Normal file
8
.gitpod.dockerfile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
FROM gitpod/workspace-full:latest
|
||||||
|
|
||||||
|
ENV ANDROID_HOME=/workspace/android-sdk \
|
||||||
|
FLUTTER_ROOT=/workspace/flutter \
|
||||||
|
FLUTTER_HOME=/workspace/flutter
|
||||||
|
|
||||||
|
RUN bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh \
|
||||||
|
&& sdk install java 8.0.242.j9-adpt"
|
26
.gitpod.yml
Normal file
26
.gitpod.yml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
image:
|
||||||
|
file: .gitpod.dockerfile
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- before: |
|
||||||
|
export PATH=$FLUTTER_HOME/bin:$FLUTTER_HOME/bin/cache/dart-sdk/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.12.13+hotfix.7-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: |
|
||||||
|
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==
|
BIN
.gradle/6.0.1/fileChanges/last-build.bin
Normal file
BIN
.gradle/6.0.1/fileChanges/last-build.bin
Normal file
Binary file not shown.
BIN
.gradle/6.0.1/fileHashes/fileHashes.lock
Normal file
BIN
.gradle/6.0.1/fileHashes/fileHashes.lock
Normal file
Binary file not shown.
0
.gradle/6.0.1/gc.properties
Normal file
0
.gradle/6.0.1/gc.properties
Normal file
16
README.md
16
README.md
@ -1,12 +1,16 @@
|
|||||||
[](https://somegeeky.website/badges/flutter) [](https://somegeeky.website/badges/dart)
|
|
||||||
# HA Client
|
# HA Client
|
||||||
## Native Android client for Home Assistant
|
## Native Android client for Home Assistant
|
||||||
### With Lovelace UI support
|
### With notifications and Lovelace UI support
|
||||||
|
|
||||||
Visit [homemade.systems](http://ha-client.homemade.systems/) for more info.
|
Visit [ha-client.app](http://ha-client.app/) 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 on [Discord](https://discord.gg/nd6FZQ) or at [Home Assistant community](https://community.home-assistant.io/c/mobile-apps/ha-client-android)
|
||||||
|
|
||||||
Discuss it in [Home Assistant community](https://community.home-assistant.io/t/alpha-testing-ha-client-native-android-client-for-home-assistant/69912)
|
[](https://gitpod.io/#https://github.com/estevez-dev/ha_client)
|
||||||
|
|
||||||
|
#### 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']
|
||||||
@ -70,7 +78,11 @@ flutter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation 'com.google.firebase:firebase-analytics:17.2.2'
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply plugin: 'io.fabric'
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
64
android/app/google-services.json
Normal file
64
android/app/google-services.json
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "441874387819",
|
||||||
|
"firebase_url": "https://ha-client-c73c4.firebaseio.com",
|
||||||
|
"project_id": "ha-client-c73c4",
|
||||||
|
"storage_bucket": "ha-client-c73c4.appspot.com"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:441874387819:android:92c7efc892dc3d45",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.keyboardcrumbs.haclient"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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_type": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyBsl9cjBY633IrdrTyCsLFlO9lfsYJ0OJU"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "441874387819-id0hqsfprj3b5kc312faqv3lmdfpm7l8.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration_version": "1"
|
||||||
|
}
|
@ -1,11 +1,15 @@
|
|||||||
<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">
|
||||||
|
|
||||||
<!-- The INTERNET permission is required for development. Specifically,
|
<uses-feature android:name="android.hardware.touchscreen"
|
||||||
flutter needs it to communicate with the running application
|
android:required="false" />
|
||||||
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.RECEIVE_BOOT_COMPLETED"/>
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
|
||||||
<!-- 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.
|
||||||
@ -15,7 +19,17 @@
|
|||||||
<application
|
<application
|
||||||
android:name="io.flutter.app.FlutterApplication"
|
android:name="io.flutter.app.FlutterApplication"
|
||||||
android:label="HA Client"
|
android:label="HA Client"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
|
||||||
|
<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"
|
||||||
@ -23,17 +37,35 @@
|
|||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<!-- This keeps the window background of the activity showing
|
|
||||||
until Flutter renders its first frame. It can be removed if
|
|
||||||
there is no splash screen (such as the default splash screen
|
|
||||||
defined in @style/LaunchTheme). -->
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
|
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||||
android:value="true" />
|
android:resource="@drawable/launch_background" />
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme" />
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<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>
|
||||||
</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>
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
package com.keyboardcrumbs.hassclient;
|
package com.keyboardcrumbs.hassclient;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import androidx.annotation.NonNull;
|
||||||
import io.flutter.app.FlutterActivity;
|
import io.flutter.embedding.android.FlutterActivity;
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine;
|
||||||
import io.flutter.plugins.GeneratedPluginRegistrant;
|
import io.flutter.plugins.GeneratedPluginRegistrant;
|
||||||
|
|
||||||
public class MainActivity extends FlutterActivity {
|
public class MainActivity extends FlutterActivity {
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
@Override
|
||||||
super.onCreate(savedInstanceState);
|
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
|
||||||
GeneratedPluginRegistrant.registerWith(this);
|
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
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 |
@ -5,4 +5,7 @@
|
|||||||
Flutter draws its first frame -->
|
Flutter draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
</style>
|
</style>
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -2,10 +2,15 @@ buildscript {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven {
|
||||||
|
url 'https://maven.fabric.io/public'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.1.2'
|
classpath 'com.android.tools.build:gradle:3.3.2'
|
||||||
|
classpath 'com.google.gms:google-services:4.3.3'
|
||||||
|
classpath 'io.fabric.tools:gradle:1.26.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,6 +18,9 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven {
|
||||||
|
url 'https://maven.fabric.io/public'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,4 +2,5 @@ org.gradle.jvmargs=-Xmx2g
|
|||||||
org.gradle.daemon=true
|
org.gradle.daemon=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=false
|
android.enableJetifier=true
|
||||||
|
android.enableR8=true
|
||||||
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||||
|
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
1
android/settings_aar.gradle
Normal file
1
android/settings_aar.gradle
Normal file
@ -0,0 +1 @@
|
|||||||
|
include ':app'
|
61
assets/html/cameraLiveView.html
Normal file
61
assets/html/cameraLiveView.html
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
widows: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
var messageChannel = '{{message_channel}}';
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<video id="screen" width="100%" controls></video>
|
||||||
|
<script>
|
||||||
|
if (Hls.isSupported()) {
|
||||||
|
var video = document.getElementById('screen');
|
||||||
|
var hls = new Hls();
|
||||||
|
hls.on(Hls.Events.ERROR, function (event, data) {
|
||||||
|
if (data.fatal) {
|
||||||
|
switch(data.type) {
|
||||||
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
|
// try to recover network error
|
||||||
|
console.log("fatal network error encountered, try to recover");
|
||||||
|
hls.startLoad();
|
||||||
|
break;
|
||||||
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
|
console.log("fatal media error encountered, try to recover");
|
||||||
|
hls.recoverMediaError();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// cannot recover
|
||||||
|
hls.destroy();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// bind them together
|
||||||
|
hls.attachMedia(video);
|
||||||
|
hls.on(Hls.Events.MEDIA_ATTACHED, function () {
|
||||||
|
console.log("video and hls.js are now bound together !");
|
||||||
|
hls.loadSource("{{stream_url}}");
|
||||||
|
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
|
||||||
|
console.log("manifest loaded, found " + data.levels.length + " quality level");
|
||||||
|
video.play();
|
||||||
|
video.onloadedmetadata = function() {
|
||||||
|
window[messageChannel].postMessage(document.body.clientWidth / video.offsetHeight);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
28
assets/html/cameraView.html
Normal file
28
assets/html/cameraView.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
widows: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
var messageChannel = '{{message_channel}}';
|
||||||
|
window.onload = function() {
|
||||||
|
var img = document.getElementById('screen');
|
||||||
|
if (img) {
|
||||||
|
window[messageChannel].postMessage(document.body.clientWidth / img.offsetHeight);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img id="screen" src="{{stream_url}}">
|
||||||
|
</body>
|
||||||
|
</html>
|
35
assets/js/externalAuth.js
Normal file
35
assets/js/externalAuth.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.externalApp.externalBus = function(message) {
|
||||||
|
console.log("External bus message: " + message);
|
||||||
|
var messageObj = JSON.parse(message);
|
||||||
|
if (messageObj.type == "config/get") {
|
||||||
|
var responseData = {
|
||||||
|
id: messageObj.id,
|
||||||
|
type: "result",
|
||||||
|
success: true,
|
||||||
|
result: {
|
||||||
|
hasSettingsScreen: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
setTimeout(function(){
|
||||||
|
window.externalBus(responseData);
|
||||||
|
}, 500);
|
||||||
|
} else if (messageObj.type == "config_screen/show") {
|
||||||
|
HAClient.postMessage('show-settings');
|
||||||
|
}
|
||||||
|
};
|
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.
62
lib/cards/alarm_panel_card.dart
Normal file
62
lib/cards/alarm_panel_card.dart
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class AlarmPanelCard extends StatelessWidget {
|
||||||
|
final AlarmPanelCardData card;
|
||||||
|
|
||||||
|
const AlarmPanelCard({Key key, this.card}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (card.entity.entity.statelessType == StatelessEntityType.missed) {
|
||||||
|
return EntityModel(
|
||||||
|
entityWrapper: card.entity,
|
||||||
|
child: MissedEntityWidget(),
|
||||||
|
handleTap: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
List<Widget> body = [];
|
||||||
|
body.add(CardHeader(
|
||||||
|
name: card.name ?? "",
|
||||||
|
subtitle: Text("${card.entity.entity.displayState}",
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
EntityIcon(
|
||||||
|
size: 50.0,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 26.0,
|
||||||
|
child: IconButton(
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||||
|
"mdi:dots-vertical")),
|
||||||
|
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity: card.entity.entity))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
));
|
||||||
|
body.add(
|
||||||
|
AlarmControlPanelControlsWidget(
|
||||||
|
extended: true,
|
||||||
|
states: card.states,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return CardWrapper(
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: card.entity,
|
||||||
|
handleTap: null,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: body
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
510
lib/cards/card.class.dart
Normal file
510
lib/cards/card.class.dart
Normal file
@ -0,0 +1,510 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class CardData {
|
||||||
|
|
||||||
|
String type;
|
||||||
|
List<EntityWrapper> entities = [];
|
||||||
|
List conditions;
|
||||||
|
bool showEmpty;
|
||||||
|
List stateFilter;
|
||||||
|
bool stateColor = true;
|
||||||
|
|
||||||
|
EntityWrapper get entity => entities.isNotEmpty ? entities[0] : null;
|
||||||
|
|
||||||
|
factory CardData.parse(Map<String, dynamic> rawData) {
|
||||||
|
switch (rawData['type']) {
|
||||||
|
case CardType.ENTITIES:
|
||||||
|
return EntitiesCardData(rawData);
|
||||||
|
break;
|
||||||
|
case CardType.ALARM_PANEL:
|
||||||
|
return AlarmPanelCardData(rawData);
|
||||||
|
break;
|
||||||
|
case CardType.BUTTON:
|
||||||
|
return ButtonCardData(rawData);
|
||||||
|
break;
|
||||||
|
case CardType.ENTITY_BUTTON:
|
||||||
|
return ButtonCardData(rawData);
|
||||||
|
break;
|
||||||
|
case CardType.CONDITIONAL:
|
||||||
|
return CardData.parse(rawData['card']);
|
||||||
|
break;
|
||||||
|
case CardType.ENTITY_FILTER:
|
||||||
|
Map<String, dynamic> cardData = Map.from(rawData);
|
||||||
|
cardData.remove('type');
|
||||||
|
if (rawData.containsKey('card')) {
|
||||||
|
cardData.addAll(rawData['card']);
|
||||||
|
}
|
||||||
|
cardData['type'] ??= CardType.ENTITIES;
|
||||||
|
return CardData.parse(cardData);
|
||||||
|
break;
|
||||||
|
case CardType.GAUGE:
|
||||||
|
return GaugeCardData(rawData);
|
||||||
|
break;
|
||||||
|
case CardType.GLANCE:
|
||||||
|
return GlanceCardData(rawData);
|
||||||
|
break;
|
||||||
|
case CardType.HORIZONTAL_STACK:
|
||||||
|
return HorizontalStackCardData(rawData);
|
||||||
|
break;
|
||||||
|
case CardType.VERTICAL_STACK:
|
||||||
|
return VerticalStackCardData(rawData);
|
||||||
|
break;
|
||||||
|
case CardType.MARKDOWN:
|
||||||
|
return MarkdownCardData(rawData);
|
||||||
|
break;
|
||||||
|
case CardType.MEDIA_CONTROL:
|
||||||
|
return MediaControlCardData(rawData);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (rawData.containsKey('entities')) {
|
||||||
|
return EntitiesCardData(rawData);
|
||||||
|
} else if (rawData.containsKey('entity')) {
|
||||||
|
rawData['entities'] = [rawData['entity']];
|
||||||
|
return EntitiesCardData(rawData);
|
||||||
|
}
|
||||||
|
return CardData(rawData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CardData(Map<String, dynamic> rawData) {
|
||||||
|
type = rawData['type'] ?? CardType.ENTITIES;
|
||||||
|
conditions = rawData['conditions'] ?? [];
|
||||||
|
showEmpty = rawData['show_empty'] ?? true;
|
||||||
|
stateFilter = rawData['state_filter'] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildCardWidget() {
|
||||||
|
return UnsupportedCard(card: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EntityWrapper> getEntitiesToShow() {
|
||||||
|
return entities.where((entityWrapper) {
|
||||||
|
if (HomeAssistant().autoUi && entityWrapper.entity.isHidden) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List currentStateFilter;
|
||||||
|
if (entityWrapper.stateFilter != null && entityWrapper.stateFilter.isNotEmpty) {
|
||||||
|
currentStateFilter = entityWrapper.stateFilter;
|
||||||
|
} else {
|
||||||
|
currentStateFilter = stateFilter;
|
||||||
|
}
|
||||||
|
bool showByFilter = currentStateFilter.isEmpty;
|
||||||
|
for (var allowedState in currentStateFilter) {
|
||||||
|
if (allowedState is String && allowedState == entityWrapper.entity.state) {
|
||||||
|
showByFilter = true;
|
||||||
|
break;
|
||||||
|
} else if (allowedState is Map) {
|
||||||
|
try {
|
||||||
|
var tmpVal = allowedState['attribute'] != null ? entityWrapper.entity.getAttribute(allowedState['attribute']) : entityWrapper.entity.state;
|
||||||
|
var valToCompareWith = allowedState['value'];
|
||||||
|
var valToCompare;
|
||||||
|
if (valToCompareWith is! String && tmpVal is String) {
|
||||||
|
valToCompare = double.tryParse(tmpVal);
|
||||||
|
} else {
|
||||||
|
valToCompare = tmpVal;
|
||||||
|
}
|
||||||
|
if (valToCompare != null) {
|
||||||
|
bool result;
|
||||||
|
switch (allowedState['operator']) {
|
||||||
|
case '<=': { result = valToCompare <= valToCompareWith;}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '<': { result = valToCompare < valToCompareWith;}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '>=': { result = valToCompare >= valToCompareWith;}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '>': { result = valToCompare > valToCompareWith;}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '!=': { result = valToCompare != valToCompareWith;}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'regex': {
|
||||||
|
RegExp regExp = RegExp(valToCompareWith.toString());
|
||||||
|
result = regExp.hasMatch(valToCompare.toString());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: {
|
||||||
|
result = valToCompare == valToCompareWith;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result) {
|
||||||
|
showByFilter = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Logger.e('Error filtering ${entityWrapper.entity.entityId} by $allowedState');
|
||||||
|
Logger.e('$e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return showByFilter;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class EntitiesCardData extends CardData {
|
||||||
|
|
||||||
|
String title;
|
||||||
|
String icon;
|
||||||
|
bool showHeaderToggle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildCardWidget() {
|
||||||
|
return EntitiesCard(card: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
EntitiesCardData(Map<String, dynamic> rawData) : super(rawData) {
|
||||||
|
//Parsing card data
|
||||||
|
title = rawData["title"];
|
||||||
|
icon = rawData['icon'];
|
||||||
|
stateColor = rawData['state_color'] ?? false;
|
||||||
|
showHeaderToggle = rawData['show_header_toggle'] ?? false;
|
||||||
|
//Parsing entities
|
||||||
|
var rawEntities = rawData["entities"] ?? [];
|
||||||
|
rawEntities.forEach((rawEntity) {
|
||||||
|
if (rawEntity is String) {
|
||||||
|
if (HomeAssistant().entities.isExist(rawEntity)) {
|
||||||
|
entities.add(EntityWrapper(entity: HomeAssistant().entities.get(rawEntity)));
|
||||||
|
} else {
|
||||||
|
entities.add(EntityWrapper(entity: Entity.missed(rawEntity)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (rawEntity["type"] == "divider") {
|
||||||
|
entities.add(EntityWrapper(entity: Entity.divider()));
|
||||||
|
} else if (rawEntity["type"] == "section") {
|
||||||
|
entities.add(EntityWrapper(entity: Entity.section(rawEntity["label"] ?? "")));
|
||||||
|
} else if (rawEntity["type"] == "call-service") {
|
||||||
|
Map uiActionData = {
|
||||||
|
"tap_action": {
|
||||||
|
"action": EntityUIAction.callService,
|
||||||
|
"service": rawEntity["service"],
|
||||||
|
"service_data": rawEntity["service_data"]
|
||||||
|
},
|
||||||
|
"hold_action": EntityUIAction.none
|
||||||
|
};
|
||||||
|
entities.add(
|
||||||
|
EntityWrapper(
|
||||||
|
entity: Entity.callService(
|
||||||
|
icon: rawEntity["icon"],
|
||||||
|
name: rawEntity["name"],
|
||||||
|
service: rawEntity["service"],
|
||||||
|
actionName: rawEntity["action_name"]
|
||||||
|
),
|
||||||
|
stateColor: rawEntity["state_color"] ?? stateColor,
|
||||||
|
uiAction: EntityUIAction(rawEntityData: uiActionData)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (rawEntity["type"] == "weblink") {
|
||||||
|
Map uiActionData = {
|
||||||
|
"tap_action": {
|
||||||
|
"action": EntityUIAction.navigate,
|
||||||
|
"service": rawEntity["url"]
|
||||||
|
},
|
||||||
|
"hold_action": EntityUIAction.none
|
||||||
|
};
|
||||||
|
entities.add(EntityWrapper(
|
||||||
|
entity: Entity.weblink(
|
||||||
|
icon: rawEntity["icon"],
|
||||||
|
name: rawEntity["name"],
|
||||||
|
url: rawEntity["url"]
|
||||||
|
),
|
||||||
|
stateColor: rawEntity["state_color"] ?? stateColor,
|
||||||
|
uiAction: EntityUIAction(rawEntityData: uiActionData)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (HomeAssistant().entities.isExist(rawEntity["entity"])) {
|
||||||
|
Entity e = HomeAssistant().entities.get(rawEntity["entity"]);
|
||||||
|
entities.add(
|
||||||
|
EntityWrapper(
|
||||||
|
entity: e,
|
||||||
|
stateColor: rawEntity["state_color"] ?? stateColor,
|
||||||
|
overrideName: rawEntity["name"],
|
||||||
|
overrideIcon: rawEntity["icon"],
|
||||||
|
stateFilter: rawEntity['state_filter'] ?? [],
|
||||||
|
uiAction: EntityUIAction(rawEntityData: rawEntity)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
entities.add(EntityWrapper(entity: Entity.missed(rawEntity["entity"])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class AlarmPanelCardData extends CardData {
|
||||||
|
|
||||||
|
String name;
|
||||||
|
List<dynamic> states;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildCardWidget() {
|
||||||
|
return AlarmPanelCard(card: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AlarmPanelCardData(Map<String, dynamic> rawData) : super(rawData) {
|
||||||
|
//Parsing card data
|
||||||
|
name = rawData['name'];
|
||||||
|
states = rawData['states'];
|
||||||
|
//Parsing entity
|
||||||
|
var entitiId = rawData["entity"];
|
||||||
|
if (entitiId != null && entitiId is String) {
|
||||||
|
if (HomeAssistant().entities.isExist(entitiId)) {
|
||||||
|
entities.add(EntityWrapper(
|
||||||
|
entity: HomeAssistant().entities.get(entitiId),
|
||||||
|
stateColor: true,
|
||||||
|
overrideName: name
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
entities.add(EntityWrapper(entity: Entity.missed(entitiId)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ButtonCardData extends CardData {
|
||||||
|
|
||||||
|
String name;
|
||||||
|
String icon;
|
||||||
|
bool showName;
|
||||||
|
bool showIcon;
|
||||||
|
double iconHeightPx = 0;
|
||||||
|
double iconHeightRem = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildCardWidget() {
|
||||||
|
return EntityButtonCard(card: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonCardData(Map<String, dynamic> rawData) : super(rawData) {
|
||||||
|
//Parsing card data
|
||||||
|
name = rawData['name'];
|
||||||
|
icon = rawData['icon'];
|
||||||
|
showName = rawData['show_name'] ?? true;
|
||||||
|
showIcon = rawData['show_icon'] ?? true;
|
||||||
|
stateColor = rawData['state_color'] ?? true;
|
||||||
|
var rawHeight = rawData['icon_height'];
|
||||||
|
if (rawHeight != null && rawHeight is String) {
|
||||||
|
if (rawHeight.contains('px')) {
|
||||||
|
iconHeightPx = double.tryParse(rawHeight.replaceFirst('px', '')) ?? 0;
|
||||||
|
} else if (rawHeight.contains('rem')) {
|
||||||
|
iconHeightRem = double.tryParse(rawHeight.replaceFirst('rem', '')) ?? 0;
|
||||||
|
} else if (rawHeight.contains('em')) {
|
||||||
|
iconHeightRem = double.tryParse(rawHeight.replaceFirst('em', '')) ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Parsing entity
|
||||||
|
var entitiId = rawData["entity"];
|
||||||
|
if (entitiId != null && entitiId is String) {
|
||||||
|
if (HomeAssistant().entities.isExist(entitiId)) {
|
||||||
|
entities.add(EntityWrapper(
|
||||||
|
entity: HomeAssistant().entities.get(entitiId),
|
||||||
|
overrideName: name,
|
||||||
|
overrideIcon: icon,
|
||||||
|
stateColor: stateColor,
|
||||||
|
uiAction: EntityUIAction(
|
||||||
|
rawEntityData: rawData
|
||||||
|
)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
entities.add(EntityWrapper(entity: Entity.missed(entitiId)));
|
||||||
|
}
|
||||||
|
} else if (entitiId == null) {
|
||||||
|
entities.add(
|
||||||
|
EntityWrapper(
|
||||||
|
entity: Entity.ghost(
|
||||||
|
name,
|
||||||
|
icon,
|
||||||
|
),
|
||||||
|
stateColor: stateColor,
|
||||||
|
uiAction: EntityUIAction(
|
||||||
|
rawEntityData: rawData
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GaugeCardData extends CardData {
|
||||||
|
|
||||||
|
String name;
|
||||||
|
String unit;
|
||||||
|
int min;
|
||||||
|
int max;
|
||||||
|
Map severity;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildCardWidget() {
|
||||||
|
return GaugeCard(card: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
GaugeCardData(Map<String, dynamic> rawData) : super(rawData) {
|
||||||
|
//Parsing card data
|
||||||
|
name = rawData['name'];
|
||||||
|
unit = rawData['unit'];
|
||||||
|
min = rawData['min'] ?? 0;
|
||||||
|
max = rawData['max'] ?? 100;
|
||||||
|
severity = rawData['severity'];
|
||||||
|
//Parsing entity
|
||||||
|
var entitiId = rawData["entity"];
|
||||||
|
if (entitiId != null && entitiId is String) {
|
||||||
|
if (HomeAssistant().entities.isExist(entitiId)) {
|
||||||
|
entities.add(EntityWrapper(
|
||||||
|
entity: HomeAssistant().entities.get(entitiId),
|
||||||
|
overrideName: name
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
entities.add(EntityWrapper(entity: Entity.missed(entitiId)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entities.add(EntityWrapper(entity: Entity.missed(entitiId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class GlanceCardData extends CardData {
|
||||||
|
|
||||||
|
String title;
|
||||||
|
bool showName;
|
||||||
|
bool showIcon;
|
||||||
|
bool showState;
|
||||||
|
bool stateColor;
|
||||||
|
int columnsCount;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildCardWidget() {
|
||||||
|
return GlanceCard(card: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
GlanceCardData(Map<String, dynamic> rawData) : super(rawData) {
|
||||||
|
//Parsing card data
|
||||||
|
title = rawData["title"];
|
||||||
|
showName = rawData['show_name'] ?? true;
|
||||||
|
showIcon = rawData['show_icon'] ?? true;
|
||||||
|
showState = rawData['show_state'] ?? true;
|
||||||
|
stateColor = rawData['state_color'] ?? true;
|
||||||
|
columnsCount = rawData['columns'] ?? 4;
|
||||||
|
//Parsing entities
|
||||||
|
var rawEntities = rawData["entities"] ?? [];
|
||||||
|
rawEntities.forEach((rawEntity) {
|
||||||
|
if (rawEntity is String) {
|
||||||
|
if (HomeAssistant().entities.isExist(rawEntity)) {
|
||||||
|
entities.add(EntityWrapper(entity: HomeAssistant().entities.get(rawEntity)));
|
||||||
|
} else {
|
||||||
|
entities.add(EntityWrapper(entity: Entity.missed(rawEntity)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (HomeAssistant().entities.isExist(rawEntity["entity"])) {
|
||||||
|
Entity e = HomeAssistant().entities.get(rawEntity["entity"]);
|
||||||
|
entities.add(
|
||||||
|
EntityWrapper(
|
||||||
|
entity: e,
|
||||||
|
stateColor: stateColor,
|
||||||
|
overrideName: rawEntity["name"],
|
||||||
|
overrideIcon: rawEntity["icon"],
|
||||||
|
stateFilter: rawEntity['state_filter'] ?? [],
|
||||||
|
uiAction: EntityUIAction(rawEntityData: rawEntity)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
entities.add(EntityWrapper(entity: Entity.missed(rawEntity["entity"])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class HorizontalStackCardData extends CardData {
|
||||||
|
|
||||||
|
List<CardData> childCards;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildCardWidget() {
|
||||||
|
return HorizontalStackCard(card: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalStackCardData(Map<String, dynamic> rawData) : super(rawData) {
|
||||||
|
if (rawData.containsKey('cards')) {
|
||||||
|
childCards = rawData['cards'].map<CardData>((childCard) {
|
||||||
|
return CardData.parse(childCard);
|
||||||
|
}).toList();
|
||||||
|
} else {
|
||||||
|
childCards = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class VerticalStackCardData extends CardData {
|
||||||
|
|
||||||
|
List<CardData> childCards;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildCardWidget() {
|
||||||
|
return VerticalStackCard(card: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalStackCardData(Map<String, dynamic> rawData) : super(rawData) {
|
||||||
|
if (rawData.containsKey('cards')) {
|
||||||
|
childCards = rawData['cards'].map<CardData>((childCard) {
|
||||||
|
return CardData.parse(childCard);
|
||||||
|
}).toList();
|
||||||
|
} else {
|
||||||
|
childCards = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class MarkdownCardData extends CardData {
|
||||||
|
|
||||||
|
String title;
|
||||||
|
String content;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildCardWidget() {
|
||||||
|
return MarkdownCard(card: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
MarkdownCardData(Map<String, dynamic> rawData) : super(rawData) {
|
||||||
|
//Parsing card data
|
||||||
|
title = rawData['title'];
|
||||||
|
content = rawData['content'];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class MediaControlCardData extends CardData {
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildCardWidget() {
|
||||||
|
return MediaControlsCard(card: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaControlCardData(Map<String, dynamic> rawData) : super(rawData) {
|
||||||
|
var entitiId = rawData["entity"];
|
||||||
|
if (entitiId != null && entitiId is String) {
|
||||||
|
if (HomeAssistant().entities.isExist(entitiId)) {
|
||||||
|
entities.add(EntityWrapper(
|
||||||
|
entity: HomeAssistant().entities.get(entitiId),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
entities.add(EntityWrapper(entity: Entity.missed(entitiId)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
79
lib/cards/entities_card.dart
Normal file
79
lib/cards/entities_card.dart
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class EntitiesCard extends StatelessWidget {
|
||||||
|
final EntitiesCardData card;
|
||||||
|
|
||||||
|
const EntitiesCard({Key key, this.card}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
List<EntityWrapper> entitiesToShow = card.getEntitiesToShow();
|
||||||
|
if (entitiesToShow.isEmpty && !card.showEmpty) {
|
||||||
|
return Container(height: 0.0, width: 0.0,);
|
||||||
|
}
|
||||||
|
List<Widget> body = [];
|
||||||
|
Widget headerSwitch;
|
||||||
|
if (card.showHeaderToggle) {
|
||||||
|
bool headerToggleVal = entitiesToShow.any((EntityWrapper en){ return en.entity.state == EntityState.on; });
|
||||||
|
List<String> entitiesToToggle = entitiesToShow.where((EntityWrapper enw) {
|
||||||
|
return <String>["switch", "light", "automation", "input_boolean"].contains(enw.entity.domain);
|
||||||
|
}).map((EntityWrapper en) {
|
||||||
|
return en.entity.entityId;
|
||||||
|
}).toList();
|
||||||
|
headerSwitch = Switch(
|
||||||
|
value: headerToggleVal,
|
||||||
|
onChanged: (val) {
|
||||||
|
if (entitiesToToggle.isNotEmpty) {
|
||||||
|
ConnectionManager().callService(
|
||||||
|
domain: "homeassistant",
|
||||||
|
service: val ? "turn_on" : "turn_off",
|
||||||
|
entityId: entitiesToToggle
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
body.add(
|
||||||
|
CardHeader(
|
||||||
|
name: card.title,
|
||||||
|
trailing: headerSwitch,
|
||||||
|
emptyPadding: Sizes.rowPadding,
|
||||||
|
leading: card.icon != null ? Icon(
|
||||||
|
MaterialDesignIcons.getIconDataFromIconName(card.icon),
|
||||||
|
size: Sizes.iconSize,
|
||||||
|
color: Theme.of(context).textTheme.headline.color
|
||||||
|
) : null,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
body.addAll(
|
||||||
|
entitiesToShow.map((EntityWrapper entity) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0, 4.0, 0.0, 4.0),
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: entity,
|
||||||
|
handleTap: true,
|
||||||
|
child: entity.entity.buildDefaultWidget(context)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return CardWrapper(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
right: Sizes.rightWidgetPadding,
|
||||||
|
left: Sizes.leftWidgetPadding,
|
||||||
|
bottom: Sizes.rowPadding,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: body
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
93
lib/cards/entity_button_card.dart
Normal file
93
lib/cards/entity_button_card.dart
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class EntityButtonCard extends StatelessWidget {
|
||||||
|
|
||||||
|
final ButtonCardData card;
|
||||||
|
|
||||||
|
EntityButtonCard({
|
||||||
|
Key key, this.card
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
EntityWrapper entityWrapper = card.entity;
|
||||||
|
if (entityWrapper.entity.statelessType == StatelessEntityType.missed) {
|
||||||
|
return EntityModel(
|
||||||
|
entityWrapper: card.entity,
|
||||||
|
child: MissedEntityWidget(),
|
||||||
|
handleTap: false,
|
||||||
|
);
|
||||||
|
} else if (entityWrapper.entity.statelessType != StatelessEntityType.ghost && entityWrapper.entity.statelessType != StatelessEntityType.none) {
|
||||||
|
return Container(width: 0.0, height: 0.0,);
|
||||||
|
}
|
||||||
|
|
||||||
|
double iconSize = math.max(card.iconHeightPx, card.iconHeightRem * Theme.of(context).textTheme.body1.fontSize);
|
||||||
|
|
||||||
|
Widget buttonIcon;
|
||||||
|
if (!card.showIcon) {
|
||||||
|
buttonIcon = Container(height: Sizes.rowPadding, width: 10);
|
||||||
|
} else if (iconSize > 0) {
|
||||||
|
buttonIcon = SizedBox(
|
||||||
|
height: iconSize,
|
||||||
|
child: FractionallySizedBox(
|
||||||
|
widthFactor: 0.5,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
child: EntityIcon(
|
||||||
|
//padding: EdgeInsets.only(top: 6),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
buttonIcon = AspectRatio(
|
||||||
|
aspectRatio: 2,
|
||||||
|
child: FractionallySizedBox(
|
||||||
|
widthFactor: 0.5,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.fitWidth,
|
||||||
|
child: EntityIcon(
|
||||||
|
//padding: EdgeInsets.only(top: 6),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CardWrapper(
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: card.entity,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => entityWrapper.handleTap(),
|
||||||
|
onLongPress: () => entityWrapper.handleHold(),
|
||||||
|
onDoubleTap: () => entityWrapper.handleDoubleTap(),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: <Widget>[
|
||||||
|
buttonIcon,
|
||||||
|
_buildName(context)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
handleTap: true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildName(BuildContext context) {
|
||||||
|
if (card.showName) {
|
||||||
|
return EntityName(
|
||||||
|
padding: EdgeInsets.fromLTRB(Sizes.buttonPadding, 0.0, Sizes.buttonPadding, Sizes.rowPadding),
|
||||||
|
textOverflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 3,
|
||||||
|
textStyle: Theme.of(context).textTheme.subhead,
|
||||||
|
wordsWrap: true,
|
||||||
|
textAlign: TextAlign.center
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container(width: 0, height: 0);
|
||||||
|
}
|
||||||
|
}
|
198
lib/cards/gauge_card.dart
Normal file
198
lib/cards/gauge_card.dart
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class GaugeCard extends StatelessWidget {
|
||||||
|
|
||||||
|
final GaugeCardData card;
|
||||||
|
|
||||||
|
GaugeCard({Key key, this.card}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
EntityWrapper entityWrapper = card.entity;
|
||||||
|
if (entityWrapper.entity.statelessType == StatelessEntityType.missed) {
|
||||||
|
return EntityModel(
|
||||||
|
entityWrapper: card.entity,
|
||||||
|
child: MissedEntityWidget(),
|
||||||
|
handleTap: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
entityWrapper.overrideName = card.name ??
|
||||||
|
entityWrapper.displayName;
|
||||||
|
entityWrapper.unitOfMeasurementOverride = card.unit ??
|
||||||
|
entityWrapper.unitOfMeasurement;
|
||||||
|
double fixedValue;
|
||||||
|
double value = entityWrapper.entity.doubleState;
|
||||||
|
if (value > card.max) {
|
||||||
|
fixedValue = card.max.toDouble();
|
||||||
|
} else if (value < card.min) {
|
||||||
|
fixedValue = card.min.toDouble();
|
||||||
|
} else {
|
||||||
|
fixedValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GaugeRange> ranges;
|
||||||
|
Color currentColor;
|
||||||
|
if (card.severity != null && card.severity["green"] is int && card.severity["red"] is int && card.severity["yellow"] is int) {
|
||||||
|
List<RangeContainer> rangesList = <RangeContainer>[
|
||||||
|
RangeContainer(card.severity["green"], HAClientTheme().getGreenGaugeColor()),
|
||||||
|
RangeContainer(card.severity["red"], HAClientTheme().getRedGaugeColor()),
|
||||||
|
RangeContainer(card.severity["yellow"], HAClientTheme().getYellowGaugeColor())
|
||||||
|
];
|
||||||
|
rangesList.sort((current, next) {
|
||||||
|
if (current.startFrom > next.startFrom) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (current.startFrom < next.startFrom) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fixedValue < rangesList[1].startFrom) {
|
||||||
|
currentColor = rangesList[0].color;
|
||||||
|
} else if (fixedValue < rangesList[2].startFrom && fixedValue >= rangesList[1].startFrom) {
|
||||||
|
currentColor = rangesList[1].color;
|
||||||
|
} else {
|
||||||
|
currentColor = rangesList[2].color;
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges = [
|
||||||
|
GaugeRange(
|
||||||
|
startValue: rangesList[0].startFrom.toDouble(),
|
||||||
|
endValue: rangesList[1].startFrom.toDouble(),
|
||||||
|
color: rangesList[0].color.withOpacity(0.1),
|
||||||
|
sizeUnit: GaugeSizeUnit.factor,
|
||||||
|
endWidth: 0.3,
|
||||||
|
startWidth: 0.3
|
||||||
|
),
|
||||||
|
GaugeRange(
|
||||||
|
startValue: rangesList[1].startFrom.toDouble(),
|
||||||
|
endValue: rangesList[2].startFrom.toDouble(),
|
||||||
|
color: rangesList[1].color.withOpacity(0.1),
|
||||||
|
sizeUnit: GaugeSizeUnit.factor,
|
||||||
|
endWidth: 0.3,
|
||||||
|
startWidth: 0.3
|
||||||
|
),
|
||||||
|
GaugeRange(
|
||||||
|
startValue: rangesList[2].startFrom.toDouble(),
|
||||||
|
endValue: card.max.toDouble(),
|
||||||
|
color: rangesList[2].color.withOpacity(0.1),
|
||||||
|
sizeUnit: GaugeSizeUnit.factor,
|
||||||
|
endWidth: 0.3,
|
||||||
|
startWidth: 0.3
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (ranges == null) {
|
||||||
|
currentColor = Theme.of(context).primaryColorDark;
|
||||||
|
ranges = <GaugeRange>[
|
||||||
|
GaugeRange(
|
||||||
|
startValue: card.min.toDouble(),
|
||||||
|
endValue: card.max.toDouble(),
|
||||||
|
color: Theme.of(context).primaryColorDark.withOpacity(0.1),
|
||||||
|
sizeUnit: GaugeSizeUnit.factor,
|
||||||
|
endWidth: 0.3,
|
||||||
|
startWidth: 0.3,
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return CardWrapper(
|
||||||
|
padding: EdgeInsets.all(4),
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: entityWrapper,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => entityWrapper.handleTap(),
|
||||||
|
onLongPress: () => entityWrapper.handleHold(),
|
||||||
|
onDoubleTap: () => entityWrapper.handleDoubleTap(),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 1.8,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
children: <Widget>[
|
||||||
|
SfRadialGauge(
|
||||||
|
axes: <RadialAxis>[
|
||||||
|
RadialAxis(
|
||||||
|
maximum: card.max.toDouble(),
|
||||||
|
minimum: card.min.toDouble(),
|
||||||
|
showLabels: false,
|
||||||
|
useRangeColorForAxis: true,
|
||||||
|
showTicks: false,
|
||||||
|
canScaleToFit: true,
|
||||||
|
ranges: ranges,
|
||||||
|
axisLineStyle: AxisLineStyle(
|
||||||
|
thickness: 0.3,
|
||||||
|
thicknessUnit: GaugeSizeUnit.factor,
|
||||||
|
color: Colors.transparent
|
||||||
|
),
|
||||||
|
startAngle: 180,
|
||||||
|
endAngle: 0,
|
||||||
|
pointers: <GaugePointer>[
|
||||||
|
RangePointer(
|
||||||
|
value: fixedValue,
|
||||||
|
sizeUnit: GaugeSizeUnit.factor,
|
||||||
|
width: 0.3,
|
||||||
|
color: currentColor,
|
||||||
|
enableAnimation: true,
|
||||||
|
animationType: AnimationType.bounceOut,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Flexible(
|
||||||
|
flex: 8,
|
||||||
|
fit: FlexFit.tight,
|
||||||
|
child: Container()
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
flex: 6,
|
||||||
|
fit: FlexFit.tight,
|
||||||
|
child: FractionallySizedBox(
|
||||||
|
widthFactor: 0.4,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: SimpleEntityState(
|
||||||
|
padding: EdgeInsets.all(0),
|
||||||
|
expanded: false,
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.center
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
flex: 3,
|
||||||
|
fit: FlexFit.tight,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
child: EntityName(
|
||||||
|
padding: EdgeInsets.all(0),
|
||||||
|
textStyle: Theme.of(context).textTheme.subhead
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
handleTap: true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class RangeContainer {
|
||||||
|
final int startFrom;
|
||||||
|
Color color;
|
||||||
|
|
||||||
|
RangeContainer(this.startFrom, this.color);
|
||||||
|
}
|
120
lib/cards/glance_card.dart
Normal file
120
lib/cards/glance_card.dart
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class GlanceCard extends StatelessWidget {
|
||||||
|
final GlanceCardData card;
|
||||||
|
|
||||||
|
const GlanceCard({Key key, this.card}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
List<EntityWrapper> entitiesToShow = card.getEntitiesToShow();
|
||||||
|
if (entitiesToShow.isEmpty && !card.showEmpty) {
|
||||||
|
return Container(height: 0.0, width: 0.0,);
|
||||||
|
}
|
||||||
|
int length = entitiesToShow.length;
|
||||||
|
int rowsCount;
|
||||||
|
int columnsCount;
|
||||||
|
if (length == 0) {
|
||||||
|
columnsCount = 0;
|
||||||
|
rowsCount = 0;
|
||||||
|
} else {
|
||||||
|
columnsCount = length >= card.columnsCount ? card.columnsCount : entitiesToShow.length;
|
||||||
|
rowsCount = (length / columnsCount).round();
|
||||||
|
}
|
||||||
|
List<TableRow> rows = [];
|
||||||
|
for (int i = 0; i < rowsCount; i++) {
|
||||||
|
int start = i*columnsCount;
|
||||||
|
int end = start + math.min(columnsCount, length - start);
|
||||||
|
List<Widget> rowChildren = [];
|
||||||
|
rowChildren.addAll(entitiesToShow.sublist(
|
||||||
|
start, end
|
||||||
|
).map(
|
||||||
|
(EntityWrapper entity){
|
||||||
|
return EntityModel(
|
||||||
|
entityWrapper: entity,
|
||||||
|
child: _buildEntityContainer(context, entity),
|
||||||
|
handleTap: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
).toList()
|
||||||
|
);
|
||||||
|
while (rowChildren.length < columnsCount) {
|
||||||
|
rowChildren.add(
|
||||||
|
Container()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
rows.add(
|
||||||
|
TableRow(
|
||||||
|
children: rowChildren
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return CardWrapper(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
CardHeader(name: card.title),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: Sizes.rowPadding),
|
||||||
|
child: Table(
|
||||||
|
children: rows
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEntityContainer(BuildContext context, EntityWrapper entityWrapper) {
|
||||||
|
if (entityWrapper.entity.statelessType == StatelessEntityType.missed) {
|
||||||
|
return MissedEntityWidget();
|
||||||
|
} else if (entityWrapper.entity.statelessType != StatelessEntityType.none) {
|
||||||
|
return Container(width: 0.0, height: 0.0,);
|
||||||
|
}
|
||||||
|
List<Widget> result = [];
|
||||||
|
if (card.showName) {
|
||||||
|
result.add(_buildName(context));
|
||||||
|
}
|
||||||
|
result.add(
|
||||||
|
EntityIcon(
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
size: Sizes.iconSize,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (card.showState) {
|
||||||
|
result.add(_buildState());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: InkResponse(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: result,
|
||||||
|
),
|
||||||
|
onTap: () => entityWrapper.handleTap(),
|
||||||
|
onLongPress: () => entityWrapper.handleHold(),
|
||||||
|
onDoubleTap: () => entityWrapper.handleDoubleTap(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildName(BuildContext context) {
|
||||||
|
return EntityName(
|
||||||
|
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
|
||||||
|
textOverflow: TextOverflow.ellipsis,
|
||||||
|
wordsWrap: false,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
textStyle: Theme.of(context).textTheme.body1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildState() {
|
||||||
|
return SimpleEntityState(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
expanded: false,
|
||||||
|
maxLines: 1,
|
||||||
|
padding: EdgeInsets.only(top: Sizes.rowPadding),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
30
lib/cards/horizontal_srack_card.dart
Normal file
30
lib/cards/horizontal_srack_card.dart
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class HorizontalStackCard extends StatelessWidget {
|
||||||
|
final HorizontalStackCardData card;
|
||||||
|
|
||||||
|
const HorizontalStackCard({Key key, this.card}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (card.childCards.isNotEmpty) {
|
||||||
|
List<Widget> children = [];
|
||||||
|
children = card.childCards.map((childCard) => Flexible(
|
||||||
|
fit: FlexFit.tight,
|
||||||
|
child: childCard.buildCardWidget()
|
||||||
|
)
|
||||||
|
).toList();
|
||||||
|
return IntrinsicHeight(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container(height: 0.0, width: 0.0,);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
33
lib/cards/markdown_card.dart
Normal file
33
lib/cards/markdown_card.dart
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class MarkdownCard extends StatelessWidget {
|
||||||
|
final MarkdownCardData card;
|
||||||
|
|
||||||
|
const MarkdownCard({Key key, this.card}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (card.content == null) {
|
||||||
|
return Container(height: 0.0, width: 0.0,);
|
||||||
|
} else if (card.content == '***') {
|
||||||
|
return Container(height: Sizes.rowPadding, width: 0.0,);
|
||||||
|
}
|
||||||
|
return CardWrapper(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: <Widget>[
|
||||||
|
CardHeader(name: card.title),
|
||||||
|
MarkdownBody(
|
||||||
|
data: card.content,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
27
lib/cards/media_control_card.dart
Normal file
27
lib/cards/media_control_card.dart
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class MediaControlsCard extends StatelessWidget {
|
||||||
|
final MediaControlCardData card;
|
||||||
|
|
||||||
|
const MediaControlsCard({Key key, this.card}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (card.entity.entity.statelessType == StatelessEntityType.missed) {
|
||||||
|
return EntityModel(
|
||||||
|
entityWrapper: card.entity,
|
||||||
|
child: MissedEntityWidget(),
|
||||||
|
handleTap: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return CardWrapper(
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: card.entity,
|
||||||
|
handleTap: null,
|
||||||
|
child: MediaPlayerWidget()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
17
lib/cards/unsupported_card.dart
Normal file
17
lib/cards/unsupported_card.dart
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class UnsupportedCard extends StatelessWidget {
|
||||||
|
final CardData card;
|
||||||
|
|
||||||
|
const UnsupportedCard({Key key, this.card}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CardWrapper(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
|
||||||
|
child: Text("'${card.type}' card is not supported yet"),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
23
lib/cards/vertical_stack_card.dart
Normal file
23
lib/cards/vertical_stack_card.dart
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class VerticalStackCard extends StatelessWidget {
|
||||||
|
final VerticalStackCardData card;
|
||||||
|
|
||||||
|
const VerticalStackCard({Key key, this.card}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (card.childCards.isNotEmpty) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: card.childCards.map<Widget>(
|
||||||
|
(childCard) => childCard.buildCardWidget()
|
||||||
|
).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container(height: 0.0, width: 0.0,);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,12 +1,14 @@
|
|||||||
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 leading;
|
||||||
final Widget subtitle;
|
final Widget subtitle;
|
||||||
|
final double emptyPadding;
|
||||||
|
|
||||||
const CardHeaderWidget({Key key, this.name, this.trailing, this.subtitle}) : super(key: key);
|
const CardHeader({Key key, this.name, this.leading, this.emptyPadding: 0, this.trailing, this.subtitle}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -14,14 +16,15 @@ class CardHeaderWidget extends StatelessWidget {
|
|||||||
if ((name != null) && (name.trim().length > 0)) {
|
if ((name != null) && (name.trim().length > 0)) {
|
||||||
result = new ListTile(
|
result = new ListTile(
|
||||||
trailing: trailing,
|
trailing: trailing,
|
||||||
|
leading: leading,
|
||||||
subtitle: subtitle,
|
subtitle: subtitle,
|
||||||
title: Text("$name",
|
title: Text("$name",
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: Sizes.largeFontSize)),
|
style: Theme.of(context).textTheme.headline),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
result = new Container(width: 0.0, height: 0.0);
|
result = new Container(width: 0.0, height: emptyPadding);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
21
lib/cards/widgets/card_wrapper.widget.dart
Normal file
21
lib/cards/widgets/card_wrapper.widget.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class CardWrapper extends StatelessWidget {
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final EdgeInsets padding;
|
||||||
|
|
||||||
|
const CardWrapper({Key key, this.child, this.padding: const EdgeInsets.all(0)}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: padding,
|
||||||
|
child: child
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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 {
|
||||||
@ -46,6 +51,10 @@ class EntityUIAction {
|
|||||||
String holdNavigationPath;
|
String holdNavigationPath;
|
||||||
String holdService;
|
String holdService;
|
||||||
Map<String, dynamic> holdServiceData;
|
Map<String, dynamic> holdServiceData;
|
||||||
|
String doubleTapAction = EntityUIAction.none;
|
||||||
|
String doubleTapNavigationPath;
|
||||||
|
String doubleTapService;
|
||||||
|
Map<String, dynamic> doubleTapServiceData;
|
||||||
|
|
||||||
EntityUIAction({rawEntityData}) {
|
EntityUIAction({rawEntityData}) {
|
||||||
if (rawEntityData != null) {
|
if (rawEntityData != null) {
|
||||||
@ -71,29 +80,63 @@ class EntityUIAction {
|
|||||||
holdServiceData = rawEntityData["hold_action"]["service_data"];
|
holdServiceData = rawEntityData["hold_action"]["service_data"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (rawEntityData["double_tap_action"] != null) {
|
||||||
|
if (rawEntityData["double_tap_action"] is String) {
|
||||||
|
doubleTapAction = rawEntityData["double_tap_action"];
|
||||||
|
} else {
|
||||||
|
doubleTapAction =
|
||||||
|
rawEntityData["double_tap_action"]["action"] ?? EntityUIAction.none;
|
||||||
|
doubleTapNavigationPath = rawEntityData["double_tap_action"]["navigation_path"];
|
||||||
|
doubleTapService = rawEntityData["double_tap_action"]["service"];
|
||||||
|
doubleTapServiceData = rawEntityData["double_tap_action"]["service_data"];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 BUTTON = "button";
|
||||||
static const alarmPanel = "alarm-panel";
|
static const CONDITIONAL = "conditional";
|
||||||
static const markdown = "markdown";
|
static const ALARM_PANEL = "alarm-panel";
|
||||||
|
static const MARKDOWN = "markdown";
|
||||||
|
static const LIGHT = "light";
|
||||||
|
static const ENTITY_FILTER = "entity-filter";
|
||||||
|
}
|
||||||
|
|
||||||
|
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,7 +1,8 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class AlarmControlPanelEntity extends Entity {
|
class AlarmControlPanelEntity extends Entity {
|
||||||
AlarmControlPanelEntity(Map rawData) : super(rawData);
|
AlarmControlPanelEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
@ -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,
|
domain: entity.domain,
|
||||||
{"code": "$code"}));
|
service: service,
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"code": "$code"}
|
||||||
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
code = "";
|
code = "";
|
||||||
});
|
});
|
||||||
@ -58,7 +61,11 @@ 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(
|
||||||
|
domain: entity.domain,
|
||||||
|
service: "alarm_trigger",
|
||||||
|
entityId: entity.entityId
|
||||||
|
);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -241,7 +248,9 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
|
|||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text(
|
child: Text(
|
||||||
"TRIGGER",
|
"TRIGGER",
|
||||||
style: TextStyle(color: Colors.redAccent)
|
style: Theme.of(context).textTheme.subhead.copyWith(
|
||||||
|
color: Theme.of(context).errorColor
|
||||||
|
)
|
||||||
),
|
),
|
||||||
onPressed: () => _askToTrigger(entity),
|
onPressed: () => _askToTrigger(entity),
|
||||||
)
|
)
|
@ -1,7 +1,8 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class AutomationEntity extends Entity {
|
class AutomationEntity extends Entity {
|
||||||
AutomationEntity(Map rawData) : super(rawData);
|
AutomationEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget _buildStatePart(BuildContext context) {
|
Widget _buildStatePart(BuildContext context) {
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../main.dart';
|
||||||
|
|
||||||
class BadgeWidget extends StatelessWidget {
|
class BadgeWidget extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
@ -7,8 +7,7 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
double iconSize = 26.0;
|
double iconSize = 26.0;
|
||||||
Widget badgeIcon;
|
Widget badgeIcon;
|
||||||
String onBadgeTextValue;
|
String onBadgeTextValue;
|
||||||
Color iconColor = EntityColor.badgeColors[entityModel.entityWrapper.entity.domain] ??
|
Color iconColor = HAClientTheme().getBadgeColor(entityModel.entityWrapper.entity.domain);
|
||||||
EntityColor.badgeColors["default"];
|
|
||||||
switch (entityModel.entityWrapper.entity.domain) {
|
switch (entityModel.entityWrapper.entity.domain) {
|
||||||
case "sun":
|
case "sun":
|
||||||
{
|
{
|
||||||
@ -30,30 +29,43 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
badgeIcon = EntityIcon(
|
badgeIcon = EntityIcon(
|
||||||
padding: EdgeInsets.all(0.0),
|
padding: EdgeInsets.all(0.0),
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
color: Colors.black
|
color: Theme.of(context).textTheme.body1.color
|
||||||
);
|
);
|
||||||
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: Theme.of(context).textTheme.body1.color
|
||||||
);
|
);
|
||||||
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: Theme.of(context).textTheme.body1.copyWith(
|
||||||
|
fontSize: stateFontSize
|
||||||
|
)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@ -66,7 +78,9 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
onBadgeText = Container(
|
onBadgeText = Container(
|
||||||
padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0),
|
padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0),
|
||||||
child: Text("$onBadgeTextValue",
|
child: Text("$onBadgeTextValue",
|
||||||
style: TextStyle(fontSize: 12.0, color: Colors.white),
|
style: Theme.of(context).textTheme.overline.copyWith(
|
||||||
|
color: HAClientTheme().getOnBadgeTextColor()
|
||||||
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
softWrap: false,
|
softWrap: false,
|
||||||
overflow: TextOverflow.fade),
|
overflow: TextOverflow.fade),
|
||||||
@ -87,7 +101,7 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
decoration: new BoxDecoration(
|
decoration: new BoxDecoration(
|
||||||
// Circle shape
|
// Circle shape
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: Colors.white,
|
color: Theme.of(context).cardColor,
|
||||||
// The border you want
|
// The border you want
|
||||||
border: new Border.all(
|
border: new Border.all(
|
||||||
width: 2.0,
|
width: 2.0,
|
||||||
@ -120,7 +134,7 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
"${entityModel.entityWrapper.displayName}",
|
"${entityModel.entityWrapper.displayName}",
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(fontSize: 12.0),
|
style: Theme.of(context).textTheme.caption,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
@ -129,6 +143,6 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
eventBus.fire(new ShowEntityPageEvent(entityModel.entityWrapper.entity)));
|
eventBus.fire(new ShowEntityPageEvent(entity: entityModel.entityWrapper.entity)));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,8 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class ButtonEntity extends Entity {
|
class ButtonEntity extends Entity {
|
||||||
ButtonEntity(Map rawData) : super(rawData);
|
ButtonEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget _buildStatePart(BuildContext context) {
|
Widget _buildStatePart(BuildContext context) {
|
@ -1,14 +1,18 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class CameraEntity extends Entity {
|
class CameraEntity extends Entity {
|
||||||
|
|
||||||
static const SUPPORT_ON_OFF = 1;
|
static const SUPPORT_ON_OFF = 1;
|
||||||
|
static const SUPPORT_STREAM = 2;
|
||||||
|
|
||||||
CameraEntity(Map rawData) : super(rawData);
|
CameraEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
bool get supportOnOff => ((supportedFeatures &
|
bool get supportOnOff => ((supportedFeatures &
|
||||||
CameraEntity.SUPPORT_ON_OFF) ==
|
CameraEntity.SUPPORT_ON_OFF) ==
|
||||||
CameraEntity.SUPPORT_ON_OFF);
|
CameraEntity.SUPPORT_ON_OFF);
|
||||||
|
bool get supportStream => ((supportedFeatures &
|
||||||
|
CameraEntity.SUPPORT_STREAM) ==
|
||||||
|
CameraEntity.SUPPORT_STREAM);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
191
lib/entities/camera/widgets/camera_stream_view.dart
Normal file
191
lib/entities/camera/widgets/camera_stream_view.dart
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
part of '../../../main.dart';
|
||||||
|
|
||||||
|
class CameraStreamView extends StatefulWidget {
|
||||||
|
|
||||||
|
final bool withControls;
|
||||||
|
|
||||||
|
CameraStreamView({Key key, this.withControls: true}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CameraStreamViewState createState() => _CameraStreamViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CameraStreamViewState extends State<CameraStreamView> {
|
||||||
|
|
||||||
|
CameraEntity _entity;
|
||||||
|
String _streamUrl = "";
|
||||||
|
bool _isLoaded = false;
|
||||||
|
double _aspectRatio = 1.33;
|
||||||
|
String _webViewHtml;
|
||||||
|
String _jsMessageChannelName = 'unknown';
|
||||||
|
Completer _loading;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _loadResources() {
|
||||||
|
if (_loading != null && !_loading.isCompleted) {
|
||||||
|
Logger.d("[Camera Player] Resources loading is not finished yet");
|
||||||
|
return _loading.future;
|
||||||
|
}
|
||||||
|
Logger.d("[Camera Player] Loading resources");
|
||||||
|
_loading = Completer();
|
||||||
|
_entity = EntityModel
|
||||||
|
.of(context)
|
||||||
|
.entityWrapper
|
||||||
|
.entity;
|
||||||
|
if (_entity.supportStream && HomeAssistant().isComponentEnabled('stream')) {
|
||||||
|
HomeAssistant().getCameraStream(_entity.entityId)
|
||||||
|
.then((data) {
|
||||||
|
_jsMessageChannelName = 'HA_${_entity.entityId.replaceAll('.', '_')}';
|
||||||
|
rootBundle.loadString('assets/html/cameraLiveView.html').then((file) {
|
||||||
|
_webViewHtml = Uri.dataFromString(
|
||||||
|
file.replaceFirst('{{stream_url}}', '${ConnectionManager().httpWebHost}${data["url"]}').replaceFirst('{{message_channel}}', _jsMessageChannelName),
|
||||||
|
mimeType: 'text/html',
|
||||||
|
encoding: Encoding.getByName('utf-8')
|
||||||
|
).toString();
|
||||||
|
_loading.complete();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catchError((e) {
|
||||||
|
if (e == 'start_stream_failed') {
|
||||||
|
Logger.e("[Camera Player] Home Assistant failed starting stream. Forcing MJPEG: $e");
|
||||||
|
_loadMJPEG().then((_) {
|
||||||
|
_loading.complete();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_loading.completeError(e);
|
||||||
|
Logger.e("[Camera Player] Error loading stream: $e");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_loadMJPEG().then((_) {
|
||||||
|
_loading.complete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return _loading.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _loadMJPEG() async {
|
||||||
|
_streamUrl = '${ConnectionManager().httpWebHost}/api/camera_proxy_stream/${_entity
|
||||||
|
.entityId}?token=${_entity.attributes['access_token']}';
|
||||||
|
_jsMessageChannelName = 'HA_${_entity.entityId.replaceAll('.', '_')}';
|
||||||
|
var file = await rootBundle.loadString('assets/html/cameraView.html');
|
||||||
|
_webViewHtml = Uri.dataFromString(
|
||||||
|
file.replaceFirst('{{stream_url}}', _streamUrl).replaceFirst('{{message_channel}}', _jsMessageChannelName),
|
||||||
|
mimeType: 'text/html',
|
||||||
|
encoding: Encoding.getByName('utf-8')
|
||||||
|
).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildScreen() {
|
||||||
|
Widget screenWidget;
|
||||||
|
if (!_isLoaded) {
|
||||||
|
screenWidget = Center(
|
||||||
|
child: EntityPicture(
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
screenWidget = WebView(
|
||||||
|
initialUrl: _webViewHtml,
|
||||||
|
initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
|
||||||
|
debuggingEnabled: Logger.isInDebugMode,
|
||||||
|
gestureNavigationEnabled: false,
|
||||||
|
javascriptMode: JavascriptMode.unrestricted,
|
||||||
|
javascriptChannels: {
|
||||||
|
JavascriptChannel(
|
||||||
|
name: _jsMessageChannelName,
|
||||||
|
onMessageReceived: ((message) {
|
||||||
|
Logger.d('[Camera Player] Message from page: $message');
|
||||||
|
setState((){
|
||||||
|
_aspectRatio = double.tryParse(message.message) ?? 1.33;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return AspectRatio(
|
||||||
|
aspectRatio: _aspectRatio,
|
||||||
|
child: screenWidget
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildControls() {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.refresh),
|
||||||
|
iconSize: 40,
|
||||||
|
color: Theme.of(context).accentColor,
|
||||||
|
onPressed: _isLoaded ? () {
|
||||||
|
setState(() {
|
||||||
|
_isLoaded = false;
|
||||||
|
});
|
||||||
|
} : null,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.fullscreen),
|
||||||
|
iconSize: 40,
|
||||||
|
color: Theme.of(context).accentColor,
|
||||||
|
onPressed: _isLoaded ? () {
|
||||||
|
eventBus.fire(ShowEntityPageEvent());
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (conext) => FullScreenPage(
|
||||||
|
child: EntityModel(
|
||||||
|
child: CameraStreamView(
|
||||||
|
withControls: false
|
||||||
|
),
|
||||||
|
handleTap: false,
|
||||||
|
entityWrapper: EntityWrapper(
|
||||||
|
entity: _entity
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
fullscreenDialog: true
|
||||||
|
)
|
||||||
|
).then((_) {
|
||||||
|
eventBus.fire(ShowEntityPageEvent(entity: _entity));
|
||||||
|
});
|
||||||
|
} : null,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (!_isLoaded && (_loading == null || _loading.isCompleted)) {
|
||||||
|
_loadResources().then((_) => setState((){ _isLoaded = true; }));
|
||||||
|
}
|
||||||
|
if (widget.withControls) {
|
||||||
|
return Card(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
_buildScreen(),
|
||||||
|
_buildControls()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return _buildScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,69 +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);
|
||||||
|
|
||||||
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;
|
||||||
@ -81,25 +69,22 @@ 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";
|
||||||
|
|
||||||
ClimateEntity(Map rawData) : super(rawData);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void update(Map rawData) {
|
void update(Map rawData, String webHost) {
|
||||||
super.update(rawData);
|
super.update(rawData, webHost);
|
||||||
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 {
|
||||||
|
|
||||||
@ -10,35 +10,35 @@ class ClimateControlWidget extends StatefulWidget {
|
|||||||
|
|
||||||
class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||||
|
|
||||||
bool _showPending = false;
|
bool _temperaturePending = false;
|
||||||
bool _changedHere = false;
|
bool _changedHere = false;
|
||||||
Timer _resetTimer;
|
|
||||||
Timer _tempThrottleTimer;
|
Timer _tempThrottleTimer;
|
||||||
Timer _targetTempThrottleTimer;
|
Timer _targetTempThrottleTimer;
|
||||||
double _tmpTemperature = 0.0;
|
double _tmpTemperature = 0.0;
|
||||||
double _tmpTargetLow = 0.0;
|
double _tmpTargetLow = 0.0;
|
||||||
double _tmpTargetHigh = 0.0;
|
double _tmpTargetHigh = 0.0;
|
||||||
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;
|
if (!_temperaturePending) {
|
||||||
_tmpTargetHigh = entity.targetHigh;
|
_tmpTemperature = entity.temperature;
|
||||||
_tmpTargetLow = entity.targetLow;
|
_tmpTargetHigh = entity.targetHigh;
|
||||||
_tmpOperationMode = entity.operationMode;
|
_tmpTargetLow = entity.targetLow;
|
||||||
|
}
|
||||||
|
_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;
|
||||||
|
|
||||||
_showPending = false;
|
|
||||||
_changedHere = false;
|
_changedHere = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,36 +73,44 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _setTemperature(ClimateEntity entity) {
|
void _setTemperature(ClimateEntity entity) {
|
||||||
if (_tempThrottleTimer!=null) {
|
_tempThrottleTimer?.cancel();
|
||||||
_tempThrottleTimer.cancel();
|
|
||||||
}
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
|
_temperaturePending = true;
|
||||||
_tmpTemperature = double.parse(_tmpTemperature.toStringAsFixed(1));
|
_tmpTemperature = double.parse(_tmpTemperature.toStringAsFixed(1));
|
||||||
});
|
});
|
||||||
_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)}"}));
|
_temperaturePending = false;
|
||||||
_resetStateTimer(entity);
|
ConnectionManager().callService(
|
||||||
|
domain: entity.domain,
|
||||||
|
service: "set_temperature",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"temperature": "${_tmpTemperature.toStringAsFixed(1)}"}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setTargetTemp(ClimateEntity entity) {
|
void _setTargetTemp(ClimateEntity entity) {
|
||||||
if (_targetTempThrottleTimer!=null) {
|
_targetTempThrottleTimer?.cancel();
|
||||||
_targetTempThrottleTimer.cancel();
|
|
||||||
}
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
|
_temperaturePending = true;
|
||||||
_tmpTargetLow = double.parse(_tmpTargetLow.toStringAsFixed(1));
|
_tmpTargetLow = double.parse(_tmpTargetLow.toStringAsFixed(1));
|
||||||
_tmpTargetHigh = double.parse(_tmpTargetHigh.toStringAsFixed(1));
|
_tmpTargetHigh = double.parse(_tmpTargetHigh.toStringAsFixed(1));
|
||||||
});
|
});
|
||||||
_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)}"}));
|
_temperaturePending = false;
|
||||||
_resetStateTimer(entity);
|
ConnectionManager().callService(
|
||||||
|
domain: entity.domain,
|
||||||
|
service: "set_temperature",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"target_temp_high": "${_tmpTargetHigh.toStringAsFixed(1)}", "target_temp_low": "${_tmpTargetLow.toStringAsFixed(1)}"}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -111,17 +119,25 @@ 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(
|
||||||
_resetStateTimer(entity);
|
domain: entity.domain,
|
||||||
|
service: "set_humidity",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"humidity": "$_tmpTargetHumidity"}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
||||||
_resetStateTimer(entity);
|
domain: entity.domain,
|
||||||
|
service: "set_hvac_mode",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"hvac_mode": "$_tmpHVACMode"}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,8 +145,12 @@ 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(
|
||||||
_resetStateTimer(entity);
|
domain: entity.domain,
|
||||||
|
service: "set_swing_mode",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"swing_mode": "$_tmpSwingMode"}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,45 +158,32 @@ 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(domain: entity.domain, service: "set_fan_mode", entityId: entity.entityId, data: {"fan_mode": "$_tmpFanMode"});
|
||||||
_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(domain: entity.domain, service: "set_preset_mode", entityId: entity.entityId, data: {"preset_mode": "$_tmpPresetMode"});
|
||||||
_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(domain: entity.domain, service: "set_aux_heat", entityId: entity.entityId, data: {"aux_heat": "$_tmpAuxHeat"});
|
||||||
_resetStateTimer(entity);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _resetStateTimer(ClimateEntity entity) {
|
|
||||||
if (_resetTimer!=null) {
|
|
||||||
_resetTimer.cancel();
|
|
||||||
}
|
|
||||||
_resetTimer = Timer(Duration(seconds: 3), () {
|
|
||||||
setState(() {});
|
|
||||||
_resetVars(entity);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,11 +191,11 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final entityModel = EntityModel.of(context);
|
final entityModel = EntityModel.of(context);
|
||||||
final ClimateEntity entity = entityModel.entityWrapper.entity;
|
final ClimateEntity entity = entityModel.entityWrapper.entity;
|
||||||
|
Logger.d("[Climate widget build] changed here = $_changedHere");
|
||||||
if (_changedHere) {
|
if (_changedHere) {
|
||||||
_showPending = (_tmpTemperature != entity.temperature || _tmpTargetHigh != entity.targetHigh || _tmpTargetLow != entity.targetLow);
|
//_showPending = (_tmpTemperature != entity.temperature || _tmpTargetHigh != entity.targetHigh || _tmpTargetLow != entity.targetLow);
|
||||||
_changedHere = false;
|
_changedHere = false;
|
||||||
} else {
|
} else {
|
||||||
_resetTimer?.cancel();
|
|
||||||
_resetVars(entity);
|
_resetVars(entity);
|
||||||
}
|
}
|
||||||
return Padding(
|
return Padding(
|
||||||
@ -196,33 +203,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, context),
|
||||||
_buildTargetTemperatureControls(entity),
|
_buildTargetTemperatureControls(entity, context),
|
||||||
_buildHumidityControls(entity),
|
_buildHumidityControls(entity, context),
|
||||||
_buildOperationControl(entity),
|
_buildOperationControl(entity, context),
|
||||||
_buildFanControl(entity),
|
_buildFanControl(entity, context),
|
||||||
_buildSwingControl(entity),
|
_buildSwingControl(entity, context),
|
||||||
_buildAwayModeControl(entity),
|
_buildPresetModeControl(entity, context),
|
||||||
_buildAuxHeatControl(entity)
|
_buildAuxHeatControl(entity, context)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAwayModeControl(ClimateEntity entity) {
|
Widget _buildPresetModeControl(ClimateEntity entity, BuildContext context) {
|
||||||
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,9 +240,9 @@ 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, BuildContext context) {
|
||||||
if (entity.supportAuxHeat ) {
|
if (entity.supportAuxHeat ) {
|
||||||
return ModeSwitchWidget(
|
return ModeSwitchWidget(
|
||||||
caption: "Aux heat",
|
caption: "Aux heat",
|
||||||
@ -246,23 +254,23 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildOperationControl(ClimateEntity entity) {
|
Widget _buildOperationControl(ClimateEntity entity, BuildContext context) {
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFanControl(ClimateEntity entity) {
|
Widget _buildFanControl(ClimateEntity entity, BuildContext context) {
|
||||||
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,
|
||||||
@ -272,11 +280,11 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSwingControl(ClimateEntity entity) {
|
Widget _buildSwingControl(ClimateEntity entity, BuildContext context) {
|
||||||
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"
|
||||||
);
|
);
|
||||||
@ -285,17 +293,15 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTemperatureControls(ClimateEntity entity) {
|
Widget _buildTemperatureControls(ClimateEntity entity, BuildContext context) {
|
||||||
if ((entity.supportTargetTemperature) && (entity.temperature != null)) {
|
if ((entity.supportTargetTemperature) && (entity.temperature != null)) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text("Target temperature", style: TextStyle(
|
Text("Target temperature", style: Theme.of(context).textTheme.body1),
|
||||||
fontSize: Sizes.stateFontSize
|
|
||||||
)),
|
|
||||||
TemperatureControlWidget(
|
TemperatureControlWidget(
|
||||||
value: _tmpTemperature,
|
value: _tmpTemperature,
|
||||||
fontColor: _showPending ? Colors.red : Colors.black,
|
active: _temperaturePending,
|
||||||
onDec: () => _temperatureDown(entity),
|
onDec: () => _temperatureDown(entity),
|
||||||
onInc: () => _temperatureUp(entity),
|
onInc: () => _temperatureUp(entity),
|
||||||
)
|
)
|
||||||
@ -306,13 +312,13 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTargetTemperatureControls(ClimateEntity entity) {
|
Widget _buildTargetTemperatureControls(ClimateEntity entity, BuildContext context) {
|
||||||
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,
|
||||||
fontColor: _showPending ? Colors.red : Colors.black,
|
active: _temperaturePending,
|
||||||
onDec: () => _targetLowDown(entity),
|
onDec: () => _targetLowDown(entity),
|
||||||
onInc: () => _targetLowUp(entity),
|
onInc: () => _targetLowUp(entity),
|
||||||
),
|
),
|
||||||
@ -321,11 +327,11 @@ 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,
|
||||||
fontColor: _showPending ? Colors.red : Colors.black,
|
active: _temperaturePending,
|
||||||
onDec: () => _targetHighDown(entity),
|
onDec: () => _targetHighDown(entity),
|
||||||
onInc: () => _targetHighUp(entity),
|
onInc: () => _targetHighUp(entity),
|
||||||
)
|
)
|
||||||
@ -335,9 +341,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text("Target temperature range", style: TextStyle(
|
Text("Target temperature range", style: Theme.of(context).textTheme.body1),
|
||||||
fontSize: Sizes.stateFontSize
|
|
||||||
)),
|
|
||||||
Row(
|
Row(
|
||||||
children: controls,
|
children: controls,
|
||||||
)
|
)
|
||||||
@ -348,13 +352,13 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildHumidityControls(ClimateEntity entity) {
|
Widget _buildHumidityControls(ClimateEntity entity, BuildContext context) {
|
||||||
List<Widget> result = [];
|
List<Widget> result = [];
|
||||||
if (entity.supportTargetHumidity) {
|
if (entity.supportTargetHumidity) {
|
||||||
result.addAll(<Widget>[
|
result.addAll(<Widget>[
|
||||||
Text(
|
Text(
|
||||||
"$_tmpTargetHumidity%",
|
"$_tmpTargetHumidity%",
|
||||||
style: TextStyle(fontSize: Sizes.largeFontSize),
|
style: Theme.of(context).textTheme.display1,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Slider(
|
child: Slider(
|
||||||
@ -379,9 +383,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||||
child: Text("Target humidity", style: TextStyle(
|
child: Text("Target humidity", style: Theme.of(context).textTheme.body1),
|
||||||
fontSize: Sizes.stateFontSize
|
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
@ -403,57 +405,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_resetTimer?.cancel();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,25 +31,18 @@ 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: Theme.of(context).textTheme.body2),
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: Sizes.stateFontSize,
|
|
||||||
)),
|
|
||||||
Text(" $targetTemp",
|
Text(" $targetTemp",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: new TextStyle(
|
style: Theme.of(context).textTheme.body1)
|
||||||
fontSize: Sizes.stateFontSize,
|
|
||||||
))
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
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: Theme.of(context).textTheme.subtitle
|
||||||
fontSize: Sizes.stateFontSize,
|
|
||||||
color: Colors.black45)
|
|
||||||
) :
|
) :
|
||||||
Container(height: 0.0,)
|
Container(height: 0.0,)
|
||||||
],
|
],
|
@ -1,12 +1,10 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class ModeSelectorWidget extends StatelessWidget {
|
class ModeSelectorWidget extends StatelessWidget {
|
||||||
|
|
||||||
final String caption;
|
final String caption;
|
||||||
final List<String> options;
|
final List options;
|
||||||
final String value;
|
final String value;
|
||||||
final double captionFontSize;
|
|
||||||
final double valueFontSize;
|
|
||||||
final onChange;
|
final onChange;
|
||||||
final EdgeInsets padding;
|
final EdgeInsets padding;
|
||||||
|
|
||||||
@ -16,8 +14,6 @@ class ModeSelectorWidget extends StatelessWidget {
|
|||||||
@required this.options,
|
@required this.options,
|
||||||
this.value,
|
this.value,
|
||||||
@required this.onChange,
|
@required this.onChange,
|
||||||
this.captionFontSize,
|
|
||||||
this.valueFontSize,
|
|
||||||
this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
|
this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -28,9 +24,7 @@ class ModeSelectorWidget extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text("$caption", style: TextStyle(
|
Text("$caption", style: Theme.of(context).textTheme.body1),
|
||||||
fontSize: captionFontSize ?? Sizes.stateFontSize
|
|
||||||
)),
|
|
||||||
Row(
|
Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -40,15 +34,12 @@ class ModeSelectorWidget extends StatelessWidget {
|
|||||||
value: value,
|
value: value,
|
||||||
iconSize: 30.0,
|
iconSize: 30.0,
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.title,
|
||||||
fontSize: valueFontSize ?? Sizes.largeFontSize,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
hint: Text("Select ${caption.toLowerCase()}"),
|
hint: Text("Select ${caption.toLowerCase()}"),
|
||||||
items: options.map((String value) {
|
items: options.map((value) {
|
||||||
return new DropdownMenuItem<String>(
|
return new DropdownMenuItem<String>(
|
||||||
value: value,
|
value: '$value',
|
||||||
child: Text(value),
|
child: Text('$value'),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
onChanged: (mode) => onChange(mode),
|
onChanged: (mode) => onChange(mode),
|
@ -1,10 +1,9 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class ModeSwitchWidget extends StatelessWidget {
|
class ModeSwitchWidget extends StatelessWidget {
|
||||||
|
|
||||||
final String caption;
|
final String caption;
|
||||||
final onChange;
|
final onChange;
|
||||||
final double captionFontSize;
|
|
||||||
final bool value;
|
final bool value;
|
||||||
final bool expanded;
|
final bool expanded;
|
||||||
final EdgeInsets padding;
|
final EdgeInsets padding;
|
||||||
@ -13,7 +12,6 @@ class ModeSwitchWidget extends StatelessWidget {
|
|||||||
Key key,
|
Key key,
|
||||||
@required this.caption,
|
@required this.caption,
|
||||||
@required this.onChange,
|
@required this.onChange,
|
||||||
this.captionFontSize,
|
|
||||||
this.value,
|
this.value,
|
||||||
this.expanded: true,
|
this.expanded: true,
|
||||||
this.padding: const EdgeInsets.only(left: Sizes.leftWidgetPadding, right: Sizes.rightWidgetPadding)
|
this.padding: const EdgeInsets.only(left: Sizes.leftWidgetPadding, right: Sizes.rightWidgetPadding)
|
||||||
@ -25,7 +23,7 @@ class ModeSwitchWidget extends StatelessWidget {
|
|||||||
padding: this.padding,
|
padding: this.padding,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildCaption(),
|
_buildCaption(context),
|
||||||
Switch(
|
Switch(
|
||||||
onChanged: (value) => onChange(value),
|
onChanged: (value) => onChange(value),
|
||||||
value: value ?? false,
|
value: value ?? false,
|
||||||
@ -35,12 +33,10 @@ class ModeSwitchWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCaption() {
|
Widget _buildCaption(BuildContext context) {
|
||||||
Widget captionWidget = Text(
|
Widget captionWidget = Text(
|
||||||
"$caption",
|
"$caption",
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.body1,
|
||||||
fontSize: captionFontSize ?? Sizes.stateFontSize
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
return Expanded(
|
return Expanded(
|
47
lib/entities/climate/widgets/temperature_control_widget.dart
Normal file
47
lib/entities/climate/widgets/temperature_control_widget.dart
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
part of '../../../main.dart';
|
||||||
|
|
||||||
|
class TemperatureControlWidget extends StatelessWidget {
|
||||||
|
final double value;
|
||||||
|
final bool active;
|
||||||
|
final onInc;
|
||||||
|
final onDec;
|
||||||
|
|
||||||
|
TemperatureControlWidget(
|
||||||
|
{Key key,
|
||||||
|
@required this.value,
|
||||||
|
@required this.onInc,
|
||||||
|
@required this.onDec,
|
||||||
|
//this.fontSize,
|
||||||
|
this.active: false
|
||||||
|
})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
"$value",
|
||||||
|
style: active ? Theme.of(context).textTheme.display2 : Theme.of(context).textTheme.display1,
|
||||||
|
),
|
||||||
|
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 {
|
||||||
|
|
||||||
@ -11,6 +11,8 @@ class CoverEntity extends Entity {
|
|||||||
static const SUPPORT_STOP_TILT = 64;
|
static const SUPPORT_STOP_TILT = 64;
|
||||||
static const SUPPORT_SET_TILT_POSITION = 128;
|
static const SUPPORT_SET_TILT_POSITION = 128;
|
||||||
|
|
||||||
|
CoverEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
bool get supportOpen => ((supportedFeatures &
|
bool get supportOpen => ((supportedFeatures &
|
||||||
CoverEntity.SUPPORT_OPEN) ==
|
CoverEntity.SUPPORT_OPEN) ==
|
||||||
CoverEntity.SUPPORT_OPEN);
|
CoverEntity.SUPPORT_OPEN);
|
||||||
@ -45,8 +47,6 @@ class CoverEntity extends Entity {
|
|||||||
bool get canTiltBeOpened => currentTiltPosition < 100;
|
bool get canTiltBeOpened => currentTiltPosition < 100;
|
||||||
bool get canTiltBeClosed => currentTiltPosition > 0;
|
bool get canTiltBeClosed => currentTiltPosition > 0;
|
||||||
|
|
||||||
CoverEntity(Map rawData) : super(rawData);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget _buildStatePart(BuildContext context) {
|
Widget _buildStatePart(BuildContext context) {
|
||||||
return CoverStateWidget();
|
return CoverStateWidget();
|
@ -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(domain: entity.domain, service: "set_cover_position", entityId: entity.entityId, data: {"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(domain: entity.domain, service: "set_cover_tilt_position", entityId: entity.entityId, data: {"tilt_position": _tmpTiltPosition.round()});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,9 +64,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||||
child: Text("Position", style: TextStyle(
|
child: Text("Position"),
|
||||||
fontSize: Sizes.stateFontSize
|
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
Slider(
|
Slider(
|
||||||
value: _tmpPosition,
|
value: _tmpPosition,
|
||||||
@ -118,9 +116,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
|||||||
controls.insert(0, Padding(
|
controls.insert(0, Padding(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||||
child: Text("Tilt position", style: TextStyle(
|
child: Text("Tilt position"),
|
||||||
fontSize: Sizes.stateFontSize
|
|
||||||
)),
|
|
||||||
));
|
));
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -135,18 +131,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));
|
domain: entity.domain, service: "open_cover_tilt", entityId: entity.entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _close(CoverEntity entity) {
|
void _close(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "close_cover_tilt", entity.entityId, null));
|
domain: entity.domain, service: "close_cover_tilt", entityId: entity.entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _stop(CoverEntity entity) {
|
void _stop(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "stop_cover_tilt", entity.entityId, null));
|
domain: entity.domain, service: "stop_cover_tilt", entityId: entity.entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
@ -1,19 +1,28 @@
|
|||||||
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));
|
domain: entity.domain,
|
||||||
|
service: "open_cover",
|
||||||
|
entityId: entity.entityId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _close(CoverEntity entity) {
|
void _close(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "close_cover", entity.entityId, null));
|
domain: entity.domain,
|
||||||
|
service: "close_cover",
|
||||||
|
entityId: entity.entityId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _stop(CoverEntity entity) {
|
void _stop(CoverEntity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent(
|
ConnectionManager().callService(
|
||||||
entity.domain, "stop_cover", entity.entityId, null));
|
domain: entity.domain,
|
||||||
|
service: "stop_cover",
|
||||||
|
entityId: entity.entityId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
@ -1,6 +1,8 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class DateTimeEntity extends Entity {
|
class DateTimeEntity extends Entity {
|
||||||
|
DateTimeEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
bool get hasDate => attributes["has_date"] ?? false;
|
bool get hasDate => attributes["has_date"] ?? false;
|
||||||
bool get hasTime => attributes["has_time"] ?? false;
|
bool get hasTime => attributes["has_time"] ?? false;
|
||||||
int get year => attributes["year"] ?? 1970;
|
int get year => attributes["year"] ?? 1970;
|
||||||
@ -12,8 +14,6 @@ class DateTimeEntity extends Entity {
|
|||||||
String get formattedState => _getFormattedState();
|
String get formattedState => _getFormattedState();
|
||||||
DateTime get dateTimeState => _getDateTimeState();
|
DateTime get dateTimeState => _getDateTimeState();
|
||||||
|
|
||||||
DateTimeEntity(Map rawData) : super(rawData);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget _buildStatePart(BuildContext context) {
|
Widget _buildStatePart(BuildContext context) {
|
||||||
return DateTimeStateWidget();
|
return DateTimeStateWidget();
|
||||||
@ -35,8 +35,7 @@ class DateTimeEntity extends Entity {
|
|||||||
return formattedState;
|
return formattedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setNewState(newValue) {
|
void setNewState(Map newValue) {
|
||||||
eventBus
|
ConnectionManager().callService(domain: domain, service: "set_datetime", entityId: entityId, data: 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
|
||||||
@ -9,10 +9,8 @@ class DateTimeStateWidget extends StatelessWidget {
|
|||||||
padding: EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0),
|
padding: EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
child: Text("${entity.formattedState}",
|
child: Text("${entity.formattedState}",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right
|
||||||
style: new TextStyle(
|
),
|
||||||
fontSize: Sizes.stateFontSize,
|
|
||||||
)),
|
|
||||||
onTap: () => _handleStateTap(context, entity),
|
onTap: () => _handleStateTap(context, entity),
|
||||||
));
|
));
|
||||||
}
|
}
|
73
lib/entities/default_entity_container.widget.dart
Normal file
73
lib/entities/default_entity_container.widget.dart
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class DefaultEntityContainer extends StatelessWidget {
|
||||||
|
DefaultEntityContainer({
|
||||||
|
Key key,
|
||||||
|
@required this.state
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final Widget state;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final EntityModel entityModel = EntityModel.of(context);
|
||||||
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.missed) {
|
||||||
|
return MissedEntityWidget();
|
||||||
|
}
|
||||||
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.divider) {
|
||||||
|
return Divider();
|
||||||
|
}
|
||||||
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.section) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Divider(),
|
||||||
|
Text(
|
||||||
|
"${entityModel.entityWrapper.entity.displayName}",
|
||||||
|
style: HAClientTheme().getLinkTextStyle(context).copyWith(
|
||||||
|
decoration: TextDecoration.none
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Widget result = Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: <Widget>[
|
||||||
|
EntityIcon(),
|
||||||
|
|
||||||
|
Flexible(
|
||||||
|
fit: FlexFit.tight,
|
||||||
|
flex: 3,
|
||||||
|
child: EntityName(
|
||||||
|
padding: EdgeInsets.fromLTRB(10.0, 2.0, 10.0, 2.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
state
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if (entityModel.handleTap) {
|
||||||
|
return InkWell(
|
||||||
|
onLongPress: () {
|
||||||
|
if (entityModel.handleTap) {
|
||||||
|
entityModel.entityWrapper.handleHold();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTap: () {
|
||||||
|
if (entityModel.handleTap) {
|
||||||
|
entityModel.entityWrapper.handleTap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDoubleTap: () {
|
||||||
|
if (entityModel.handleTap) {
|
||||||
|
entityModel.entityWrapper.handleDoubleTap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: result,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,6 @@
|
|||||||
part of '../main.dart';
|
part of '../main.dart';
|
||||||
|
|
||||||
class StatelessEntityType {
|
enum StatelessEntityType {none, missed, ghost, divider, section, callService, webLink}
|
||||||
static const NONE = 0;
|
|
||||||
static const MISSED = 1;
|
|
||||||
static const DIVIDER = 2;
|
|
||||||
static const SECTION = 3;
|
|
||||||
static const CALL_SERVICE = 4;
|
|
||||||
static const WEBLINK = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Entity {
|
class Entity {
|
||||||
|
|
||||||
@ -27,7 +20,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",
|
||||||
@ -73,10 +66,11 @@ class Entity {
|
|||||||
Map attributes;
|
Map attributes;
|
||||||
String domain;
|
String domain;
|
||||||
String entityId;
|
String entityId;
|
||||||
|
String entityPicture;
|
||||||
String state;
|
String state;
|
||||||
String displayState;
|
String displayState;
|
||||||
DateTime _lastUpdated;
|
DateTime lastUpdatedTimestamp;
|
||||||
int statelessType = 0;
|
StatelessEntityType statelessType = StatelessEntityType.none;
|
||||||
|
|
||||||
List<Entity> childEntities = [];
|
List<Entity> childEntities = [];
|
||||||
String deviceClass;
|
String deviceClass;
|
||||||
@ -94,7 +88,6 @@ class Entity {
|
|||||||
bool get isBadge => Entity.badgeDomains.contains(domain);
|
bool get isBadge => Entity.badgeDomains.contains(domain);
|
||||||
String get icon => attributes["icon"] ?? "";
|
String get icon => attributes["icon"] ?? "";
|
||||||
bool get isOn => state == EntityState.on;
|
bool get isOn => state == EntityState.on;
|
||||||
String get entityPicture => _getEntityPictureUrl();
|
|
||||||
String get unitOfMeasurement => attributes["unit_of_measurement"] ?? "";
|
String get unitOfMeasurement => attributes["unit_of_measurement"] ?? "";
|
||||||
List get childEntityIds => attributes["entity_id"] ?? [];
|
List get childEntityIds => attributes["entity_id"] ?? [];
|
||||||
String get lastUpdated => _getLastUpdatedFormatted();
|
String get lastUpdated => _getLastUpdatedFormatted();
|
||||||
@ -102,60 +95,66 @@ class Entity {
|
|||||||
double get doubleState => double.tryParse(state) ?? 0.0;
|
double get doubleState => double.tryParse(state) ?? 0.0;
|
||||||
int get supportedFeatures => attributes["supported_features"] ?? 0;
|
int get supportedFeatures => attributes["supported_features"] ?? 0;
|
||||||
|
|
||||||
String _getEntityPictureUrl() {
|
String _getEntityPictureUrl(String webHost) {
|
||||||
String result = attributes["entity_picture"];
|
String result = attributes["entity_picture"];
|
||||||
if (result == null) return result;
|
if (result == null) return result;
|
||||||
if (!result.startsWith("http")) {
|
if (!result.startsWith("http")) {
|
||||||
if (result.startsWith("/")) {
|
if (result.startsWith("/")) {
|
||||||
result = "$homeAssistantWebHost$result";
|
result = "$webHost$result";
|
||||||
} else {
|
} else {
|
||||||
result = "$homeAssistantWebHost/$result";
|
result = "$webHost/$result";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity(Map rawData) {
|
Entity(Map rawData, String webHost) {
|
||||||
update(rawData);
|
update(rawData, webHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity.missed(String entityId) {
|
Entity.missed(String entityId) {
|
||||||
statelessType = StatelessEntityType.MISSED;
|
statelessType = StatelessEntityType.missed;
|
||||||
attributes = {"hidden": false};
|
attributes = {"hidden": false};
|
||||||
this.entityId = entityId;
|
this.entityId = entityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity.divider() {
|
Entity.divider() {
|
||||||
statelessType = StatelessEntityType.DIVIDER;
|
statelessType = StatelessEntityType.divider;
|
||||||
attributes = {"hidden": false};
|
attributes = {"hidden": false};
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity.section(String label) {
|
Entity.section(String label) {
|
||||||
statelessType = StatelessEntityType.SECTION;
|
statelessType = StatelessEntityType.section;
|
||||||
attributes = {"hidden": false, "friendly_name": "$label"};
|
attributes = {"hidden": false, "friendly_name": "$label"};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Entity.ghost(String name, String icon) {
|
||||||
|
statelessType = StatelessEntityType.ghost;
|
||||||
|
attributes = {"icon": icon, "hidden": false, "friendly_name": name};
|
||||||
|
}
|
||||||
|
|
||||||
Entity.callService({String icon, String name, String service, String actionName}) {
|
Entity.callService({String icon, String name, String service, String actionName}) {
|
||||||
statelessType = StatelessEntityType.CALL_SERVICE;
|
statelessType = StatelessEntityType.callService;
|
||||||
entityId = service;
|
entityId = service;
|
||||||
displayState = actionName?.toUpperCase() ?? "RUN";
|
displayState = actionName?.toUpperCase() ?? "RUN";
|
||||||
attributes = {"hidden": false, "friendly_name": "$name", "icon": "$icon"};
|
attributes = {"hidden": false, "friendly_name": "$name", "icon": "$icon"};
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity.weblink({String url, String name, String icon}) {
|
Entity.weblink({String url, String name, String icon}) {
|
||||||
statelessType = StatelessEntityType.WEBLINK;
|
statelessType = StatelessEntityType.webLink;
|
||||||
entityId = "custom.custom"; //TODO wtf??
|
entityId = "custom.custom";
|
||||||
attributes = {"hidden": false, "friendly_name": "${name ?? url}", "icon": "${icon ?? 'mdi:link'}"};
|
attributes = {"hidden": false, "friendly_name": "${name ?? url}", "icon": "${icon ?? 'mdi:link'}"};
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(Map rawData) {
|
void update(Map rawData, String webHost) {
|
||||||
attributes = rawData["attributes"] ?? {};
|
attributes = rawData["attributes"] ?? {};
|
||||||
domain = rawData["entity_id"].split(".")[0];
|
domain = rawData["entity_id"].split(".")[0];
|
||||||
entityId = rawData["entity_id"];
|
entityId = rawData["entity_id"];
|
||||||
deviceClass = attributes["device_class"];
|
deviceClass = attributes["device_class"];
|
||||||
state = rawData["state"];
|
state = rawData["state"] is bool ? (rawData["state"] ? EntityState.on : EntityState.off) : rawData["state"];
|
||||||
displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? state;
|
displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? (state.toLowerCase() == 'unknown' ? '-' : state);
|
||||||
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
|
lastUpdatedTimestamp = DateTime.tryParse(rawData["last_updated"]);
|
||||||
|
entityPicture = _getEntityPictureUrl(webHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
double _getDoubleAttributeValue(String attributeName) {
|
double _getDoubleAttributeValue(String attributeName) {
|
||||||
@ -210,31 +209,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),
|
||||||
@ -245,17 +219,17 @@ class Entity {
|
|||||||
|
|
||||||
String getAttribute(String attributeName) {
|
String getAttribute(String attributeName) {
|
||||||
if (attributes != null) {
|
if (attributes != null) {
|
||||||
return attributes["$attributeName"];
|
return attributes["$attributeName"].toString();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getLastUpdatedFormatted() {
|
String _getLastUpdatedFormatted() {
|
||||||
if (_lastUpdated == null) {
|
if (lastUpdatedTimestamp == null) {
|
||||||
return "-";
|
return "-";
|
||||||
} else {
|
} else {
|
||||||
DateTime now = DateTime.now();
|
DateTime now = DateTime.now();
|
||||||
Duration d = now.difference(_lastUpdated);
|
Duration d = now.difference(lastUpdatedTimestamp);
|
||||||
String text;
|
String text;
|
||||||
int v;
|
int v;
|
||||||
if (d.inDays == 0) {
|
if (d.inDays == 0) {
|
@ -50,24 +50,29 @@ class EntityIcon extends StatelessWidget {
|
|||||||
iconCode = getDefaultIconByEntityId(data.entity.entityId,
|
iconCode = getDefaultIconByEntityId(data.entity.entityId,
|
||||||
data.entity.deviceClass, data.entity.state); //
|
data.entity.deviceClass, data.entity.state); //
|
||||||
}
|
}
|
||||||
return Padding(
|
return Icon(
|
||||||
padding: EdgeInsets.fromLTRB(6.0, 6.0, 6.0, 6.0),
|
IconData(iconCode, fontFamily: 'Material Design Icons'),
|
||||||
child: Icon(
|
size: size,
|
||||||
IconData(iconCode, fontFamily: 'Material Design Icons'),
|
color: color,
|
||||||
size: size,
|
|
||||||
color: color,
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||||
|
Color iconColor;
|
||||||
|
if (color != null) {
|
||||||
|
iconColor = color;
|
||||||
|
} else if (entityWrapper.stateColor) {
|
||||||
|
iconColor = HAClientTheme().getColorByEntityState(entityWrapper.entity.state, context);
|
||||||
|
} else {
|
||||||
|
iconColor = HAClientTheme().getOffStateColor(context);
|
||||||
|
}
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
child: buildIcon(
|
child: buildIcon(
|
||||||
entityWrapper,
|
entityWrapper,
|
||||||
color ?? EntityColor.stateColor(entityWrapper.entity.state)
|
iconColor
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
22
lib/entities/entity_model.widget.dart
Normal file
22
lib/entities/entity_model.widget.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class EntityModel extends InheritedWidget {
|
||||||
|
const EntityModel({
|
||||||
|
Key key,
|
||||||
|
@required this.entityWrapper,
|
||||||
|
@required this.handleTap,
|
||||||
|
@required Widget child,
|
||||||
|
}) : super(key: key, child: child);
|
||||||
|
|
||||||
|
final EntityWrapper entityWrapper;
|
||||||
|
final bool handleTap;
|
||||||
|
|
||||||
|
static EntityModel of(BuildContext context) {
|
||||||
|
return context.dependOnInheritedWidgetOfExactType<EntityModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool updateShouldNotify(EntityModel oldWidget) {
|
||||||
|
return entityWrapper.entity.lastUpdatedTimestamp != oldWidget.entityWrapper.entity.lastUpdatedTimestamp;
|
||||||
|
}
|
||||||
|
}
|
@ -5,18 +5,24 @@ class EntityName extends StatelessWidget {
|
|||||||
final EdgeInsetsGeometry padding;
|
final EdgeInsetsGeometry padding;
|
||||||
final TextOverflow textOverflow;
|
final TextOverflow textOverflow;
|
||||||
final bool wordsWrap;
|
final bool wordsWrap;
|
||||||
final double fontSize;
|
|
||||||
final TextAlign textAlign;
|
final TextAlign textAlign;
|
||||||
final int maxLines;
|
final int maxLines;
|
||||||
|
final TextStyle textStyle;
|
||||||
|
|
||||||
const EntityName({Key key, this.maxLines, this.padding: const EdgeInsets.only(right: 10.0), this.textOverflow: TextOverflow.ellipsis, this.wordsWrap: true, this.fontSize: Sizes.nameFontSize, this.textAlign: TextAlign.left}) : super(key: key);
|
const EntityName({Key key, this.maxLines, this.padding: const EdgeInsets.only(right: 10.0), this.textOverflow: TextOverflow.ellipsis, this.textStyle, this.wordsWrap: true, this.textAlign: TextAlign.left}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||||
TextStyle textStyle = TextStyle(fontSize: fontSize);
|
TextStyle tStyle;
|
||||||
if (entityWrapper.entity.statelessType == StatelessEntityType.WEBLINK) {
|
if (textStyle == null) {
|
||||||
textStyle = textStyle.apply(color: Colors.blue, decoration: TextDecoration.underline);
|
if (entityWrapper.entity.statelessType == StatelessEntityType.webLink) {
|
||||||
|
tStyle = HAClientTheme().getLinkTextStyle(context);
|
||||||
|
} else {
|
||||||
|
tStyle = Theme.of(context).textTheme.body1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tStyle = textStyle;
|
||||||
}
|
}
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
@ -25,7 +31,7 @@ class EntityName extends StatelessWidget {
|
|||||||
overflow: textOverflow,
|
overflow: textOverflow,
|
||||||
softWrap: wordsWrap,
|
softWrap: wordsWrap,
|
||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
style: textStyle,
|
style: tStyle,
|
||||||
textAlign: textAlign,
|
textAlign: textAlign,
|
||||||
),
|
),
|
||||||
);
|
);
|
66
lib/entities/entity_page_layout.widget.dart
Normal file
66
lib/entities/entity_page_layout.widget.dart
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
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: Theme.of(context).primaryColor,
|
||||||
|
height: 40,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(left: 8),
|
||||||
|
child: Text(
|
||||||
|
entity.displayName,
|
||||||
|
style: Theme.of(context).primaryTextTheme.headline
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
padding: EdgeInsets.all(0),
|
||||||
|
icon: Icon(Icons.close),
|
||||||
|
color: Theme.of(context).primaryTextTheme.headline.color,
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
71
lib/entities/entity_picture.widget.dart
Normal file
71
lib/entities/entity_picture.widget.dart
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class EntityPicture extends StatelessWidget {
|
||||||
|
|
||||||
|
final EdgeInsetsGeometry padding;
|
||||||
|
final BoxFit fit;
|
||||||
|
|
||||||
|
const EntityPicture({Key key, this.padding: const EdgeInsets.all(0.0), this.fit: BoxFit.cover}) : super(key: key);
|
||||||
|
|
||||||
|
int getDefaultIconByEntityId(String entityId, String deviceClass, String state) {
|
||||||
|
String domain = entityId.split(".")[0];
|
||||||
|
String iconNameByDomain = MaterialDesignIcons.defaultIconsByDomains["$domain.$state"] ?? MaterialDesignIcons.defaultIconsByDomains["$domain"];
|
||||||
|
String iconNameByDeviceClass;
|
||||||
|
if (deviceClass != null) {
|
||||||
|
iconNameByDeviceClass = MaterialDesignIcons.defaultIconsByDeviceClass["$domain.$deviceClass.$state"] ?? MaterialDesignIcons.defaultIconsByDeviceClass["$domain.$deviceClass"];
|
||||||
|
}
|
||||||
|
String iconName = iconNameByDeviceClass ?? iconNameByDomain;
|
||||||
|
if (iconName != null) {
|
||||||
|
return MaterialDesignIcons.iconsDataMap[iconName] ?? 0;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildIcon(EntityWrapper data, BuildContext context) {
|
||||||
|
if (data == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String iconName = data.icon;
|
||||||
|
int iconCode = 0;
|
||||||
|
if (iconName.length > 0) {
|
||||||
|
iconCode = MaterialDesignIcons.getIconCodeByIconName(iconName);
|
||||||
|
} else {
|
||||||
|
iconCode = getDefaultIconByEntityId(data.entity.entityId,
|
||||||
|
data.entity.deviceClass, data.entity.state); //
|
||||||
|
}
|
||||||
|
Widget iconPicture = Container(
|
||||||
|
child: Center(
|
||||||
|
child: Icon(
|
||||||
|
IconData(iconCode, fontFamily: 'Material Design Icons'),
|
||||||
|
size: Sizes.largeIconSize,
|
||||||
|
color: HAClientTheme().getOffStateColor(context),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (data.entityPicture != null) {
|
||||||
|
return CachedNetworkImage(
|
||||||
|
imageUrl: data.entityPicture,
|
||||||
|
fit: this.fit,
|
||||||
|
errorWidget: (context, _, __) => iconPicture,
|
||||||
|
placeholder: (context, _) => iconPicture,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return iconPicture;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||||
|
return Padding(
|
||||||
|
padding: padding,
|
||||||
|
child: buildIcon(
|
||||||
|
entityWrapper,
|
||||||
|
context
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
157
lib/entities/entity_wrapper.class.dart
Normal file
157
lib/entities/entity_wrapper.class.dart
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class EntityWrapper {
|
||||||
|
|
||||||
|
String overrideName;
|
||||||
|
final String overrideIcon;
|
||||||
|
final bool stateColor;
|
||||||
|
EntityUIAction uiAction;
|
||||||
|
Entity entity;
|
||||||
|
String unitOfMeasurementOverride;
|
||||||
|
final List stateFilter;
|
||||||
|
|
||||||
|
String get icon => overrideIcon ?? entity.icon;
|
||||||
|
String get entityPicture => entity.entityPicture;
|
||||||
|
String get displayName => overrideName ?? entity.displayName;
|
||||||
|
String get unitOfMeasurement => unitOfMeasurementOverride ?? entity.unitOfMeasurement;
|
||||||
|
|
||||||
|
EntityWrapper({
|
||||||
|
this.entity,
|
||||||
|
this.overrideIcon,
|
||||||
|
this.overrideName,
|
||||||
|
this.stateColor: true,
|
||||||
|
this.uiAction,
|
||||||
|
this.stateFilter
|
||||||
|
}) {
|
||||||
|
if (entity.statelessType == StatelessEntityType.ghost || entity.statelessType == StatelessEntityType.none || entity.statelessType == StatelessEntityType.callService || entity.statelessType == StatelessEntityType.webLink) {
|
||||||
|
if (uiAction == null) {
|
||||||
|
uiAction = EntityUIAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleTap() {
|
||||||
|
switch (uiAction.tapAction) {
|
||||||
|
case EntityUIAction.toggle: {
|
||||||
|
ConnectionManager().callService(domain: "homeassistant", service: "toggle", entityId: entity.entityId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.callService: {
|
||||||
|
if (uiAction.tapService != null) {
|
||||||
|
ConnectionManager().callService(
|
||||||
|
domain: uiAction.tapService.split(".")[0],
|
||||||
|
service: uiAction.tapService.split(".")[1],
|
||||||
|
data: uiAction.tapServiceData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.none: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.moreInfo: {
|
||||||
|
eventBus.fire(
|
||||||
|
new ShowEntityPageEvent(entity: entity));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.navigate: {
|
||||||
|
if (uiAction.tapService != null && uiAction.tapService.startsWith("/")) {
|
||||||
|
//TODO handle local urls
|
||||||
|
Logger.w("Local urls is not supported yet");
|
||||||
|
} else {
|
||||||
|
Launcher.launchURL(uiAction.tapService);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleHold() {
|
||||||
|
switch (uiAction.holdAction) {
|
||||||
|
case EntityUIAction.toggle: {
|
||||||
|
ConnectionManager().callService(domain: "homeassistant", service: "toggle", entityId: entity.entityId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.callService: {
|
||||||
|
if (uiAction.holdService != null) {
|
||||||
|
ConnectionManager().callService(
|
||||||
|
domain: uiAction.holdService.split(".")[0],
|
||||||
|
service: uiAction.holdService.split(".")[1],
|
||||||
|
data: uiAction.holdServiceData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.moreInfo: {
|
||||||
|
eventBus.fire(
|
||||||
|
new ShowEntityPageEvent(entity: entity));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.navigate: {
|
||||||
|
if (uiAction.holdService != null && uiAction.holdService.startsWith("/")) {
|
||||||
|
//TODO handle local urls
|
||||||
|
Logger.w("Local urls is not supported yet");
|
||||||
|
} else {
|
||||||
|
Launcher.launchURL(uiAction.holdService);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleDoubleTap() {
|
||||||
|
switch (uiAction.doubleTapAction) {
|
||||||
|
case EntityUIAction.toggle: {
|
||||||
|
ConnectionManager().callService(domain: "homeassistant", service: "toggle", entityId: entity.entityId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.callService: {
|
||||||
|
if (uiAction.doubleTapService != null) {
|
||||||
|
ConnectionManager().callService(
|
||||||
|
domain: uiAction.doubleTapService.split(".")[0],
|
||||||
|
service: uiAction.doubleTapService.split(".")[1],
|
||||||
|
data: uiAction.doubleTapServiceData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.moreInfo: {
|
||||||
|
eventBus.fire(
|
||||||
|
new ShowEntityPageEvent(entity: entity));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EntityUIAction.navigate: {
|
||||||
|
if (uiAction.doubleTapService != null && uiAction.doubleTapService.startsWith("/")) {
|
||||||
|
//TODO handle local urls
|
||||||
|
Logger.w("Local urls is not supported yet");
|
||||||
|
} else {
|
||||||
|
Launcher.launchURL(uiAction.doubleTapService);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class FanEntity extends Entity {
|
class FanEntity extends Entity {
|
||||||
|
|
||||||
@ -6,7 +6,7 @@ class FanEntity extends Entity {
|
|||||||
static const SUPPORT_OSCILLATE = 2;
|
static const SUPPORT_OSCILLATE = 2;
|
||||||
static const SUPPORT_DIRECTION = 4;
|
static const SUPPORT_DIRECTION = 4;
|
||||||
|
|
||||||
FanEntity(Map rawData) : super(rawData);
|
FanEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
bool get supportSetSpeed => ((supportedFeatures &
|
bool get supportSetSpeed => ((supportedFeatures &
|
||||||
FanEntity.SUPPORT_SET_SPEED) ==
|
FanEntity.SUPPORT_SET_SPEED) ==
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class FanControlsWidget extends StatefulWidget {
|
class FanControlsWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -24,9 +24,12 @@ 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,
|
domain: "fan",
|
||||||
{"oscillating": oscillate}));
|
service: "oscillate",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"oscillating": oscillate}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,9 +37,12 @@ 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,
|
domain: "fan",
|
||||||
{"direction": forward ? "forward" : "reverse"}));
|
service: "set_direction",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"direction": forward ? "forward" : "reverse"}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,9 +50,12 @@ 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,
|
domain: "fan",
|
||||||
{"speed": value}));
|
service: "set_speed",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"speed": value}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../main.dart';
|
||||||
|
|
||||||
class FlatServiceButton extends StatelessWidget {
|
class FlatServiceButton extends StatelessWidget {
|
||||||
|
|
||||||
@ -6,7 +6,6 @@ class FlatServiceButton extends StatelessWidget {
|
|||||||
final String serviceName;
|
final String serviceName;
|
||||||
final String entityId;
|
final String entityId;
|
||||||
final String text;
|
final String text;
|
||||||
final double fontSize;
|
|
||||||
|
|
||||||
FlatServiceButton({
|
FlatServiceButton({
|
||||||
Key key,
|
Key key,
|
||||||
@ -14,17 +13,16 @@ class FlatServiceButton extends StatelessWidget {
|
|||||||
@required this.serviceName,
|
@required this.serviceName,
|
||||||
@required this.entityId,
|
@required this.entityId,
|
||||||
@required this.text,
|
@required this.text,
|
||||||
this.fontSize: Sizes.stateFontSize
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
void _setNewState() {
|
void _setNewState() {
|
||||||
eventBus.fire(new ServiceCallEvent(serviceDomain, serviceName, entityId, null));
|
ConnectionManager().callService(domain: serviceDomain, service: serviceName, entityId: entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: fontSize*2.5,
|
height: Theme.of(context).textTheme.subhead.fontSize*2.5,
|
||||||
child: FlatButton(
|
child: FlatButton(
|
||||||
onPressed: (() {
|
onPressed: (() {
|
||||||
_setNewState();
|
_setNewState();
|
||||||
@ -32,8 +30,7 @@ class FlatServiceButton extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style:
|
style: HAClientTheme().getActionTextStyle(context),
|
||||||
new TextStyle(fontSize: fontSize, color: Colors.blue),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
@ -1,12 +1,13 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class GroupEntity extends Entity {
|
class GroupEntity extends Entity {
|
||||||
GroupEntity(Map rawData) : super(rawData);
|
|
||||||
|
|
||||||
final List<String> _domainsForSwitchableGroup = ["switch", "light", "automation", "input_boolean"];
|
final List<String> _domainsForSwitchableGroup = ["switch", "light", "automation", "input_boolean"];
|
||||||
String mutualDomain;
|
String mutualDomain;
|
||||||
bool switchable = false;
|
bool switchable = false;
|
||||||
|
|
||||||
|
GroupEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget _buildStatePart(BuildContext context) {
|
Widget _buildStatePart(BuildContext context) {
|
||||||
if (switchable) {
|
if (switchable) {
|
||||||
@ -19,8 +20,8 @@ class GroupEntity extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void update(Map rawData) {
|
void update(Map rawData, String webHost) {
|
||||||
super.update(rawData);
|
super.update(rawData, webHost);
|
||||||
if (_isOneDomain()) {
|
if (_isOneDomain()) {
|
||||||
mutualDomain = attributes['entity_id'][0].split(".")[0];
|
mutualDomain = attributes['entity_id'][0].split(".")[0];
|
||||||
switchable = _domainsForSwitchableGroup.contains(mutualDomain);
|
switchable = _domainsForSwitchableGroup.contains(mutualDomain);
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class LightEntity extends Entity {
|
class LightEntity extends Entity {
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ class LightEntity extends Entity {
|
|||||||
bool get isAdditionalControls => ((supportedFeatures != null) && (supportedFeatures != 0));
|
bool get isAdditionalControls => ((supportedFeatures != null) && (supportedFeatures != 0));
|
||||||
List<String> get effectList => getStringListAttributeValue("effect_list");
|
List<String> get effectList => getStringListAttributeValue("effect_list");
|
||||||
|
|
||||||
LightEntity(Map rawData) : super(rawData);
|
LightEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
HSVColor _getColor() {
|
HSVColor _getColor() {
|
||||||
List hs = attributes["hs_color"];
|
List hs = attributes["hs_color"];
|
@ -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,12 @@ 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(
|
domain: entity.domain,
|
||||||
entity.domain, "turn_on", entity.entityId,
|
service: "turn_on",
|
||||||
{"brightness": _tmpBrightness}));
|
entityId: entity.entityId,
|
||||||
} else {
|
data: {"brightness": _tmpBrightness}
|
||||||
eventBus.fire(new ServiceCallEvent(
|
);
|
||||||
entity.domain, "turn_off", entity.entityId,
|
|
||||||
null));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,9 +41,12 @@ 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,
|
domain: entity.domain,
|
||||||
{"white_value": _tmpWhiteValue}));
|
service: "turn_on",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"white_value": _tmpWhiteValue}
|
||||||
|
);
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -55,9 +55,12 @@ 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,
|
domain: entity.domain,
|
||||||
{"color_temp": _tmpColorTemp}));
|
service: "turn_on",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"color_temp": _tmpColorTemp}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,10 +68,12 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpColor = color;
|
_tmpColor = color;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
Logger.d( "HS Color: [${color.hue}, ${color.saturation}]");
|
ConnectionManager().callService(
|
||||||
eventBus.fire(new ServiceCallEvent(
|
domain: entity.domain,
|
||||||
entity.domain, "turn_on", entity.entityId,
|
service: "turn_on",
|
||||||
{"hs_color": [color.hue, color.saturation*100]}));
|
entityId: entity.entityId,
|
||||||
|
data: {"hs_color": [color.hue, color.saturation*100]}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,9 +82,12 @@ 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,
|
domain: entity.domain,
|
||||||
{"effect": "$value"}));
|
service: "turn_on",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"effect": "$value"}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -106,7 +114,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 +134,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 +169,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: Theme.of(context).textTheme.body1.copyWith(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,
|
||||||
@ -162,7 +194,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
_tmpColorTemp = value.round();
|
_tmpColorTemp = value.round();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
closing: Text("Warm", style: TextStyle(color: Colors.amberAccent),),
|
closing: Text("Warm", style: Theme.of(context).textTheme.body1.copyWith(color: Colors.amberAccent),),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Container(width: 0.0, height: 0.0);
|
return Container(width: 0.0, height: 0.0);
|
||||||
@ -171,7 +203,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,15 +219,12 @@ 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;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
FlatButton(
|
FlatButton(
|
||||||
color: savedColor?.toColor() ?? Colors.transparent,
|
color: savedColor?.toColor() ?? Theme.of(context).backgroundColor,
|
||||||
child: Text('Paste color'),
|
child: Text('Paste color'),
|
||||||
onPressed: savedColor == null ? null : () {
|
onPressed: savedColor == null ? null : () {
|
||||||
_setColor(entity, savedColor);
|
_setColor(entity, savedColor);
|
||||||
@ -212,10 +241,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,7 +1,7 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class LockEntity extends Entity {
|
class LockEntity extends Entity {
|
||||||
LockEntity(Map rawData) : super(rawData);
|
LockEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
bool get isLocked => state == "locked";
|
bool get isLocked => state == "locked";
|
||||||
|
|
@ -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(domain: "lock", service: "lock", entityId: entity.entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _unlock(Entity entity) {
|
void _unlock(Entity entity) {
|
||||||
eventBus.fire(new ServiceCallEvent("lock", "unlock", entity.entityId, null));
|
ConnectionManager().callService(domain: "lock", service: "unlock", entityId: entity.entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -28,8 +28,7 @@ class LockStateWidget extends StatelessWidget {
|
|||||||
onPressed: () => _unlock(entity),
|
onPressed: () => _unlock(entity),
|
||||||
child: Text("UNLOCK",
|
child: Text("UNLOCK",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style:
|
style: HAClientTheme().getActionTextStyle(context)
|
||||||
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -39,8 +38,7 @@ class LockStateWidget extends StatelessWidget {
|
|||||||
onPressed: () => _lock(entity),
|
onPressed: () => _lock(entity),
|
||||||
child: Text("LOCK",
|
child: Text("LOCK",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style:
|
style: HAClientTheme().getActionTextStyle(context),
|
||||||
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -56,8 +54,7 @@ class LockStateWidget extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
entity.isLocked ? "UNLOCK" : "LOCK",
|
entity.isLocked ? "UNLOCK" : "LOCK",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style:
|
style: HAClientTheme().getActionTextStyle(context),
|
||||||
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
@ -1,4 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class MediaPlayerEntity extends Entity {
|
class MediaPlayerEntity extends Entity {
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ class MediaPlayerEntity extends Entity {
|
|||||||
static const SUPPORT_SHUFFLE_SET = 32768;
|
static const SUPPORT_SHUFFLE_SET = 32768;
|
||||||
static const SUPPORT_SELECT_SOUND_MODE = 65536;
|
static const SUPPORT_SELECT_SOUND_MODE = 65536;
|
||||||
|
|
||||||
MediaPlayerEntity(Map rawData) : super(rawData);
|
MediaPlayerEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
bool get supportPause => ((supportedFeatures &
|
bool get supportPause => ((supportedFeatures &
|
||||||
MediaPlayerEntity.SUPPORT_PAUSE) ==
|
MediaPlayerEntity.SUPPORT_PAUSE) ==
|
||||||
@ -74,10 +74,34 @@ 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;
|
||||||
|
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 = 0;
|
||||||
|
int currentPosition;
|
||||||
|
if (entity.canCalculateActualPosition()) {
|
||||||
|
currentPosition = entity.getActualPosition().toInt();
|
||||||
|
if (currentPosition > 0) {
|
||||||
|
progress = (currentPosition <= entity.durationSeconds) ? currentPosition / entity.durationSeconds : 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return LinearProgressIndicator(
|
||||||
|
value: progress,
|
||||||
|
backgroundColor: Colors.black45,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(HAClientTheme().getOnStateColor(context)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,135 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
@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: Theme.of(context).accentColor,
|
||||||
|
onPressed: () {
|
||||||
|
ConnectionManager().callService(
|
||||||
|
domain: "media_player",
|
||||||
|
service: "media_seek",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"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: Theme.of(context).textTheme.title.copyWith(
|
||||||
|
color: Colors.blue
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text("${Duration(seconds: entity.durationSeconds).toString().split(".")[0]}")
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Container(height: 10,),
|
||||||
|
Slider(
|
||||||
|
min: 0,
|
||||||
|
activeColor: Theme.of(context).accentColor,
|
||||||
|
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(
|
||||||
|
domain: "media_player",
|
||||||
|
service: "media_seek",
|
||||||
|
entityId: entity.entityId,
|
||||||
|
data: {"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 {
|
||||||
|
|
||||||
@ -12,21 +12,21 @@ class MediaPlayerWidget extends StatelessWidget {
|
|||||||
Stack(
|
Stack(
|
||||||
alignment: AlignmentDirectional.topEnd,
|
alignment: AlignmentDirectional.topEnd,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildImage(entity),
|
_buildImage(entity, context),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0.0,
|
bottom: 0.0,
|
||||||
left: 0.0,
|
left: 0.0,
|
||||||
right: 0.0,
|
right: 0.0,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.black45,
|
color: Colors.black45,
|
||||||
child: _buildState(entity),
|
child: _buildState(entity, context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0.0,
|
bottom: 0.0,
|
||||||
left: 0.0,
|
left: 0.0,
|
||||||
right: 0.0,
|
right: 0.0,
|
||||||
child: MediaPlayerProgressWidget()
|
child: MediaPlayerProgressBar()
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -35,12 +35,9 @@ class MediaPlayerWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildState(MediaPlayerEntity entity) {
|
Widget _buildState(MediaPlayerEntity entity, BuildContext context) {
|
||||||
TextStyle style = TextStyle(
|
TextStyle style = Theme.of(context).textTheme.body1.copyWith(
|
||||||
fontSize: 14.0,
|
color: Colors.white
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
height: 1.2
|
|
||||||
);
|
);
|
||||||
List<Widget> states = [];
|
List<Widget> states = [];
|
||||||
states.add(Text("${entity.displayName}", style: style));
|
states.add(Text("${entity.displayName}", style: style));
|
||||||
@ -71,9 +68,9 @@ class MediaPlayerWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildImage(MediaPlayerEntity entity) {
|
Widget _buildImage(MediaPlayerEntity entity, BuildContext context) {
|
||||||
String state = entity.state;
|
String state = entity.state;
|
||||||
if (homeAssistantWebHost != null && entity.entityPicture != null && state != EntityState.off && state != EntityState.unavailable && state != EntityState.idle) {
|
if (entity.entityPicture != null && state != EntityState.off && state != EntityState.unavailable && state != EntityState.idle) {
|
||||||
return Container(
|
return Container(
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -97,7 +94,7 @@ class MediaPlayerWidget extends StatelessWidget {
|
|||||||
Icon(
|
Icon(
|
||||||
MaterialDesignIcons.getIconDataFromIconName("mdi:movie"),
|
MaterialDesignIcons.getIconDataFromIconName("mdi:movie"),
|
||||||
size: 150.0,
|
size: 150.0,
|
||||||
color: EntityColor.stateColor("$state"),
|
color: HAClientTheme().getColorByEntityState("$state", context),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -118,26 +115,28 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
|
|||||||
|
|
||||||
|
|
||||||
void _setPower(MediaPlayerEntity entity) {
|
void _setPower(MediaPlayerEntity entity) {
|
||||||
if (entity.state != EntityState.unavailable && entity.state != EntityState.unknown) {
|
|
||||||
if (entity.state == EntityState.off) {
|
if (entity.state == EntityState.off) {
|
||||||
Logger.d("${entity.entityId} turn_on");
|
ConnectionManager().callService(
|
||||||
eventBus.fire(new ServiceCallEvent(
|
domain: entity.domain,
|
||||||
entity.domain, "turn_on", entity.entityId,
|
service: "turn_on",
|
||||||
null));
|
entityId: entity.entityId
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
Logger.d("${entity.entityId} turn_off");
|
ConnectionManager().callService(
|
||||||
eventBus.fire(new ServiceCallEvent(
|
domain: entity.domain,
|
||||||
entity.domain, "turn_off", entity.entityId,
|
service: "turn_off",
|
||||||
null));
|
entityId: entity.entityId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
domain: entity.domain,
|
||||||
null));
|
service: "$action",
|
||||||
|
entityId: entity.entityId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -229,7 +228,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 +263,50 @@ 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(
|
||||||
|
domain: "media_player",
|
||||||
|
service: "volume_set",
|
||||||
|
entityId: entityId,
|
||||||
|
data: {"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(
|
||||||
|
domain: "media_player",
|
||||||
|
service: "volume_mute",
|
||||||
|
entityId: entityId,
|
||||||
|
data: {"is_volume_muted": isMuted}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setVolumeUp(String entityId) {
|
void _setVolumeUp(String entityId) {
|
||||||
eventBus.fire(ServiceCallEvent("media_player", "volume_up", entityId, null));
|
ConnectionManager().callService(
|
||||||
|
domain: "media_player",
|
||||||
|
service: "volume_up",
|
||||||
|
entityId: entityId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setVolumeDown(String entityId) {
|
void _setVolumeDown(String entityId) {
|
||||||
eventBus.fire(ServiceCallEvent("media_player", "volume_down", entityId, null));
|
ConnectionManager().callService(
|
||||||
|
domain: "media_player",
|
||||||
|
service: "volume_down",
|
||||||
|
entityId: entityId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
||||||
|
domain: "media_player",
|
||||||
|
service: "select_sound_mode",
|
||||||
|
entityId: entityId,
|
||||||
|
data: {"sound_mode": "$value"}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +314,12 @@ 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(
|
||||||
|
domain: "media_player",
|
||||||
|
service: "select_source",
|
||||||
|
entityId: entityId,
|
||||||
|
data: {"source": "$source"}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,6 +332,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) {
|
||||||
@ -321,13 +353,13 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
|||||||
volumeStepWidget = Row(
|
volumeStepWidget = Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
IconButton(
|
|
||||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:plus")),
|
|
||||||
onPressed: () => _setVolumeUp(entity.entityId)
|
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:minus")),
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:minus")),
|
||||||
onPressed: () => _setVolumeDown(entity.entityId)
|
onPressed: () => _setVolumeDown(entity.entityId)
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:plus")),
|
||||||
|
onPressed: () => _setVolumeUp(entity.entityId)
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -398,69 +430,47 @@ 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) {
|
||||||
|
if (entity.canCalculateActualPosition()) {
|
||||||
class MediaPlayerProgressWidget extends StatefulWidget {
|
HomeAssistant().savedPlayerPosition = entity.getActualPosition().toInt();
|
||||||
@override
|
} else {
|
||||||
_MediaPlayerProgressWidgetState createState() => _MediaPlayerProgressWidgetState();
|
HomeAssistant().savedPlayerPosition = 0;
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
Navigator.of(context).pushNamed("/play-media", arguments: {
|
||||||
value: progress,
|
"url": entity.attributes["media_content_id"],
|
||||||
backgroundColor: Colors.black45,
|
"type": entity.attributes["media_content_type"]
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void _switchTo(entity) {
|
||||||
void dispose() {
|
HomeAssistant().sendFromPlayerId = entity.entityId;
|
||||||
_timer?.cancel();
|
_duplicateTo(entity);
|
||||||
super.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,11 +1,11 @@
|
|||||||
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
|
||||||
? (attributes["options"] as List).cast<String>()
|
? (attributes["options"] as List).cast<String>()
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
SelectEntity(Map rawData) : super(rawData);
|
SelectEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget _buildStatePart(BuildContext context) {
|
Widget _buildStatePart(BuildContext context) {
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class SelectStateWidget extends StatefulWidget {
|
class SelectStateWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -11,8 +11,12 @@ 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(
|
||||||
{"option": "$newValue"}));
|
domain: domain,
|
||||||
|
service: "select_option",
|
||||||
|
entityId: entityId,
|
||||||
|
data: {"option": "$newValue"}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
@ -1,8 +1,4 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class SunEntity extends Entity {
|
|
||||||
SunEntity(Map rawData) : super(rawData);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SensorEntity extends Entity {
|
class SensorEntity extends Entity {
|
||||||
|
|
||||||
@ -12,6 +8,6 @@ class SensorEntity extends Entity {
|
|||||||
numericState: true
|
numericState: true
|
||||||
);
|
);
|
||||||
|
|
||||||
SensorEntity(Map rawData) : super(rawData);
|
SensorEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
}
|
}
|
@ -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 TextStyle textStyle;
|
||||||
|
//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.expanded: true, this.textAlign: TextAlign.right, this.textStyle, 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) {
|
||||||
@ -20,24 +22,31 @@ class SimpleEntityState extends StatelessWidget {
|
|||||||
} else {
|
} else {
|
||||||
state = customValue;
|
state = customValue;
|
||||||
}
|
}
|
||||||
TextStyle textStyle = TextStyle(
|
TextStyle tStyle;
|
||||||
fontSize: Sizes.stateFontSize,
|
if (textStyle != null) {
|
||||||
);
|
tStyle = textStyle;
|
||||||
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.CALL_SERVICE) {
|
} else if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.callService) {
|
||||||
textStyle = textStyle.apply(color: Colors.blue);
|
tStyle = Theme.of(context).textTheme.subhead.copyWith(
|
||||||
|
color: Colors.blue
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
tStyle = Theme.of(context).textTheme.body1;
|
||||||
}
|
}
|
||||||
|
/*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,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
style: textStyle
|
style: tStyle
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if (expanded) {
|
if (expanded) {
|
@ -1,7 +1,7 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class SliderEntity extends Entity {
|
class SliderEntity extends Entity {
|
||||||
SliderEntity(Map rawData) : super(rawData);
|
SliderEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
double get minValue => _getDoubleAttributeValue("min") ?? 0.0;
|
double get minValue => _getDoubleAttributeValue("min") ?? 0.0;
|
||||||
double get maxValue =>_getDoubleAttributeValue("max") ?? 100.0;
|
double get maxValue =>_getDoubleAttributeValue("max") ?? 100.0;
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class SliderControlsWidget extends StatefulWidget {
|
class SliderControlsWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -18,8 +18,12 @@ class _SliderControlsWidgetState extends State<SliderControlsWidget> {
|
|||||||
_newValue = newValue;
|
_newValue = newValue;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
});
|
});
|
||||||
eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId,
|
ConnectionManager().callService(
|
||||||
{"value": "${newValue.toString()}"}));
|
domain: domain,
|
||||||
|
service: "set_value",
|
||||||
|
entityId: entityId,
|
||||||
|
data: {"value": "${newValue.toString()}"}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -58,8 +62,7 @@ class _SliderControlsWidgetState extends State<SliderControlsWidget> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
"$_newValue",
|
"$_newValue",
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.display1.copyWith(
|
||||||
fontSize: Sizes.largeFontSize,
|
|
||||||
color: Colors.blue
|
color: Colors.blue
|
||||||
),
|
),
|
||||||
),
|
),
|
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,7 +1,7 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class SwitchEntity extends Entity {
|
class SwitchEntity extends Entity {
|
||||||
SwitchEntity(Map rawData) : super(rawData);
|
SwitchEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget _buildStatePart(BuildContext context) {
|
Widget _buildStatePart(BuildContext context) {
|
@ -1,4 +1,4 @@
|
|||||||
part of '../../main.dart';
|
part of '../../../main.dart';
|
||||||
|
|
||||||
class SwitchStateWidget extends StatefulWidget {
|
class SwitchStateWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -38,8 +38,11 @@ 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: domain,
|
||||||
|
service: (newValue as bool) ? "turn_on" : "turn_off",
|
||||||
|
entityId: entity.entityId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
@ -1,7 +1,7 @@
|
|||||||
part of '../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class TextEntity extends Entity {
|
class TextEntity extends Entity {
|
||||||
TextEntity(Map rawData) : super(rawData);
|
TextEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
int get valueMinLength => attributes["min"] ?? -1;
|
int get valueMinLength => attributes["min"] ?? -1;
|
||||||
int get valueMaxLength => attributes["max"] ?? -1;
|
int get valueMaxLength => attributes["max"] ?? -1;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user