Compare commits
3 Commits
beta/0.8.3
...
0.5.4
Author | SHA1 | Date | |
---|---|---|---|
5211d1ff46 | |||
d1c9fddba6 | |||
14cc55a2c7 |
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,22 +0,0 @@
|
||||
---
|
||||
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
12
.github/ISSUE_TEMPLATE/entity-support-request.md
vendored
@ -1,12 +0,0 @@
|
||||
---
|
||||
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
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,17 +0,0 @@
|
||||
---
|
||||
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.
|
@ -1,12 +0,0 @@
|
||||
---
|
||||
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
11
.github/no-response.yml
vendored
@ -1,11 +0,0 @@
|
||||
# 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,14 +9,5 @@ build/
|
||||
.flutter-plugins
|
||||
|
||||
.idea/
|
||||
.vscode/
|
||||
.theia/
|
||||
.project/
|
||||
.settings/
|
||||
|
||||
flutter_export_environment.sh
|
||||
.flutter-plugins-dependencies
|
||||
|
||||
key.properties
|
||||
.secrets.dart
|
||||
pubspec.lock
|
||||
|
@ -1,8 +0,0 @@
|
||||
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
26
.gitpod.yml
@ -1,26 +0,0 @@
|
||||
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==
|
Binary file not shown.
Binary file not shown.
16
README.md
16
README.md
@ -1,16 +1,12 @@
|
||||
[](https://somegeeky.website/badges/flutter) [](https://somegeeky.website/badges/dart)
|
||||
# HA Client
|
||||
## Native Android client for Home Assistant
|
||||
### With notifications and Lovelace UI support
|
||||
### With Lovelace UI support
|
||||
|
||||
Visit [ha-client.app](http://ha-client.app/) for more info.
|
||||
Visit [homemade.systems](http://ha-client.homemade.systems/) for more info.
|
||||
|
||||
Download the app from [Google Play](https://play.google.com/apps/testing/com.keyboardcrumbs.haclient)
|
||||
Join [Google Group](https://groups.google.com/d/forum/ha-client-alpha-testing) to become an alpha tester
|
||||
|
||||
Discuss it on [Discord](https://discord.gg/nd6FZQ) or at [Home Assistant community](https://community.home-assistant.io/c/mobile-apps/ha-client-android)
|
||||
Download the app from [Google Play](https://play.google.com/apps/testing/com.keyboardcrumbs.haclient) after joining the group
|
||||
|
||||
[](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)
|
||||
Discuss it in [Home Assistant community](https://community.home-assistant.io/t/alpha-testing-ha-client-native-android-client-for-home-assistant/69912)
|
||||
|
1
android/.gitignore
vendored
1
android/.gitignore
vendored
@ -8,4 +8,3 @@
|
||||
/build
|
||||
/captures
|
||||
GeneratedPluginRegistrant.java
|
||||
.project/
|
@ -1,17 +0,0 @@
|
||||
<?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>
|
@ -1,6 +0,0 @@
|
||||
<?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>
|
@ -1,23 +0,0 @@
|
||||
<?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,14 +50,6 @@ android {
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
if (!System.getenv()["CI"]) {
|
||||
debug {
|
||||
keyAlias keystoreProperties['debugKeyAlias']
|
||||
keyPassword keystoreProperties['debugKeyPassword']
|
||||
storeFile file(keystoreProperties['debugStoreFile'])
|
||||
storePassword keystoreProperties['debugStorePassword']
|
||||
}
|
||||
}
|
||||
release {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
@ -78,11 +70,7 @@ flutter {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.firebase:firebase-analytics:17.2.2'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||
}
|
||||
|
||||
apply plugin: 'io.fabric'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
@ -1,64 +0,0 @@
|
||||
{
|
||||
"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,15 +1,11 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.keyboardcrumbs.hassclient">
|
||||
|
||||
<uses-feature android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<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
|
||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||
@ -17,18 +13,9 @@
|
||||
additional functionality it is fine to subclass or reimplement
|
||||
FlutterApplication and put your custom class here. -->
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterApplication"
|
||||
android:label="HA Client"
|
||||
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" />
|
||||
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
@ -36,35 +23,17 @@
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
|
||||
android:hardwareAccelerated="true"
|
||||
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
|
||||
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||
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>
|
||||
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
|
||||
android:value="true" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</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>
|
||||
</manifest>
|
||||
|
@ -1,15 +1,13 @@
|
||||
package com.keyboardcrumbs.hassclient;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
import io.flutter.embedding.engine.FlutterEngine;
|
||||
import android.os.Bundle;
|
||||
import io.flutter.app.FlutterActivity;
|
||||
import io.flutter.plugins.GeneratedPluginRegistrant;
|
||||
|
||||
public class MainActivity extends FlutterActivity {
|
||||
|
||||
@Override
|
||||
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
|
||||
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
GeneratedPluginRegistrant.registerWith(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 612 B |
@ -5,7 +5,4 @@
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
@ -2,15 +2,10 @@ buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://maven.fabric.io/public'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
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'
|
||||
classpath 'com.android.tools.build:gradle:3.1.2'
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,9 +13,6 @@ allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://maven.fabric.io/public'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,5 +2,4 @@ org.gradle.jvmargs=-Xmx2g
|
||||
org.gradle.daemon=true
|
||||
org.gradle.caching=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.enableR8=true
|
||||
android.enableJetifier=false
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
||||
include ':app'
|
@ -1,61 +0,0 @@
|
||||
<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>
|
@ -1,28 +0,0 @@
|
||||
<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>
|
@ -1,35 +0,0 @@
|
||||
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');
|
||||
}
|
||||
};
|
@ -1,41 +0,0 @@
|
||||
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!
|
||||
```
|
@ -1,41 +0,0 @@
|
||||
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!
|
||||
```
|
@ -1,41 +0,0 @@
|
||||
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!
|
||||
```
|
BIN
fonts/materialdesignicons-webfont-3-5-95.ttf
Normal file
BIN
fonts/materialdesignicons-webfont-3-5-95.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
fonts/materialdesignicons-webfont.ttf
Normal file
BIN
fonts/materialdesignicons-webfont.ttf
Normal file
Binary file not shown.
@ -1,123 +0,0 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class HACard {
|
||||
List<EntityWrapper> entities = [];
|
||||
List<HACard> childCards = [];
|
||||
EntityWrapper linkedEntityWrapper;
|
||||
String name;
|
||||
String id;
|
||||
String type;
|
||||
bool showName;
|
||||
bool showState;
|
||||
bool showEmpty;
|
||||
bool showHeaderToggle;
|
||||
int columnsCount;
|
||||
List stateFilter;
|
||||
List states;
|
||||
List conditions;
|
||||
String content;
|
||||
String unit;
|
||||
int min;
|
||||
int max;
|
||||
Map severity;
|
||||
|
||||
HACard({
|
||||
this.name,
|
||||
this.id,
|
||||
this.linkedEntityWrapper,
|
||||
this.columnsCount: 4,
|
||||
this.showName: true,
|
||||
this.showHeaderToggle: true,
|
||||
this.showState: true,
|
||||
this.stateFilter: const [],
|
||||
this.showEmpty: true,
|
||||
this.content,
|
||||
this.states,
|
||||
this.conditions: const [],
|
||||
this.unit,
|
||||
this.min,
|
||||
this.max,
|
||||
this.severity,
|
||||
@required this.type
|
||||
}) {
|
||||
if (this.columnsCount <= 0) {
|
||||
this.columnsCount = 4;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return CardWidget(
|
||||
card: this,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class EntityButtonCardBody extends StatelessWidget {
|
||||
|
||||
final bool showName;
|
||||
|
||||
EntityButtonCardBody({
|
||||
Key key, this.showName: true,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||
if (entityWrapper.entity.statelessType == StatelessEntityType.MISSED) {
|
||||
return MissedEntityWidget();
|
||||
}
|
||||
if (entityWrapper.entity.statelessType > StatelessEntityType.MISSED) {
|
||||
return Container(width: 0.0, height: 0.0,);
|
||||
}
|
||||
|
||||
return InkWell(
|
||||
onTap: () => entityWrapper.handleTap(),
|
||||
onLongPress: () => entityWrapper.handleHold(),
|
||||
onDoubleTap: () => entityWrapper.handleDoubleTap(),
|
||||
child: FractionallySizedBox(
|
||||
widthFactor: 1,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return EntityIcon(
|
||||
padding: EdgeInsets.fromLTRB(2.0, 6.0, 2.0, 2.0),
|
||||
size: constraints.maxWidth / 2.5,
|
||||
);
|
||||
}
|
||||
),
|
||||
_buildName()
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildName() {
|
||||
if (showName) {
|
||||
return EntityName(
|
||||
padding: EdgeInsets.fromLTRB(Sizes.buttonPadding, 0.0, Sizes.buttonPadding, Sizes.rowPadding),
|
||||
textOverflow: TextOverflow.ellipsis,
|
||||
maxLines: 3,
|
||||
wordsWrap: true,
|
||||
textAlign: TextAlign.center
|
||||
);
|
||||
}
|
||||
return Container(width: 0, height: 0);
|
||||
}
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class GaugeCardBody extends StatefulWidget {
|
||||
|
||||
final int min;
|
||||
final int max;
|
||||
final Map severity;
|
||||
|
||||
GaugeCardBody({Key key, this.min, this.max, this.severity}) : super(key: key);
|
||||
|
||||
@override
|
||||
_GaugeCardBodyState createState() => _GaugeCardBodyState();
|
||||
}
|
||||
|
||||
class _GaugeCardBodyState extends State<GaugeCardBody> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||
double fixedValue;
|
||||
double value = entityWrapper.entity.doubleState;
|
||||
if (value > widget.max) {
|
||||
fixedValue = widget.max.toDouble();
|
||||
} else if (value < widget.min) {
|
||||
fixedValue = widget.min.toDouble();
|
||||
} else {
|
||||
fixedValue = value;
|
||||
}
|
||||
|
||||
List<GaugeRange> ranges;
|
||||
if (widget.severity != null && widget.severity["green"] is int && widget.severity["red"] is int && widget.severity["yellow"] is int) {
|
||||
List<RangeContainer> rangesList = <RangeContainer>[
|
||||
RangeContainer(widget.severity["green"], HAClientTheme().getGreenGaugeColor()),
|
||||
RangeContainer(widget.severity["red"], HAClientTheme().getRedGaugeColor()),
|
||||
RangeContainer(widget.severity["yellow"], HAClientTheme().getYellowGaugeColor())
|
||||
];
|
||||
rangesList.sort((current, next) {
|
||||
if (current.startFrom > next.startFrom) {
|
||||
return 1;
|
||||
}
|
||||
if (current.startFrom < next.startFrom) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
ranges = [
|
||||
GaugeRange(
|
||||
startValue: rangesList[0].startFrom.toDouble(),
|
||||
endValue: rangesList[1].startFrom.toDouble(),
|
||||
color: fixedValue < rangesList[1].startFrom ? rangesList[0].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: (fixedValue < rangesList[2].startFrom && fixedValue >= rangesList[1].startFrom) ? rangesList[1].color : rangesList[1].color.withOpacity(0.1),
|
||||
sizeUnit: GaugeSizeUnit.factor,
|
||||
endWidth: 0.3,
|
||||
startWidth: 0.3
|
||||
),
|
||||
GaugeRange(
|
||||
startValue: rangesList[2].startFrom.toDouble(),
|
||||
endValue: widget.max.toDouble(),
|
||||
color: fixedValue >= rangesList[2].startFrom ? rangesList[2].color : rangesList[2].color.withOpacity(0.1),
|
||||
sizeUnit: GaugeSizeUnit.factor,
|
||||
endWidth: 0.3,
|
||||
startWidth: 0.3
|
||||
)
|
||||
];
|
||||
}
|
||||
if (ranges == null) {
|
||||
ranges = <GaugeRange>[
|
||||
GaugeRange(
|
||||
startValue: widget.min.toDouble(),
|
||||
endValue: widget.max.toDouble(),
|
||||
color: Theme.of(context).primaryColorDark,
|
||||
sizeUnit: GaugeSizeUnit.factor,
|
||||
endWidth: 0.3,
|
||||
startWidth: 0.3
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
return InkWell(
|
||||
onTap: () => entityWrapper.handleTap(),
|
||||
onLongPress: () => entityWrapper.handleHold(),
|
||||
onDoubleTap: () => entityWrapper.handleDoubleTap(),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 2,
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
double fontSizeFactor;
|
||||
if (constraints.maxWidth > 300.0) {
|
||||
fontSizeFactor = 1.6;
|
||||
} else if (constraints.maxWidth > 150.0) {
|
||||
fontSizeFactor = 1;
|
||||
} else if (constraints.maxWidth > 100.0) {
|
||||
fontSizeFactor = 0.6;
|
||||
} else {
|
||||
fontSizeFactor = 0.4;
|
||||
}
|
||||
return SfRadialGauge(
|
||||
axes: <RadialAxis>[
|
||||
RadialAxis(
|
||||
maximum: widget.max.toDouble(),
|
||||
minimum: widget.min.toDouble(),
|
||||
showLabels: false,
|
||||
showTicks: false,
|
||||
canScaleToFit: true,
|
||||
ranges: ranges,
|
||||
annotations: <GaugeAnnotation>[
|
||||
GaugeAnnotation(
|
||||
angle: -90,
|
||||
positionFactor: 1.3,
|
||||
//verticalAlignment: GaugeAlignment.far,
|
||||
widget: EntityName(
|
||||
textStyle: Theme.of(context).textTheme.body1.copyWith(
|
||||
fontSize: Theme.of(context).textTheme.body1.fontSize * fontSizeFactor
|
||||
),
|
||||
),
|
||||
),
|
||||
GaugeAnnotation(
|
||||
angle: 180,
|
||||
positionFactor: 0,
|
||||
verticalAlignment: GaugeAlignment.center,
|
||||
widget: SimpleEntityState(
|
||||
expanded: false,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: Theme.of(context).textTheme.title.copyWith(
|
||||
fontSize: Theme.of(context).textTheme.title.fontSize * fontSizeFactor,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
axisLineStyle: AxisLineStyle(
|
||||
thickness: 0.3,
|
||||
thicknessUnit: GaugeSizeUnit.factor
|
||||
),
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
pointers: <GaugePointer>[
|
||||
NeedlePointer(
|
||||
value: fixedValue,
|
||||
lengthUnit: GaugeSizeUnit.factor,
|
||||
needleLength: 0.9,
|
||||
needleColor: Theme.of(context).accentColor,
|
||||
enableAnimation: true,
|
||||
needleStartWidth: 1,
|
||||
animationType: AnimationType.bounceOut,
|
||||
needleEndWidth: 3,
|
||||
knobStyle: KnobStyle(
|
||||
sizeUnit: GaugeSizeUnit.factor,
|
||||
color: Theme.of(context).buttonColor,
|
||||
knobRadius: 0.1
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RangeContainer {
|
||||
final int startFrom;
|
||||
Color color;
|
||||
|
||||
RangeContainer(this.startFrom, this.color);
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class LightCardBody extends StatefulWidget {
|
||||
|
||||
final int min;
|
||||
final int max;
|
||||
final Map severity;
|
||||
|
||||
LightCardBody({Key key, this.min, this.max, this.severity}) : super(key: key);
|
||||
|
||||
@override
|
||||
_LightCardBodyState createState() => _LightCardBodyState();
|
||||
}
|
||||
|
||||
class _LightCardBodyState extends State<LightCardBody> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||
LightEntity entity = entityWrapper.entity;
|
||||
Logger.d("Light brightness: ${entity.brightness}");
|
||||
|
||||
return FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Container(
|
||||
//color: Colors.redAccent,
|
||||
child: SingleCircularSlider(
|
||||
255,
|
||||
entity.brightness ?? 0,
|
||||
baseColor: Colors.white,
|
||||
handlerColor: Colors.blue[200],
|
||||
selectionColor: Colors.blue[100],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
}
|
@ -1,191 +0,0 @@
|
||||
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().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,47 +0,0 @@
|
||||
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,73 +0,0 @@
|
||||
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,66 +0,0 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
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
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,155 +0,0 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class EntityWrapper {
|
||||
|
||||
String overrideName;
|
||||
final String overrideIcon;
|
||||
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.uiAction,
|
||||
this.stateFilter
|
||||
}) {
|
||||
if (entity.statelessType == StatelessEntityType.NONE || entity.statelessType == StatelessEntityType.CALL_SERVICE || 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,46 +0,0 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
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,5 +0,0 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class SunEntity extends Entity {
|
||||
SunEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class VacuumEntity extends Entity {
|
||||
|
||||
static const SUPPORT_TURN_ON = 1;
|
||||
static const SUPPORT_TURN_OFF = 2;
|
||||
static const SUPPORT_PAUSE = 4;
|
||||
static const SUPPORT_STOP = 8;
|
||||
static const SUPPORT_RETURN_HOME = 16;
|
||||
static const SUPPORT_FAN_SPEED = 32;
|
||||
static const SUPPORT_BATTERY = 64;
|
||||
static const SUPPORT_STATUS = 128;
|
||||
static const SUPPORT_SEND_COMMAND = 256;
|
||||
static const SUPPORT_LOCATE = 512;
|
||||
static const SUPPORT_CLEAN_SPOT = 1024;
|
||||
static const SUPPORT_MAP = 2048;
|
||||
static const SUPPORT_STATE = 4096;
|
||||
static const SUPPORT_START = 8192;
|
||||
|
||||
VacuumEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
bool get supportTurnOn => ((supportedFeatures &
|
||||
VacuumEntity.SUPPORT_TURN_ON) ==
|
||||
VacuumEntity.SUPPORT_TURN_ON);
|
||||
bool get supportTurnOff => ((supportedFeatures &
|
||||
VacuumEntity.SUPPORT_TURN_OFF) ==
|
||||
VacuumEntity.SUPPORT_TURN_OFF);
|
||||
bool get supportPause => ((supportedFeatures &
|
||||
VacuumEntity.SUPPORT_PAUSE) ==
|
||||
VacuumEntity.SUPPORT_PAUSE);
|
||||
bool get supportStop => ((supportedFeatures &
|
||||
VacuumEntity.SUPPORT_STOP) ==
|
||||
VacuumEntity.SUPPORT_STOP);
|
||||
bool get supportReturnHome => ((supportedFeatures &
|
||||
VacuumEntity.SUPPORT_RETURN_HOME) ==
|
||||
VacuumEntity.SUPPORT_RETURN_HOME);
|
||||
bool get supportFanSpeed => ((supportedFeatures &
|
||||
VacuumEntity.SUPPORT_FAN_SPEED) ==
|
||||
VacuumEntity.SUPPORT_FAN_SPEED);
|
||||
bool get supportBattery => ((supportedFeatures &
|
||||
VacuumEntity.SUPPORT_BATTERY) ==
|
||||
VacuumEntity.SUPPORT_BATTERY);
|
||||
bool get supportStatus => ((supportedFeatures &
|
||||
VacuumEntity.SUPPORT_STATUS) ==
|
||||
VacuumEntity.SUPPORT_STATUS);
|
||||
bool get supportSendCommand => ((supportedFeatures &
|
||||
VacuumEntity.SUPPORT_SEND_COMMAND) ==
|
||||
VacuumEntity.SUPPORT_SEND_COMMAND);
|
||||
bool get supportLocate => ((supportedFeatures &
|
||||
VacuumEntity.SUPPORT_LOCATE) ==
|
||||
VacuumEntity.SUPPORT_LOCATE);
|
||||
bool get supportCleanSpot => ((supportedFeatures &
|
||||
VacuumEntity.SUPPORT_CLEAN_SPOT) ==
|
||||
VacuumEntity.SUPPORT_CLEAN_SPOT);
|
||||
bool get supportMap => ((supportedFeatures &
|
||||
VacuumEntity.SUPPORT_MAP) ==
|
||||
VacuumEntity.SUPPORT_MAP);
|
||||
bool get supportState => ((supportedFeatures &
|
||||
VacuumEntity.SUPPORT_STATE) ==
|
||||
VacuumEntity.SUPPORT_STATE);
|
||||
bool get supportStart => ((supportedFeatures &
|
||||
VacuumEntity.SUPPORT_START) ==
|
||||
VacuumEntity.SUPPORT_START);
|
||||
|
||||
List<String> get fanSpeedList => getStringListAttributeValue("fan_speed_list");
|
||||
String get fanSpeed => getAttribute("fan_speed");
|
||||
String get status => getAttribute("status");
|
||||
int get batteryLevel => _getIntAttributeValue("battery_level");
|
||||
String get batteryIcon => getAttribute("battery_icon");
|
||||
double get cleanedArea => _getDoubleAttributeValue("cleaned_area");
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
if (supportTurnOn || supportTurnOff) {
|
||||
return SwitchStateWidget(
|
||||
domainForService: "vacuum",
|
||||
);
|
||||
} else {
|
||||
return SimpleEntityState();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget _buildStatePartForPage(BuildContext context) {
|
||||
return EntityModel(
|
||||
entityWrapper: EntityWrapper(
|
||||
entity: this
|
||||
),
|
||||
child: VacuumStateButton(),
|
||||
handleTap: false,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
return VacuumControls();
|
||||
}
|
||||
}
|
@ -1,229 +0,0 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class VacuumControls extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
VacuumEntity entity = EntityModel.of(context).entityWrapper.entity;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: Sizes.leftWidgetPadding, right: Sizes.rightWidgetPadding),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
_buildStatusAndBattery(entity, context),
|
||||
_buildCommands(entity),
|
||||
_buildFanSpeed(entity),
|
||||
_buildAdditionalInfo(entity)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusAndBattery(VacuumEntity entity, BuildContext context) {
|
||||
List<Widget> result = [];
|
||||
if (entity.supportStatus) {
|
||||
result.addAll(
|
||||
<Widget>[
|
||||
Text("Status:"),
|
||||
Container(width: 6,),
|
||||
Expanded(
|
||||
//flex: 1,
|
||||
child: Text(
|
||||
"${entity.status}",
|
||||
maxLines: 1,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.body2,
|
||||
),
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
if (entity.supportBattery && entity.batteryLevel != null) {
|
||||
String iconName = entity.batteryIcon ?? "mdi:battery";
|
||||
int batteryLevel = entity.batteryLevel ?? 100;
|
||||
result.addAll(<Widget>[
|
||||
Icon(MaterialDesignIcons.getIconDataFromIconName(iconName)),
|
||||
Container(width: 6,),
|
||||
Text("$batteryLevel %")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if (result.isEmpty) {
|
||||
return Container(width: 0, height: 0);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: Sizes.doubleRowPadding),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: result,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCommands(VacuumEntity entity) {
|
||||
List<Widget> commandButtons = [];
|
||||
double iconSize = 32;
|
||||
if (entity.supportStart) {
|
||||
commandButtons.add(
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:play")),
|
||||
iconSize: iconSize,
|
||||
onPressed: () => ConnectionManager().callService(
|
||||
domain: "vacuum",
|
||||
entityId: entity.entityId,
|
||||
service: "start"
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
if (entity.supportPause && !entity.supportStart) {
|
||||
commandButtons.add(
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:play-pause")),
|
||||
iconSize: iconSize,
|
||||
onPressed: () => ConnectionManager().callService(
|
||||
domain: "vacuum",
|
||||
entityId: entity.entityId,
|
||||
service: "start_pause"
|
||||
),
|
||||
)
|
||||
);
|
||||
} else if (entity.supportPause) {
|
||||
commandButtons.add(
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:pause")),
|
||||
iconSize: iconSize,
|
||||
onPressed: () => ConnectionManager().callService(
|
||||
domain: "vacuum",
|
||||
entityId: entity.entityId,
|
||||
service: "pause"
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
if (entity.supportStop) {
|
||||
commandButtons.add(
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:stop")),
|
||||
iconSize: iconSize,
|
||||
onPressed: () => ConnectionManager().callService(
|
||||
domain: "vacuum",
|
||||
entityId: entity.entityId,
|
||||
service: "stop"
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
if (entity.supportCleanSpot) {
|
||||
commandButtons.add(
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:broom")),
|
||||
iconSize: iconSize,
|
||||
onPressed: () => ConnectionManager().callService(
|
||||
domain: "vacuum",
|
||||
entityId: entity.entityId,
|
||||
service: "clean_spot"
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
if (entity.supportLocate) {
|
||||
commandButtons.add(
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:map-marker")),
|
||||
iconSize: iconSize,
|
||||
onPressed: () => ConnectionManager().callService(
|
||||
domain: "vacuum",
|
||||
entityId: entity.entityId,
|
||||
service: "locate"
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
if (entity.supportReturnHome) {
|
||||
commandButtons.add(
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:home-map-marker")),
|
||||
iconSize: iconSize,
|
||||
onPressed: () => ConnectionManager().callService(
|
||||
domain: "vacuum",
|
||||
entityId: entity.entityId,
|
||||
service: "return_to_base"
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (commandButtons.isEmpty) {
|
||||
return Container(width: 0, height: 0,);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: Sizes.doubleRowPadding),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("Vacuum cleaner commands:"),
|
||||
Container(height: Sizes.rowPadding,),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: commandButtons.map((button) => Expanded(
|
||||
child: button,
|
||||
)).toList(),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFanSpeed(VacuumEntity entity) {
|
||||
if (entity.supportFanSpeed) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: Sizes.doubleRowPadding),
|
||||
child: ModeSelectorWidget(
|
||||
caption: "Fan speed",
|
||||
options: entity.fanSpeedList,
|
||||
value: entity.fanSpeed,
|
||||
onChange: (val) => ConnectionManager().callService(
|
||||
domain: "vacuum",
|
||||
entityId: entity.entityId,
|
||||
service: "set_fan_speed",
|
||||
data: {"fan_speed": val}
|
||||
)
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0, height: 0,);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Widget _buildAdditionalInfo(VacuumEntity entity) {
|
||||
List<Widget> rows = [];
|
||||
if (entity.cleanedArea != null) {
|
||||
rows.add(
|
||||
Text("Cleaned area: ${entity.cleanedArea}")
|
||||
);
|
||||
}
|
||||
|
||||
if (rows.isEmpty) {
|
||||
return Container(width: 0, height: 0,);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: Sizes.doubleRowPadding),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: rows,
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
part of '../../../main.dart';
|
||||
|
||||
class VacuumStateButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget result;
|
||||
VacuumEntity entity = EntityModel.of(context).entityWrapper.entity;
|
||||
if (entity.supportTurnOn && entity.supportTurnOff) {
|
||||
result = FlatServiceButton(
|
||||
serviceDomain: "vacuum",
|
||||
serviceName: entity.state == EntityState.on ? "turn_off" : "turn_on",
|
||||
entityId: entity.entityId,
|
||||
text: entity.state == EntityState.on ? "TURN OFF" : "TURN ON"
|
||||
);
|
||||
} else if (entity.supportStart && (entity.state == EntityState.docked || entity.state == EntityState.idle)) {
|
||||
result = FlatServiceButton(
|
||||
serviceDomain: "vacuum",
|
||||
serviceName: "start",
|
||||
entityId: entity.entityId,
|
||||
text: "START CLEANING"
|
||||
);
|
||||
} else if (entity.supportReturnHome && entity.state == EntityState.cleaning) {
|
||||
result = FlatServiceButton(
|
||||
serviceDomain: "vacuum",
|
||||
serviceName: "return_to_base",
|
||||
entityId: entity.entityId,
|
||||
text: "RETURN TO DOCK"
|
||||
);
|
||||
} else {
|
||||
result = Text(entity.state.toUpperCase(), style: Theme.of(context).textTheme.subhead);
|
||||
}
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(right: 15),
|
||||
child: result,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,72 +1,62 @@
|
||||
part of '../main.dart';
|
||||
part of 'main.dart';
|
||||
|
||||
class EntityViewPage extends StatefulWidget {
|
||||
EntityViewPage({Key key, @required this.entityId}) : super(key: key);
|
||||
EntityViewPage({Key key, @required this.entityId, @required this.homeAssistant }) : super(key: key);
|
||||
|
||||
final String entityId;
|
||||
final HomeAssistant homeAssistant;
|
||||
|
||||
@override
|
||||
_EntityViewPageState createState() => new _EntityViewPageState();
|
||||
}
|
||||
|
||||
class _EntityViewPageState extends State<EntityViewPage> {
|
||||
String _title;
|
||||
StreamSubscription _refreshDataSubscription;
|
||||
StreamSubscription _stateSubscription;
|
||||
Entity entity;
|
||||
Entity forwardToMainPage;
|
||||
bool _popScheduled = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
|
||||
if (event.entityId == widget.entityId) {
|
||||
entity = HomeAssistant().entities.get(widget.entityId);
|
||||
Logger.d("[Entity page] State change event handled: ${event.entityId}");
|
||||
Logger.d("State change event handled by entity page: ${event.entityId}");
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
|
||||
entity = HomeAssistant().entities.get(widget.entityId);
|
||||
setState(() {});
|
||||
});
|
||||
entity = HomeAssistant().entities.get(widget.entityId);
|
||||
_prepareData();
|
||||
}
|
||||
|
||||
void _prepareData() async {
|
||||
_title = widget.homeAssistant.entities.get(widget.entityId).displayName;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget body;
|
||||
if (MediaQuery.of(context).size.width >= Sizes.tabletMinWidth) {
|
||||
if (!_popScheduled) {
|
||||
_popScheduled = true;
|
||||
_popAfterBuild();
|
||||
}
|
||||
body = PageLoadingIndicator();
|
||||
} else {
|
||||
body = EntityPageLayout(entity: entity);
|
||||
}
|
||||
return new Scaffold(
|
||||
appBar: new AppBar(
|
||||
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
|
||||
Navigator.pop(context);
|
||||
}),
|
||||
title: new Text("${entity.displayName}"),
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: new Text(_title),
|
||||
),
|
||||
body: HomeAssistantModel(
|
||||
homeAssistant: widget.homeAssistant,
|
||||
child: widget.homeAssistant.entities.get(widget.entityId).buildEntityPageWidget(context)
|
||||
),
|
||||
body: body,
|
||||
);
|
||||
}
|
||||
|
||||
_popAfterBuild() async {
|
||||
forwardToMainPage = entity;
|
||||
await Future.delayed(Duration(milliseconds: 300));
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose(){
|
||||
if (_stateSubscription != null) _stateSubscription.cancel();
|
||||
if (_refreshDataSubscription != null) _refreshDataSubscription.cancel();
|
||||
eventBus.fire(ShowEntityPageEvent(entity: forwardToMainPage));
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class AlarmControlPanelEntity extends Entity {
|
||||
AlarmControlPanelEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
AlarmControlPanelEntity(Map rawData) : super(rawData);
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
@ -1,8 +1,7 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class AutomationEntity extends Entity {
|
||||
AutomationEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
AutomationEntity(Map rawData) : super(rawData);
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
@ -1,8 +1,7 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class ButtonEntity extends Entity {
|
||||
ButtonEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
ButtonEntity(Map rawData) : super(rawData);
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
@ -1,18 +1,14 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class CameraEntity extends Entity {
|
||||
|
||||
static const SUPPORT_ON_OFF = 1;
|
||||
static const SUPPORT_STREAM = 2;
|
||||
|
||||
CameraEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
CameraEntity(Map rawData) : super(rawData);
|
||||
|
||||
bool get supportOnOff => ((supportedFeatures &
|
||||
CameraEntity.SUPPORT_ON_OFF) ==
|
||||
CameraEntity.SUPPORT_ON_OFF);
|
||||
bool get supportStream => ((supportedFeatures &
|
||||
CameraEntity.SUPPORT_STREAM) ==
|
||||
CameraEntity.SUPPORT_STREAM);
|
||||
|
||||
@override
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
@ -1,4 +1,4 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class ClimateEntity extends Entity {
|
||||
|
||||
@ -10,57 +10,69 @@ class ClimateEntity extends Entity {
|
||||
);
|
||||
|
||||
static const SUPPORT_TARGET_TEMPERATURE = 1;
|
||||
static const SUPPORT_TARGET_TEMPERATURE_RANGE = 2;
|
||||
static const SUPPORT_TARGET_HUMIDITY = 4;
|
||||
static const SUPPORT_FAN_MODE = 8;
|
||||
static const SUPPORT_PRESET_MODE = 16;
|
||||
static const SUPPORT_SWING_MODE = 32;
|
||||
static const SUPPORT_AUX_HEAT = 64;
|
||||
|
||||
|
||||
//static const SUPPORT_OPERATION_MODE = 16;
|
||||
//static const SUPPORT_HOLD_MODE = 256;
|
||||
//static const SUPPORT_AWAY_MODE = 1024;
|
||||
//static const SUPPORT_ON_OFF = 4096;
|
||||
|
||||
ClimateEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
static const SUPPORT_TARGET_TEMPERATURE_HIGH = 2;
|
||||
static const SUPPORT_TARGET_TEMPERATURE_LOW = 4;
|
||||
static const SUPPORT_TARGET_HUMIDITY = 8;
|
||||
static const SUPPORT_TARGET_HUMIDITY_HIGH = 16;
|
||||
static const SUPPORT_TARGET_HUMIDITY_LOW = 32;
|
||||
static const SUPPORT_FAN_MODE = 64;
|
||||
static const SUPPORT_OPERATION_MODE = 128;
|
||||
static const SUPPORT_HOLD_MODE = 256;
|
||||
static const SUPPORT_SWING_MODE = 512;
|
||||
static const SUPPORT_AWAY_MODE = 1024;
|
||||
static const SUPPORT_AUX_HEAT = 2048;
|
||||
static const SUPPORT_ON_OFF = 4096;
|
||||
|
||||
bool get supportTargetTemperature => ((supportedFeatures &
|
||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE) ==
|
||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE);
|
||||
bool get supportTargetTemperatureRange => ((supportedFeatures &
|
||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_RANGE) ==
|
||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_RANGE);
|
||||
bool get supportTargetTemperatureHigh => ((supportedFeatures &
|
||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH) ==
|
||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH);
|
||||
bool get supportTargetTemperatureLow => ((supportedFeatures &
|
||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW) ==
|
||||
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW);
|
||||
bool get supportTargetHumidity => ((supportedFeatures &
|
||||
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 =>
|
||||
((supportedFeatures & 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 =>
|
||||
((supportedFeatures & ClimateEntity.SUPPORT_SWING_MODE) ==
|
||||
ClimateEntity.SUPPORT_SWING_MODE);
|
||||
bool get supportPresetMode =>
|
||||
((supportedFeatures & ClimateEntity.SUPPORT_PRESET_MODE) ==
|
||||
ClimateEntity.SUPPORT_PRESET_MODE);
|
||||
bool get supportAwayMode =>
|
||||
((supportedFeatures & ClimateEntity.SUPPORT_AWAY_MODE) ==
|
||||
ClimateEntity.SUPPORT_AWAY_MODE);
|
||||
bool get supportAuxHeat =>
|
||||
((supportedFeatures & ClimateEntity.SUPPORT_AUX_HEAT) ==
|
||||
ClimateEntity.SUPPORT_AUX_HEAT);
|
||||
bool get supportOnOff =>
|
||||
((supportedFeatures & ClimateEntity.SUPPORT_ON_OFF) ==
|
||||
ClimateEntity.SUPPORT_ON_OFF);
|
||||
|
||||
List<String> get hvacModes => attributes["hvac_modes"] != null
|
||||
? (attributes["hvac_modes"] as List).cast<String>()
|
||||
List<String> get operationList => attributes["operation_list"] != null
|
||||
? (attributes["operation_list"] as List).cast<String>()
|
||||
: null;
|
||||
List<String> get fanModes => attributes["fan_modes"] != null
|
||||
? (attributes["fan_modes"] as List).cast<String>()
|
||||
List<String> get fanList => attributes["fan_list"] != null
|
||||
? (attributes["fan_list"] as List).cast<String>()
|
||||
: null;
|
||||
List<String> get presetModes => attributes["preset_modes"] != null
|
||||
? (attributes["preset_modes"] as List).cast<String>()
|
||||
: null;
|
||||
List<String> get swingModes => attributes["swing_modes"] != null
|
||||
? (attributes["swing_modes"] as List).cast<String>()
|
||||
List<String> get swingList => attributes["swing_list"] != null
|
||||
? (attributes["swing_list"] as List).cast<String>()
|
||||
: null;
|
||||
double get temperature => _getDoubleAttributeValue('temperature');
|
||||
double get currentTemperature => _getDoubleAttributeValue('current_temperature');
|
||||
double get targetHigh => _getDoubleAttributeValue('target_temp_high');
|
||||
double get targetLow => _getDoubleAttributeValue('target_temp_low');
|
||||
double get maxTemp => _getDoubleAttributeValue('max_temp') ?? 100.0;
|
||||
@ -69,22 +81,25 @@ class ClimateEntity extends Entity {
|
||||
double get maxHumidity => _getDoubleAttributeValue('max_humidity');
|
||||
double get minHumidity => _getDoubleAttributeValue('min_humidity');
|
||||
double get temperatureStep => _getDoubleAttributeValue('target_temp_step') ?? 0.5;
|
||||
String get hvacAction => attributes['hvac_action'];
|
||||
String get operationMode => attributes['operation_mode'];
|
||||
String get fanMode => attributes['fan_mode'];
|
||||
String get presetMode => attributes['preset_mode'];
|
||||
String get swingMode => attributes['swing_mode'];
|
||||
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";
|
||||
|
||||
ClimateEntity(Map rawData) : super(rawData);
|
||||
|
||||
@override
|
||||
void update(Map rawData, String webHost) {
|
||||
super.update(rawData, webHost);
|
||||
void update(Map rawData) {
|
||||
super.update(rawData);
|
||||
if (supportTargetTemperature) {
|
||||
historyConfig.numericAttributesToShow.add("temperature");
|
||||
}
|
||||
if (supportTargetTemperatureRange) {
|
||||
if (supportTargetTemperatureHigh) {
|
||||
historyConfig.numericAttributesToShow.add("target_temp_high");
|
||||
}
|
||||
if (supportTargetTemperatureLow) {
|
||||
historyConfig.numericAttributesToShow.add("target_temp_low");
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
part of 'main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class EntityState {
|
||||
static const on = 'on';
|
||||
@ -29,11 +29,6 @@ class EntityState {
|
||||
static const ok = 'ok';
|
||||
static const problem = 'problem';
|
||||
static const active = 'active';
|
||||
static const cleaning = 'cleaning';
|
||||
static const docked = 'docked';
|
||||
static const returning = 'returning';
|
||||
static const error = 'error';
|
||||
|
||||
}
|
||||
|
||||
class EntityUIAction {
|
||||
@ -51,10 +46,6 @@ class EntityUIAction {
|
||||
String holdNavigationPath;
|
||||
String holdService;
|
||||
Map<String, dynamic> holdServiceData;
|
||||
String doubleTapAction = EntityUIAction.none;
|
||||
String doubleTapNavigationPath;
|
||||
String doubleTapService;
|
||||
Map<String, dynamic> doubleTapServiceData;
|
||||
|
||||
EntityUIAction({rawEntityData}) {
|
||||
if (rawEntityData != null) {
|
||||
@ -80,61 +71,29 @@ class EntityUIAction {
|
||||
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 {
|
||||
static const HORIZONTAL_STACK = "horizontal-stack";
|
||||
static const VERTICAL_STACK = "vertical-stack";
|
||||
static const ENTITIES = "entities";
|
||||
static const GLANCE = "glance";
|
||||
static const MEDIA_CONTROL = "media-control";
|
||||
static const WEATHER_FORECAST = "weather-forecast";
|
||||
static const THERMOSTAT = "thermostat";
|
||||
static const SENSOR = "sensor";
|
||||
static const PLANT_STATUS = "plant-status";
|
||||
static const PICTURE_ENTITY = "picture-entity";
|
||||
static const PICTURE_ELEMENTS = "picture-elements";
|
||||
static const PICTURE = "picture";
|
||||
static const MAP = "map";
|
||||
static const IFRAME = "iframe";
|
||||
static const GAUGE = "gauge";
|
||||
static const ENTITY_BUTTON = "entity-button";
|
||||
static const CONDITIONAL = "conditional";
|
||||
static const ALARM_PANEL = "alarm-panel";
|
||||
static const MARKDOWN = "markdown";
|
||||
static const LIGHT = "light";
|
||||
}
|
||||
|
||||
class Sizes {
|
||||
static const rightWidgetPadding = 10.0;
|
||||
static const leftWidgetPadding = 10.0;
|
||||
static const buttonPadding = 4.0;
|
||||
static const extendedWidgetHeight = 50.0;
|
||||
static const iconSize = 28.0;
|
||||
static const largeIconSize = 46.0;
|
||||
//static const stateFontSize = 15.0;
|
||||
//static const nameFontSize = 15.0;
|
||||
//static const smallFontSize = 14.0;
|
||||
//static const largeFontSize = 24.0;
|
||||
static const inputWidth = 160.0;
|
||||
static const rowPadding = 10.0;
|
||||
static const doubleRowPadding = rowPadding*2;
|
||||
static const minViewColumnWidth = 350;
|
||||
static const entityPageMaxWidth = 400.0;
|
||||
static const mainPageScreenSeparatorWidth = 5.0;
|
||||
static const tabletMinWidth = minViewColumnWidth + entityPageMaxWidth + 5;
|
||||
static const horizontalStack = "horizontal-stack";
|
||||
static const verticalStack = "vertical-stack";
|
||||
static const entities = "entities";
|
||||
static const glance = "glance";
|
||||
static const mediaControl = "media-control";
|
||||
static const weatherForecast = "weather-forecast";
|
||||
static const thermostat = "thermostat";
|
||||
static const sensor = "sensor";
|
||||
static const plantStatus = "plant-status";
|
||||
static const pictureEntity = "picture-entity";
|
||||
static const pictureElements = "picture-elements";
|
||||
static const picture = "picture";
|
||||
static const map = "map";
|
||||
static const iframe = "iframe";
|
||||
static const gauge = "gauge";
|
||||
static const entityButton = "entity-button";
|
||||
static const conditional = "conditional";
|
||||
static const alarmPanel = "alarm-panel";
|
||||
static const markdown = "markdown";
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class CoverEntity extends Entity {
|
||||
|
||||
@ -11,8 +11,6 @@ class CoverEntity extends Entity {
|
||||
static const SUPPORT_STOP_TILT = 64;
|
||||
static const SUPPORT_SET_TILT_POSITION = 128;
|
||||
|
||||
CoverEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
bool get supportOpen => ((supportedFeatures &
|
||||
CoverEntity.SUPPORT_OPEN) ==
|
||||
CoverEntity.SUPPORT_OPEN);
|
||||
@ -47,6 +45,8 @@ class CoverEntity extends Entity {
|
||||
bool get canTiltBeOpened => currentTiltPosition < 100;
|
||||
bool get canTiltBeClosed => currentTiltPosition > 0;
|
||||
|
||||
CoverEntity(Map rawData) : super(rawData);
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return CoverStateWidget();
|
@ -1,8 +1,6 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class DateTimeEntity extends Entity {
|
||||
DateTimeEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
bool get hasDate => attributes["has_date"] ?? false;
|
||||
bool get hasTime => attributes["has_time"] ?? false;
|
||||
int get year => attributes["year"] ?? 1970;
|
||||
@ -14,6 +12,8 @@ class DateTimeEntity extends Entity {
|
||||
String get formattedState => _getFormattedState();
|
||||
DateTime get dateTimeState => _getDateTimeState();
|
||||
|
||||
DateTimeEntity(Map rawData) : super(rawData);
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
return DateTimeStateWidget();
|
||||
@ -35,7 +35,8 @@ class DateTimeEntity extends Entity {
|
||||
return formattedState;
|
||||
}
|
||||
|
||||
void setNewState(Map newValue) {
|
||||
ConnectionManager().callService(domain: domain, service: "set_datetime", entityId: entityId, data: newValue);
|
||||
void setNewState(newValue) {
|
||||
eventBus
|
||||
.fire(new ServiceCallEvent(domain, "set_datetime", entityId, newValue));
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ class Entity {
|
||||
"cold.on": "Cold",
|
||||
"cold.off": "Normal",
|
||||
"connectivity.on": "Connected",
|
||||
"connectivity.off": "Disconnected",
|
||||
"connectivity.off": "Diconnected",
|
||||
"door.on": "Open",
|
||||
"door.off": "Closed",
|
||||
"garage_door.on": "Open",
|
||||
@ -73,7 +73,6 @@ class Entity {
|
||||
Map attributes;
|
||||
String domain;
|
||||
String entityId;
|
||||
String entityPicture;
|
||||
String state;
|
||||
String displayState;
|
||||
DateTime _lastUpdated;
|
||||
@ -95,6 +94,7 @@ class Entity {
|
||||
bool get isBadge => Entity.badgeDomains.contains(domain);
|
||||
String get icon => attributes["icon"] ?? "";
|
||||
bool get isOn => state == EntityState.on;
|
||||
String get entityPicture => _getEntityPictureUrl();
|
||||
String get unitOfMeasurement => attributes["unit_of_measurement"] ?? "";
|
||||
List get childEntityIds => attributes["entity_id"] ?? [];
|
||||
String get lastUpdated => _getLastUpdatedFormatted();
|
||||
@ -102,21 +102,21 @@ class Entity {
|
||||
double get doubleState => double.tryParse(state) ?? 0.0;
|
||||
int get supportedFeatures => attributes["supported_features"] ?? 0;
|
||||
|
||||
String _getEntityPictureUrl(String webHost) {
|
||||
String _getEntityPictureUrl() {
|
||||
String result = attributes["entity_picture"];
|
||||
if (result == null) return result;
|
||||
if (!result.startsWith("http")) {
|
||||
if (result.startsWith("/")) {
|
||||
result = "$webHost$result";
|
||||
result = "$homeAssistantWebHost$result";
|
||||
} else {
|
||||
result = "$webHost/$result";
|
||||
result = "$homeAssistantWebHost/$result";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Entity(Map rawData, String webHost) {
|
||||
update(rawData, webHost);
|
||||
Entity(Map rawData) {
|
||||
update(rawData);
|
||||
}
|
||||
|
||||
Entity.missed(String entityId) {
|
||||
@ -148,15 +148,14 @@ class Entity {
|
||||
attributes = {"hidden": false, "friendly_name": "${name ?? url}", "icon": "${icon ?? 'mdi:link'}"};
|
||||
}
|
||||
|
||||
void update(Map rawData, String webHost) {
|
||||
void update(Map rawData) {
|
||||
attributes = rawData["attributes"] ?? {};
|
||||
domain = rawData["entity_id"].split(".")[0];
|
||||
entityId = rawData["entity_id"];
|
||||
deviceClass = attributes["device_class"];
|
||||
state = rawData["state"] is bool ? (rawData["state"] ? EntityState.on : EntityState.off) : rawData["state"];
|
||||
displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? (state.toLowerCase() == 'unknown' ? '-' : state);
|
||||
state = rawData["state"];
|
||||
displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? state;
|
||||
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
|
||||
entityPicture = _getEntityPictureUrl(webHost);
|
||||
}
|
||||
|
||||
double _getDoubleAttributeValue(String attributeName) {
|
||||
@ -211,6 +210,31 @@ 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) {
|
||||
return EntityModel(
|
||||
entityWrapper: EntityWrapper(entity: this),
|
||||
@ -221,7 +245,7 @@ class Entity {
|
||||
|
||||
String getAttribute(String attributeName) {
|
||||
if (attributes != null) {
|
||||
return attributes["$attributeName"].toString();
|
||||
return attributes["$attributeName"];
|
||||
}
|
||||
return null;
|
||||
}
|
114
lib/entity_class/entity_wrapper.class.dart
Normal file
114
lib/entity_class/entity_wrapper.class.dart
Normal file
@ -0,0 +1,114 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class EntityWrapper {
|
||||
|
||||
String displayName;
|
||||
String icon;
|
||||
String entityPicture;
|
||||
EntityUIAction uiAction;
|
||||
Entity entity;
|
||||
|
||||
|
||||
EntityWrapper({
|
||||
this.entity,
|
||||
String icon,
|
||||
String displayName,
|
||||
this.uiAction
|
||||
}) {
|
||||
if (entity.statelessType == StatelessEntityType.NONE || entity.statelessType == StatelessEntityType.CALL_SERVICE || entity.statelessType == StatelessEntityType.WEBLINK) {
|
||||
this.icon = icon ?? entity.icon;
|
||||
if (icon == null) {
|
||||
entityPicture = entity.entityPicture;
|
||||
}
|
||||
this.displayName = displayName ?? entity.displayName;
|
||||
if (uiAction == null) {
|
||||
uiAction = EntityUIAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleTap() {
|
||||
switch (uiAction.tapAction) {
|
||||
case EntityUIAction.toggle: {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityUIAction.callService: {
|
||||
if (uiAction.tapService != null) {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent(uiAction.tapService.split(".")[0],
|
||||
uiAction.tapService.split(".")[1], null,
|
||||
uiAction.tapServiceData));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityUIAction.none: {
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityUIAction.moreInfo: {
|
||||
eventBus.fire(
|
||||
new ShowEntityPageEvent(entity));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityUIAction.navigate: {
|
||||
if (uiAction.tapService.startsWith("/")) {
|
||||
//TODO handle local urls
|
||||
Logger.w("Local urls is not supported yet");
|
||||
} else {
|
||||
HAUtils.launchURL(uiAction.tapService);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleHold() {
|
||||
switch (uiAction.holdAction) {
|
||||
case EntityUIAction.toggle: {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityUIAction.callService: {
|
||||
if (uiAction.holdService != null) {
|
||||
eventBus.fire(
|
||||
ServiceCallEvent(uiAction.holdService.split(".")[0],
|
||||
uiAction.holdService.split(".")[1], null,
|
||||
uiAction.holdServiceData));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityUIAction.moreInfo: {
|
||||
eventBus.fire(
|
||||
new ShowEntityPageEvent(entity));
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityUIAction.navigate: {
|
||||
if (uiAction.holdService.startsWith("/")) {
|
||||
//TODO handle local urls
|
||||
Logger.w("Local urls is not supported yet");
|
||||
} else {
|
||||
HAUtils.launchURL(uiAction.holdService);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class FanEntity extends Entity {
|
||||
|
||||
@ -6,7 +6,7 @@ class FanEntity extends Entity {
|
||||
static const SUPPORT_OSCILLATE = 2;
|
||||
static const SUPPORT_DIRECTION = 4;
|
||||
|
||||
FanEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
FanEntity(Map rawData) : super(rawData);
|
||||
|
||||
bool get supportSetSpeed => ((supportedFeatures &
|
||||
FanEntity.SUPPORT_SET_SPEED) ==
|
@ -1,13 +1,12 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class GroupEntity extends Entity {
|
||||
GroupEntity(Map rawData) : super(rawData);
|
||||
|
||||
final List<String> _domainsForSwitchableGroup = ["switch", "light", "automation", "input_boolean"];
|
||||
String mutualDomain;
|
||||
bool switchable = false;
|
||||
|
||||
GroupEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
||||
if (switchable) {
|
||||
@ -20,8 +19,8 @@ class GroupEntity extends Entity {
|
||||
}
|
||||
|
||||
@override
|
||||
void update(Map rawData, String webHost) {
|
||||
super.update(rawData, webHost);
|
||||
void update(Map rawData) {
|
||||
super.update(rawData);
|
||||
if (_isOneDomain()) {
|
||||
mutualDomain = attributes['entity_id'][0].split(".")[0];
|
||||
switchable = _domainsForSwitchableGroup.contains(mutualDomain);
|
@ -1,4 +1,4 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class LightEntity extends Entity {
|
||||
|
||||
@ -42,7 +42,7 @@ class LightEntity extends Entity {
|
||||
bool get isAdditionalControls => ((supportedFeatures != null) && (supportedFeatures != 0));
|
||||
List<String> get effectList => getStringListAttributeValue("effect_list");
|
||||
|
||||
LightEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
LightEntity(Map rawData) : super(rawData);
|
||||
|
||||
HSVColor _getColor() {
|
||||
List hs = attributes["hs_color"];
|
@ -1,7 +1,7 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class LockEntity extends Entity {
|
||||
LockEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
LockEntity(Map rawData) : super(rawData);
|
||||
|
||||
bool get isLocked => state == "locked";
|
||||
|
@ -1,4 +1,4 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class MediaPlayerEntity extends Entity {
|
||||
|
||||
@ -20,7 +20,7 @@ class MediaPlayerEntity extends Entity {
|
||||
static const SUPPORT_SHUFFLE_SET = 32768;
|
||||
static const SUPPORT_SELECT_SOUND_MODE = 65536;
|
||||
|
||||
MediaPlayerEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
MediaPlayerEntity(Map rawData) : super(rawData);
|
||||
|
||||
bool get supportPause => ((supportedFeatures &
|
||||
MediaPlayerEntity.SUPPORT_PAUSE) ==
|
||||
@ -74,34 +74,10 @@ class MediaPlayerEntity extends Entity {
|
||||
|
||||
List<String> get soundModeList => getStringListAttributeValue("sound_mode_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
|
||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,8 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class SunEntity extends Entity {
|
||||
SunEntity(Map rawData) : super(rawData);
|
||||
}
|
||||
|
||||
class SensorEntity extends Entity {
|
||||
|
||||
@ -8,6 +12,6 @@ class SensorEntity extends Entity {
|
||||
numericState: true
|
||||
);
|
||||
|
||||
SensorEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
SensorEntity(Map rawData) : super(rawData);
|
||||
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class SelectEntity extends Entity {
|
||||
List<String> get listOptions => attributes["options"] != null
|
||||
? (attributes["options"] as List).cast<String>()
|
||||
: [];
|
||||
|
||||
SelectEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
SelectEntity(Map rawData) : super(rawData);
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
@ -1,7 +1,7 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class SliderEntity extends Entity {
|
||||
SliderEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
SliderEntity(Map rawData) : super(rawData);
|
||||
|
||||
double get minValue => _getDoubleAttributeValue("min") ?? 0.0;
|
||||
double get maxValue =>_getDoubleAttributeValue("max") ?? 100.0;
|
@ -1,7 +1,7 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class SwitchEntity extends Entity {
|
||||
SwitchEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
SwitchEntity(Map rawData) : super(rawData);
|
||||
|
||||
@override
|
||||
Widget _buildStatePart(BuildContext context) {
|
@ -1,7 +1,7 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class TextEntity extends Entity {
|
||||
TextEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
TextEntity(Map rawData) : super(rawData);
|
||||
|
||||
int get valueMinLength => attributes["min"] ?? -1;
|
||||
int get valueMaxLength => attributes["max"] ?? -1;
|
@ -1,13 +1,13 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class TimerEntity extends Entity {
|
||||
TimerEntity(Map rawData, String webHost) : super(rawData, webHost);
|
||||
TimerEntity(Map rawData) : super(rawData);
|
||||
|
||||
Duration duration;
|
||||
|
||||
@override
|
||||
void update(Map rawData, String webHost) {
|
||||
super.update(rawData, webHost);
|
||||
void update(Map rawData) {
|
||||
super.update(rawData);
|
||||
String durationSource = "${attributes["duration"]}";
|
||||
if (durationSource != null && durationSource.isNotEmpty) {
|
||||
try {
|
@ -2,15 +2,13 @@ part of 'main.dart';
|
||||
|
||||
class EntityCollection {
|
||||
|
||||
final homeAssistantWebHost;
|
||||
|
||||
Map<String, Entity> _allEntities;
|
||||
//Map<String, Entity> views;
|
||||
|
||||
bool get isEmpty => _allEntities.isEmpty;
|
||||
List<Entity> get viewEntities => _allEntities.values.where((entity) => entity.isView).toList();
|
||||
|
||||
EntityCollection(this.homeAssistantWebHost) {
|
||||
EntityCollection() {
|
||||
_allEntities = {};
|
||||
//views = {};
|
||||
}
|
||||
@ -35,77 +33,70 @@ class EntityCollection {
|
||||
});
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_allEntities.clear();
|
||||
}
|
||||
|
||||
Entity _createEntityInstance(rawEntityData) {
|
||||
switch (rawEntityData["entity_id"].split(".")[0]) {
|
||||
case 'sun': {
|
||||
return SunEntity(rawEntityData, homeAssistantWebHost);
|
||||
return SunEntity(rawEntityData);
|
||||
}
|
||||
case "media_player": {
|
||||
return MediaPlayerEntity(rawEntityData, homeAssistantWebHost);
|
||||
return MediaPlayerEntity(rawEntityData);
|
||||
}
|
||||
case 'sensor': {
|
||||
return SensorEntity(rawEntityData, homeAssistantWebHost);
|
||||
return SensorEntity(rawEntityData);
|
||||
}
|
||||
case 'lock': {
|
||||
return LockEntity(rawEntityData, homeAssistantWebHost);
|
||||
return LockEntity(rawEntityData);
|
||||
}
|
||||
case "automation": {
|
||||
return AutomationEntity(rawEntityData, homeAssistantWebHost);
|
||||
return AutomationEntity(rawEntityData);
|
||||
}
|
||||
|
||||
case "input_boolean":
|
||||
case "switch": {
|
||||
return SwitchEntity(rawEntityData, homeAssistantWebHost);
|
||||
return SwitchEntity(rawEntityData);
|
||||
}
|
||||
case "light": {
|
||||
return LightEntity(rawEntityData, homeAssistantWebHost);
|
||||
return LightEntity(rawEntityData);
|
||||
}
|
||||
case "group": {
|
||||
return GroupEntity(rawEntityData, homeAssistantWebHost);
|
||||
return GroupEntity(rawEntityData);
|
||||
}
|
||||
case "script":
|
||||
case "scene": {
|
||||
return ButtonEntity(rawEntityData, homeAssistantWebHost);
|
||||
return ButtonEntity(rawEntityData);
|
||||
}
|
||||
case "input_datetime": {
|
||||
return DateTimeEntity(rawEntityData, homeAssistantWebHost);
|
||||
return DateTimeEntity(rawEntityData);
|
||||
}
|
||||
case "input_select": {
|
||||
return SelectEntity(rawEntityData, homeAssistantWebHost);
|
||||
return SelectEntity(rawEntityData);
|
||||
}
|
||||
case "input_number": {
|
||||
return SliderEntity(rawEntityData, homeAssistantWebHost);
|
||||
return SliderEntity(rawEntityData);
|
||||
}
|
||||
case "input_text": {
|
||||
return TextEntity(rawEntityData, homeAssistantWebHost);
|
||||
return TextEntity(rawEntityData);
|
||||
}
|
||||
case "climate": {
|
||||
return ClimateEntity(rawEntityData, homeAssistantWebHost);
|
||||
return ClimateEntity(rawEntityData);
|
||||
}
|
||||
case "cover": {
|
||||
return CoverEntity(rawEntityData, homeAssistantWebHost);
|
||||
return CoverEntity(rawEntityData);
|
||||
}
|
||||
case "fan": {
|
||||
return FanEntity(rawEntityData, homeAssistantWebHost);
|
||||
return FanEntity(rawEntityData);
|
||||
}
|
||||
case "camera": {
|
||||
return CameraEntity(rawEntityData, homeAssistantWebHost);
|
||||
return CameraEntity(rawEntityData);
|
||||
}
|
||||
case "alarm_control_panel": {
|
||||
return AlarmControlPanelEntity(rawEntityData, homeAssistantWebHost);
|
||||
return AlarmControlPanelEntity(rawEntityData);
|
||||
}
|
||||
case "timer": {
|
||||
return TimerEntity(rawEntityData, homeAssistantWebHost);
|
||||
}
|
||||
case "vacuum": {
|
||||
return VacuumEntity(rawEntityData, homeAssistantWebHost);
|
||||
return TimerEntity(rawEntityData);
|
||||
}
|
||||
default: {
|
||||
return Entity(rawEntityData, homeAssistantWebHost);
|
||||
return Entity(rawEntityData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -130,7 +121,7 @@ class EntityCollection {
|
||||
}
|
||||
|
||||
void updateFromRaw(Map rawEntityData) {
|
||||
get("${rawEntityData["entity_id"]}")?.update(rawEntityData, homeAssistantWebHost);
|
||||
get("${rawEntityData["entity_id"]}")?.update(rawEntityData);
|
||||
}
|
||||
|
||||
Entity get(String entityId) {
|
||||
@ -152,12 +143,32 @@ class EntityCollection {
|
||||
return _allEntities[entityId] != null;
|
||||
}
|
||||
|
||||
List<Entity> getByDomains({List<String> includeDomains: const [], List<String> excludeDomains: const [], List<String> stateFiler}) {
|
||||
return _allEntities.values.where((entity) {
|
||||
return
|
||||
(excludeDomains.isEmpty || !excludeDomains.contains(entity.domain)) &&
|
||||
(includeDomains.isEmpty || includeDomains.contains(entity.domain)) &&
|
||||
((stateFiler != null && stateFiler.contains(entity.state)) || stateFiler == null);
|
||||
}).toList();
|
||||
List<Entity> filterEntitiesForDefaultView() {
|
||||
List<Entity> result = [];
|
||||
List<Entity> groups = [];
|
||||
List<Entity> nonGroupEntities = [];
|
||||
_allEntities.forEach((id, entity){
|
||||
if (entity.isGroup && (entity.attributes['auto'] == null || (entity.attributes['auto'] && !entity.isHidden)) && (!entity.isView)) {
|
||||
groups.add(entity);
|
||||
}
|
||||
if (!entity.isGroup) {
|
||||
nonGroupEntities.add(entity);
|
||||
}
|
||||
});
|
||||
|
||||
nonGroupEntities.forEach((entity) {
|
||||
bool foundInGroup = false;
|
||||
groups.forEach((groupEntity) {
|
||||
if (groupEntity.childEntityIds.contains(entity.entityId)) {
|
||||
foundInGroup = true;
|
||||
}
|
||||
});
|
||||
if (!foundInGroup) {
|
||||
result.add(entity);
|
||||
}
|
||||
});
|
||||
result.insertAll(0, groups);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
50
lib/entity_widgets/button_entity_container.dart
Normal file
50
lib/entity_widgets/button_entity_container.dart
Normal file
@ -0,0 +1,50 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class ButtonEntityContainer extends StatelessWidget {
|
||||
|
||||
ButtonEntityContainer({
|
||||
Key key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||
if (entityWrapper.entity.statelessType == StatelessEntityType.MISSED) {
|
||||
return MissedEntityWidget();
|
||||
}
|
||||
if (entityWrapper.entity.statelessType > StatelessEntityType.MISSED) {
|
||||
return Container(width: 0.0, height: 0.0,);
|
||||
}
|
||||
return InkWell(
|
||||
onTap: () => entityWrapper.handleTap(),
|
||||
onLongPress: () => entityWrapper.handleHold(),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.4,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.fitHeight,
|
||||
child: EntityIcon(
|
||||
padding: EdgeInsets.fromLTRB(2.0, 6.0, 2.0, 2.0),
|
||||
size: Sizes.iconSize,
|
||||
)
|
||||
),
|
||||
),
|
||||
_buildName()
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildName() {
|
||||
return EntityName(
|
||||
padding: EdgeInsets.fromLTRB(Sizes.buttonPadding, 0.0, Sizes.buttonPadding, Sizes.rowPadding),
|
||||
textOverflow: TextOverflow.ellipsis,
|
||||
maxLines: 3,
|
||||
wordsWrap: true,
|
||||
textAlign: TextAlign.center,
|
||||
fontSize: Sizes.nameFontSize,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
part of '../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class BadgeWidget extends StatelessWidget {
|
||||
@override
|
||||
@ -7,7 +7,8 @@ class BadgeWidget extends StatelessWidget {
|
||||
double iconSize = 26.0;
|
||||
Widget badgeIcon;
|
||||
String onBadgeTextValue;
|
||||
Color iconColor = HAClientTheme().getBadgeColor(entityModel.entityWrapper.entity.domain);
|
||||
Color iconColor = EntityColor.badgeColors[entityModel.entityWrapper.entity.domain] ??
|
||||
EntityColor.badgeColors["default"];
|
||||
switch (entityModel.entityWrapper.entity.domain) {
|
||||
case "sun":
|
||||
{
|
||||
@ -29,43 +30,30 @@ class BadgeWidget extends StatelessWidget {
|
||||
badgeIcon = EntityIcon(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
size: iconSize,
|
||||
color: Theme.of(context).textTheme.body1.color
|
||||
color: Colors.black
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "device_tracker":
|
||||
case "person":
|
||||
{
|
||||
badgeIcon = EntityIcon(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
size: iconSize,
|
||||
color: Theme.of(context).textTheme.body1.color
|
||||
color: Colors.black
|
||||
);
|
||||
onBadgeTextValue = entityModel.entityWrapper.entity.displayState;
|
||||
onBadgeTextValue = entityModel.entityWrapper.entity.state;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
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;
|
||||
onBadgeTextValue = entityModel.entityWrapper.entity.unitOfMeasurement;
|
||||
badgeIcon = Center(
|
||||
child: Text(
|
||||
"${entityModel.entityWrapper.entity.displayState}",
|
||||
"${entityModel.entityWrapper.entity.state}",
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.body1.copyWith(
|
||||
fontSize: stateFontSize
|
||||
)
|
||||
style: TextStyle(fontSize: 17.0),
|
||||
),
|
||||
);
|
||||
break;
|
||||
@ -78,9 +66,7 @@ class BadgeWidget extends StatelessWidget {
|
||||
onBadgeText = Container(
|
||||
padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0),
|
||||
child: Text("$onBadgeTextValue",
|
||||
style: Theme.of(context).textTheme.overline.copyWith(
|
||||
color: HAClientTheme().getOnBadgeTextColor()
|
||||
),
|
||||
style: TextStyle(fontSize: 12.0, color: Colors.white),
|
||||
textAlign: TextAlign.center,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade),
|
||||
@ -101,7 +87,7 @@ class BadgeWidget extends StatelessWidget {
|
||||
decoration: new BoxDecoration(
|
||||
// Circle shape
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).cardColor,
|
||||
color: Colors.white,
|
||||
// The border you want
|
||||
border: new Border.all(
|
||||
width: 2.0,
|
||||
@ -134,7 +120,7 @@ class BadgeWidget extends StatelessWidget {
|
||||
child: Text(
|
||||
"${entityModel.entityWrapper.displayName}",
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
style: TextStyle(fontSize: 12.0),
|
||||
softWrap: true,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@ -143,6 +129,6 @@ class BadgeWidget extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
onTap: () =>
|
||||
eventBus.fire(new ShowEntityPageEvent(entity: entityModel.entityWrapper.entity)));
|
||||
eventBus.fire(new ShowEntityPageEvent(entityModel.entityWrapper.entity)));
|
||||
}
|
||||
}
|
175
lib/entity_widgets/common/camera_stream_view.dart
Normal file
175
lib/entity_widgets/common/camera_stream_view.dart
Normal file
@ -0,0 +1,175 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class CameraStreamView extends StatefulWidget {
|
||||
|
||||
CameraStreamView({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_CameraStreamViewState createState() => _CameraStreamViewState();
|
||||
}
|
||||
|
||||
class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
CameraEntity _entity;
|
||||
|
||||
http.Client client;
|
||||
http.StreamedResponse response;
|
||||
List<int> binaryImage = [];
|
||||
bool timeToStop = false;
|
||||
Completer streamCompleter;
|
||||
bool started = false;
|
||||
bool useSVG = false;
|
||||
|
||||
void _connect() async {
|
||||
started = true;
|
||||
timeToStop = false;
|
||||
String streamUrl = '$homeAssistantWebHost/api/camera_proxy_stream/${_entity.entityId}?token=${_entity.attributes['access_token']}';
|
||||
client = new http.Client(); // create a client to make api calls
|
||||
http.Request request = new http.Request("GET", Uri.parse(streamUrl)); // create get request
|
||||
Logger.d("[Sending] ==> $streamUrl");
|
||||
response = await client.send(request);
|
||||
Logger.d("[Received] <== ${response.headers}");
|
||||
String frameBoundary = response.headers['content-type'].split('boundary=')[1];
|
||||
final int frameBoundarySize = frameBoundary.length;
|
||||
List<int> primaryBuffer=[];
|
||||
int imageSizeStart = 59;
|
||||
int imageSizeEnd = 0;
|
||||
int imageStart = 0;
|
||||
int imageSize = 0;
|
||||
String strBuffer = "";
|
||||
String contentType = "";
|
||||
streamCompleter = Completer();
|
||||
response.stream.transform(
|
||||
StreamTransformer.fromHandlers(
|
||||
handleData: (data, sink) {
|
||||
primaryBuffer.addAll(data);
|
||||
imageStart = 0;
|
||||
imageSizeEnd = 0;
|
||||
if (primaryBuffer.length >= imageSizeStart + 10) {
|
||||
contentType = utf8.decode(
|
||||
primaryBuffer.sublist(frameBoundarySize+16, imageSizeStart + 10), allowMalformed: true).split("\r\n")[0];
|
||||
useSVG = contentType == "image/svg+xml";
|
||||
imageSizeStart = frameBoundarySize + 16 + contentType.length + 18;
|
||||
for (int i = imageSizeStart; i < primaryBuffer.length - 4; i++) {
|
||||
strBuffer = utf8.decode(
|
||||
primaryBuffer.sublist(i, i + 4), allowMalformed: true);
|
||||
if (strBuffer == "\r\n\r\n") {
|
||||
imageSizeEnd = i;
|
||||
imageStart = i + 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (imageSizeEnd > 0) {
|
||||
imageSize = int.tryParse(utf8.decode(
|
||||
primaryBuffer.sublist(imageSizeStart, imageSizeEnd),
|
||||
allowMalformed: true));
|
||||
//Logger.d("content-length: $imageSize");
|
||||
if (imageSize != null &&
|
||||
primaryBuffer.length >= imageStart + imageSize + 2) {
|
||||
sink.add(
|
||||
primaryBuffer.sublist(
|
||||
imageStart, imageStart + imageSize));
|
||||
primaryBuffer.removeRange(0, imageStart + imageSize + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (timeToStop) {
|
||||
sink?.close();
|
||||
streamCompleter.complete();
|
||||
}
|
||||
},
|
||||
handleError: (error, stack, sink) {
|
||||
Logger.e("Error parsing MJPEG stream: $error");
|
||||
},
|
||||
handleDone: (sink) {
|
||||
Logger.d("Camera stream finished. Reconnecting...");
|
||||
sink?.close();
|
||||
streamCompleter?.complete();
|
||||
_reconnect();
|
||||
},
|
||||
)
|
||||
).listen((d) {
|
||||
if (!timeToStop) {
|
||||
setState(() {
|
||||
binaryImage = d;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _reconnect() {
|
||||
disconnect().then((_){
|
||||
_connect();
|
||||
});
|
||||
}
|
||||
|
||||
Future disconnect() {
|
||||
Completer disconF = Completer();
|
||||
timeToStop = true;
|
||||
if (streamCompleter != null && !streamCompleter.isCompleted) {
|
||||
streamCompleter.future.then((_) {
|
||||
client?.close();
|
||||
disconF.complete();
|
||||
});
|
||||
} else {
|
||||
client?.close();
|
||||
disconF.complete();
|
||||
}
|
||||
return disconF.future;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!started) {
|
||||
_entity = EntityModel
|
||||
.of(context)
|
||||
.entityWrapper
|
||||
.entity;
|
||||
_connect();
|
||||
}
|
||||
|
||||
if (binaryImage.isEmpty) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: const CircularProgressIndicator()
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
if (useSVG) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
SvgPicture.memory(
|
||||
Uint8List.fromList(binaryImage),
|
||||
placeholderBuilder: (BuildContext context) =>
|
||||
new Container(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: const CircularProgressIndicator()
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Image.memory(
|
||||
Uint8List.fromList(binaryImage), gaplessPlayback: true),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
disconnect();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
part of '../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class FlatServiceButton extends StatelessWidget {
|
||||
|
||||
@ -6,6 +6,7 @@ class FlatServiceButton extends StatelessWidget {
|
||||
final String serviceName;
|
||||
final String entityId;
|
||||
final String text;
|
||||
final double fontSize;
|
||||
|
||||
FlatServiceButton({
|
||||
Key key,
|
||||
@ -13,16 +14,17 @@ class FlatServiceButton extends StatelessWidget {
|
||||
@required this.serviceName,
|
||||
@required this.entityId,
|
||||
@required this.text,
|
||||
this.fontSize: Sizes.stateFontSize
|
||||
}) : super(key: key);
|
||||
|
||||
void _setNewState() {
|
||||
ConnectionManager().callService(domain: serviceDomain, service: serviceName, entityId: entityId);
|
||||
eventBus.fire(new ServiceCallEvent(serviceDomain, serviceName, entityId, null));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: Theme.of(context).textTheme.subhead.fontSize*2.5,
|
||||
height: fontSize*2.5,
|
||||
child: FlatButton(
|
||||
onPressed: (() {
|
||||
_setNewState();
|
||||
@ -30,7 +32,8 @@ class FlatServiceButton extends StatelessWidget {
|
||||
child: Text(
|
||||
text,
|
||||
textAlign: TextAlign.right,
|
||||
style: HAClientTheme().getActionTextStyle(context),
|
||||
style:
|
||||
new TextStyle(fontSize: fontSize, color: Colors.blue),
|
||||
),
|
||||
)
|
||||
);
|
@ -10,7 +10,8 @@ class LastUpdatedWidget extends StatelessWidget {
|
||||
child: Text(
|
||||
'${entityModel.entityWrapper.entity.lastUpdated}',
|
||||
textAlign: TextAlign.left,
|
||||
style: Theme.of(context).textTheme.caption
|
||||
style: TextStyle(
|
||||
fontSize: Sizes.smallFontSize, color: Colors.black26),
|
||||
),
|
||||
);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
part of '../../../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class LightColorPicker extends StatefulWidget {
|
||||
|
||||
@ -27,6 +27,7 @@ class LightColorPickerState extends State<LightColorPicker> {
|
||||
List<Widget> colorRows = [];
|
||||
Border border;
|
||||
bool isSomethingSelected = false;
|
||||
Logger.d("Current colotfor picker: [${widget.color.hue}, ${widget.color.saturation}]");
|
||||
for (double saturation = 1.0; saturation >= (0.0 + widget.saturationStep); saturation = double.parse((saturation - widget.saturationStep).toStringAsFixed(2))) {
|
||||
List<Widget> rowChildren = [];
|
||||
//Logger.d("$saturation");
|
@ -1,10 +1,12 @@
|
||||
part of '../../../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class ModeSelectorWidget extends StatelessWidget {
|
||||
|
||||
final String caption;
|
||||
final List options;
|
||||
final List<String> options;
|
||||
final String value;
|
||||
final double captionFontSize;
|
||||
final double valueFontSize;
|
||||
final onChange;
|
||||
final EdgeInsets padding;
|
||||
|
||||
@ -14,6 +16,8 @@ class ModeSelectorWidget extends StatelessWidget {
|
||||
@required this.options,
|
||||
this.value,
|
||||
@required this.onChange,
|
||||
this.captionFontSize,
|
||||
this.valueFontSize,
|
||||
this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
|
||||
}) : super(key: key);
|
||||
|
||||
@ -24,7 +28,9 @@ class ModeSelectorWidget extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("$caption", style: Theme.of(context).textTheme.body1),
|
||||
Text("$caption", style: TextStyle(
|
||||
fontSize: captionFontSize ?? Sizes.stateFontSize
|
||||
)),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
@ -34,12 +40,15 @@ class ModeSelectorWidget extends StatelessWidget {
|
||||
value: value,
|
||||
iconSize: 30.0,
|
||||
isExpanded: true,
|
||||
style: Theme.of(context).textTheme.title,
|
||||
style: TextStyle(
|
||||
fontSize: valueFontSize ?? Sizes.largeFontSize,
|
||||
color: Colors.black,
|
||||
),
|
||||
hint: Text("Select ${caption.toLowerCase()}"),
|
||||
items: options.map((value) {
|
||||
items: options.map((String value) {
|
||||
return new DropdownMenuItem<String>(
|
||||
value: '$value',
|
||||
child: Text('$value'),
|
||||
value: value,
|
||||
child: Text(value),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (mode) => onChange(mode),
|
@ -1,9 +1,10 @@
|
||||
part of '../../../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class ModeSwitchWidget extends StatelessWidget {
|
||||
|
||||
final String caption;
|
||||
final onChange;
|
||||
final double captionFontSize;
|
||||
final bool value;
|
||||
final bool expanded;
|
||||
final EdgeInsets padding;
|
||||
@ -12,6 +13,7 @@ class ModeSwitchWidget extends StatelessWidget {
|
||||
Key key,
|
||||
@required this.caption,
|
||||
@required this.onChange,
|
||||
this.captionFontSize,
|
||||
this.value,
|
||||
this.expanded: true,
|
||||
this.padding: const EdgeInsets.only(left: Sizes.leftWidgetPadding, right: Sizes.rightWidgetPadding)
|
||||
@ -23,7 +25,7 @@ class ModeSwitchWidget extends StatelessWidget {
|
||||
padding: this.padding,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
_buildCaption(context),
|
||||
_buildCaption(),
|
||||
Switch(
|
||||
onChanged: (value) => onChange(value),
|
||||
value: value ?? false,
|
||||
@ -33,10 +35,12 @@ class ModeSwitchWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCaption(BuildContext context) {
|
||||
Widget _buildCaption() {
|
||||
Widget captionWidget = Text(
|
||||
"$caption",
|
||||
style: Theme.of(context).textTheme.body1,
|
||||
style: TextStyle(
|
||||
fontSize: captionFontSize ?? Sizes.stateFontSize
|
||||
),
|
||||
);
|
||||
if (expanded) {
|
||||
return Expanded(
|
@ -1,4 +1,4 @@
|
||||
part of '../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class UniversalSlider extends StatelessWidget {
|
||||
|
||||
@ -40,7 +40,10 @@ class UniversalSlider extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(height: Sizes.rowPadding,),
|
||||
Text("$title"),
|
||||
Text(
|
||||
"$title",
|
||||
style: TextStyle(fontSize: Sizes.stateFontSize),
|
||||
),
|
||||
Container(height: Sizes.rowPadding,),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
@ -1,4 +1,4 @@
|
||||
part of '../../../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class AlarmControlPanelControlsWidget extends StatefulWidget {
|
||||
|
||||
@ -25,12 +25,9 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
|
||||
|
||||
|
||||
void _callService(AlarmControlPanelEntity entity, String service) {
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain,
|
||||
service: service,
|
||||
entityId: entity.entityId,
|
||||
data: {"code": "$code"}
|
||||
);
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, service, entity.entityId,
|
||||
{"code": "$code"}));
|
||||
setState(() {
|
||||
code = "";
|
||||
});
|
||||
@ -61,11 +58,7 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
|
||||
FlatButton(
|
||||
child: new Text("Yes"),
|
||||
onPressed: () {
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain,
|
||||
service: "alarm_trigger",
|
||||
entityId: entity.entityId
|
||||
);
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "alarm_trigger", entity.entityId, null));
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
@ -248,9 +241,7 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
|
||||
FlatButton(
|
||||
child: Text(
|
||||
"TRIGGER",
|
||||
style: Theme.of(context).textTheme.subhead.copyWith(
|
||||
color: Theme.of(context).errorColor
|
||||
)
|
||||
style: TextStyle(color: Colors.redAccent)
|
||||
),
|
||||
onPressed: () => _askToTrigger(entity),
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
part of '../../../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class ClimateControlWidget extends StatefulWidget {
|
||||
|
||||
@ -10,35 +10,35 @@ class ClimateControlWidget extends StatefulWidget {
|
||||
|
||||
class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
|
||||
bool _temperaturePending = false;
|
||||
bool _showPending = false;
|
||||
bool _changedHere = false;
|
||||
Timer _resetTimer;
|
||||
Timer _tempThrottleTimer;
|
||||
Timer _targetTempThrottleTimer;
|
||||
double _tmpTemperature = 0.0;
|
||||
double _tmpTargetLow = 0.0;
|
||||
double _tmpTargetHigh = 0.0;
|
||||
double _tmpTargetHumidity = 0.0;
|
||||
String _tmpHVACMode;
|
||||
String _tmpOperationMode;
|
||||
String _tmpFanMode;
|
||||
String _tmpSwingMode;
|
||||
String _tmpPresetMode;
|
||||
//bool _tmpIsOff = false;
|
||||
bool _tmpAwayMode = false;
|
||||
bool _tmpIsOff = false;
|
||||
bool _tmpAuxHeat = false;
|
||||
|
||||
void _resetVars(ClimateEntity entity) {
|
||||
if (!_temperaturePending) {
|
||||
_tmpTemperature = entity.temperature;
|
||||
_tmpTargetHigh = entity.targetHigh;
|
||||
_tmpTargetLow = entity.targetLow;
|
||||
}
|
||||
_tmpHVACMode = entity.state;
|
||||
_tmpOperationMode = entity.operationMode;
|
||||
_tmpFanMode = entity.fanMode;
|
||||
_tmpSwingMode = entity.swingMode;
|
||||
_tmpPresetMode = entity.presetMode;
|
||||
//_tmpIsOff = entity.isOff;
|
||||
_tmpAwayMode = entity.awayMode;
|
||||
_tmpIsOff = entity.isOff;
|
||||
_tmpAuxHeat = entity.auxHeat;
|
||||
_tmpTargetHumidity = entity.targetHumidity;
|
||||
|
||||
_showPending = false;
|
||||
_changedHere = false;
|
||||
}
|
||||
|
||||
@ -73,44 +73,36 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
}
|
||||
|
||||
void _setTemperature(ClimateEntity entity) {
|
||||
_tempThrottleTimer?.cancel();
|
||||
if (_tempThrottleTimer!=null) {
|
||||
_tempThrottleTimer.cancel();
|
||||
}
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_temperaturePending = true;
|
||||
_tmpTemperature = double.parse(_tmpTemperature.toStringAsFixed(1));
|
||||
});
|
||||
_tempThrottleTimer = Timer(Duration(seconds: 2), () {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_temperaturePending = false;
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain,
|
||||
service: "set_temperature",
|
||||
entityId: entity.entityId,
|
||||
data: {"temperature": "${_tmpTemperature.toStringAsFixed(1)}"}
|
||||
);
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"temperature": "${_tmpTemperature.toStringAsFixed(1)}"}));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _setTargetTemp(ClimateEntity entity) {
|
||||
_targetTempThrottleTimer?.cancel();
|
||||
if (_targetTempThrottleTimer!=null) {
|
||||
_targetTempThrottleTimer.cancel();
|
||||
}
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_temperaturePending = true;
|
||||
_tmpTargetLow = double.parse(_tmpTargetLow.toStringAsFixed(1));
|
||||
_tmpTargetHigh = double.parse(_tmpTargetHigh.toStringAsFixed(1));
|
||||
});
|
||||
_targetTempThrottleTimer = Timer(Duration(seconds: 2), () {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_temperaturePending = false;
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain,
|
||||
service: "set_temperature",
|
||||
entityId: entity.entityId,
|
||||
data: {"target_temp_high": "${_tmpTargetHigh.toStringAsFixed(1)}", "target_temp_low": "${_tmpTargetLow.toStringAsFixed(1)}"}
|
||||
);
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"target_temp_high": "${_tmpTargetHigh.toStringAsFixed(1)}", "target_temp_low": "${_tmpTargetLow.toStringAsFixed(1)}"}));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -119,25 +111,17 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
setState(() {
|
||||
_tmpTargetHumidity = value.roundToDouble();
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain,
|
||||
service: "set_humidity",
|
||||
entityId: entity.entityId,
|
||||
data: {"humidity": "$_tmpTargetHumidity"}
|
||||
);
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_humidity", entity.entityId,{"humidity": "$_tmpTargetHumidity"}));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
}
|
||||
|
||||
void _setHVACMode(ClimateEntity entity, value) {
|
||||
void _setOperationMode(ClimateEntity entity, value) {
|
||||
setState(() {
|
||||
_tmpHVACMode = value;
|
||||
_tmpOperationMode = value;
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain,
|
||||
service: "set_hvac_mode",
|
||||
entityId: entity.entityId,
|
||||
data: {"hvac_mode": "$_tmpHVACMode"}
|
||||
);
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_operation_mode", entity.entityId,{"operation_mode": "$_tmpOperationMode"}));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
}
|
||||
|
||||
@ -145,12 +129,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
setState(() {
|
||||
_tmpSwingMode = value;
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain,
|
||||
service: "set_swing_mode",
|
||||
entityId: entity.entityId,
|
||||
data: {"swing_mode": "$_tmpSwingMode"}
|
||||
);
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_swing_mode", entity.entityId,{"swing_mode": "$_tmpSwingMode"}));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
}
|
||||
|
||||
@ -158,32 +138,45 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
setState(() {
|
||||
_tmpFanMode = value;
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(domain: entity.domain, service: "set_fan_mode", entityId: entity.entityId, data: {"fan_mode": "$_tmpFanMode"});
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_fan_mode", entity.entityId,{"fan_mode": "$_tmpFanMode"}));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
}
|
||||
|
||||
void _setPresetMode(ClimateEntity entity, value) {
|
||||
void _setAwayMode(ClimateEntity entity, value) {
|
||||
setState(() {
|
||||
_tmpPresetMode = value;
|
||||
_tmpAwayMode = value;
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(domain: entity.domain, service: "set_preset_mode", entityId: entity.entityId, data: {"preset_mode": "$_tmpPresetMode"});
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_away_mode", entity.entityId,{"away_mode": "${_tmpAwayMode ? 'on' : 'off'}"}));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
}
|
||||
|
||||
/*void _setOnOf(ClimateEntity entity, value) {
|
||||
void _setOnOf(ClimateEntity entity, value) {
|
||||
setState(() {
|
||||
_tmpIsOff = !value;
|
||||
_changedHere = true;
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "${_tmpIsOff ? 'turn_off' : 'turn_on'}", entity.entityId, null));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
}*/
|
||||
}
|
||||
|
||||
void _setAuxHeat(ClimateEntity entity, value) {
|
||||
setState(() {
|
||||
_tmpAuxHeat = value;
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(domain: entity.domain, service: "set_aux_heat", entityId: entity.entityId, data: {"aux_heat": "$_tmpAuxHeat"});
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_aux_heat", entity.entityId, {"aux_heat": "$_tmpAuxHeat"}));
|
||||
_resetStateTimer(entity);
|
||||
});
|
||||
}
|
||||
|
||||
void _resetStateTimer(ClimateEntity entity) {
|
||||
if (_resetTimer!=null) {
|
||||
_resetTimer.cancel();
|
||||
}
|
||||
_resetTimer = Timer(Duration(seconds: 3), () {
|
||||
setState(() {});
|
||||
_resetVars(entity);
|
||||
});
|
||||
}
|
||||
|
||||
@ -191,11 +184,11 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
Widget build(BuildContext context) {
|
||||
final entityModel = EntityModel.of(context);
|
||||
final ClimateEntity entity = entityModel.entityWrapper.entity;
|
||||
Logger.d("[Climate widget build] changed here = $_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;
|
||||
} else {
|
||||
_resetTimer?.cancel();
|
||||
_resetVars(entity);
|
||||
}
|
||||
return Padding(
|
||||
@ -203,34 +196,33 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
//_buildOnOffControl(entity),
|
||||
_buildTemperatureControls(entity, context),
|
||||
_buildTargetTemperatureControls(entity, context),
|
||||
_buildHumidityControls(entity, context),
|
||||
_buildOperationControl(entity, context),
|
||||
_buildFanControl(entity, context),
|
||||
_buildSwingControl(entity, context),
|
||||
_buildPresetModeControl(entity, context),
|
||||
_buildAuxHeatControl(entity, context)
|
||||
_buildOnOffControl(entity),
|
||||
_buildTemperatureControls(entity),
|
||||
_buildTargetTemperatureControls(entity),
|
||||
_buildHumidityControls(entity),
|
||||
_buildOperationControl(entity),
|
||||
_buildFanControl(entity),
|
||||
_buildSwingControl(entity),
|
||||
_buildAwayModeControl(entity),
|
||||
_buildAuxHeatControl(entity)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPresetModeControl(ClimateEntity entity, BuildContext context) {
|
||||
if (entity.supportPresetMode) {
|
||||
return ModeSelectorWidget(
|
||||
options: entity.presetModes,
|
||||
onChange: (mode) => _setPresetMode(entity, mode),
|
||||
caption: "Preset",
|
||||
value: _tmpPresetMode,
|
||||
Widget _buildAwayModeControl(ClimateEntity entity) {
|
||||
if (entity.supportAwayMode) {
|
||||
return ModeSwitchWidget(
|
||||
caption: "Away mode",
|
||||
onChange: (value) => _setAwayMode(entity, value),
|
||||
value: _tmpAwayMode,
|
||||
);
|
||||
} else {
|
||||
return Container(height: 0.0, width: 0.0,);
|
||||
}
|
||||
}
|
||||
|
||||
/*Widget _buildOnOffControl(ClimateEntity entity) {
|
||||
Widget _buildOnOffControl(ClimateEntity entity) {
|
||||
if (entity.supportOnOff) {
|
||||
return ModeSwitchWidget(
|
||||
onChange: (value) => _setOnOf(entity, value),
|
||||
@ -240,9 +232,9 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
} else {
|
||||
return Container(height: 0.0, width: 0.0,);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
Widget _buildAuxHeatControl(ClimateEntity entity, BuildContext context) {
|
||||
Widget _buildAuxHeatControl(ClimateEntity entity) {
|
||||
if (entity.supportAuxHeat ) {
|
||||
return ModeSwitchWidget(
|
||||
caption: "Aux heat",
|
||||
@ -254,23 +246,23 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildOperationControl(ClimateEntity entity, BuildContext context) {
|
||||
if (entity.hvacModes != null) {
|
||||
Widget _buildOperationControl(ClimateEntity entity) {
|
||||
if (entity.supportOperationMode) {
|
||||
return ModeSelectorWidget(
|
||||
onChange: (mode) => _setHVACMode(entity, mode),
|
||||
options: entity.hvacModes,
|
||||
onChange: (mode) => _setOperationMode(entity, mode),
|
||||
options: entity.operationList,
|
||||
caption: "Operation",
|
||||
value: _tmpHVACMode,
|
||||
value: _tmpOperationMode,
|
||||
);
|
||||
} else {
|
||||
return Container(height: 0.0, width: 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildFanControl(ClimateEntity entity, BuildContext context) {
|
||||
Widget _buildFanControl(ClimateEntity entity) {
|
||||
if (entity.supportFanMode) {
|
||||
return ModeSelectorWidget(
|
||||
options: entity.fanModes,
|
||||
options: entity.fanList,
|
||||
onChange: (mode) => _setFanMode(entity, mode),
|
||||
caption: "Fan mode",
|
||||
value: _tmpFanMode,
|
||||
@ -280,11 +272,11 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildSwingControl(ClimateEntity entity, BuildContext context) {
|
||||
Widget _buildSwingControl(ClimateEntity entity) {
|
||||
if (entity.supportSwingMode) {
|
||||
return ModeSelectorWidget(
|
||||
onChange: (mode) => _setSwingMode(entity, mode),
|
||||
options: entity.swingModes,
|
||||
options: entity.swingList,
|
||||
value: _tmpSwingMode,
|
||||
caption: "Swing mode"
|
||||
);
|
||||
@ -293,15 +285,17 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTemperatureControls(ClimateEntity entity, BuildContext context) {
|
||||
Widget _buildTemperatureControls(ClimateEntity entity) {
|
||||
if ((entity.supportTargetTemperature) && (entity.temperature != null)) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("Target temperature", style: Theme.of(context).textTheme.body1),
|
||||
Text("Target temperature", style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
TemperatureControlWidget(
|
||||
value: _tmpTemperature,
|
||||
active: _temperaturePending,
|
||||
fontColor: _showPending ? Colors.red : Colors.black,
|
||||
onDec: () => _temperatureDown(entity),
|
||||
onInc: () => _temperatureUp(entity),
|
||||
)
|
||||
@ -312,13 +306,13 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTargetTemperatureControls(ClimateEntity entity, BuildContext context) {
|
||||
Widget _buildTargetTemperatureControls(ClimateEntity entity) {
|
||||
List<Widget> controls = [];
|
||||
if ((entity.supportTargetTemperatureRange) && (entity.targetLow != null)) {
|
||||
if ((entity.supportTargetTemperatureLow) && (entity.targetLow != null)) {
|
||||
controls.addAll(<Widget>[
|
||||
TemperatureControlWidget(
|
||||
value: _tmpTargetLow,
|
||||
active: _temperaturePending,
|
||||
fontColor: _showPending ? Colors.red : Colors.black,
|
||||
onDec: () => _targetLowDown(entity),
|
||||
onInc: () => _targetLowUp(entity),
|
||||
),
|
||||
@ -327,11 +321,11 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
)
|
||||
]);
|
||||
}
|
||||
if ((entity.supportTargetTemperatureRange) && (entity.targetHigh != null)) {
|
||||
if ((entity.supportTargetTemperatureHigh) && (entity.targetHigh != null)) {
|
||||
controls.add(
|
||||
TemperatureControlWidget(
|
||||
value: _tmpTargetHigh,
|
||||
active: _temperaturePending,
|
||||
fontColor: _showPending ? Colors.red : Colors.black,
|
||||
onDec: () => _targetHighDown(entity),
|
||||
onInc: () => _targetHighUp(entity),
|
||||
)
|
||||
@ -341,7 +335,9 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("Target temperature range", style: Theme.of(context).textTheme.body1),
|
||||
Text("Target temperature range", style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
Row(
|
||||
children: controls,
|
||||
)
|
||||
@ -352,13 +348,13 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildHumidityControls(ClimateEntity entity, BuildContext context) {
|
||||
Widget _buildHumidityControls(ClimateEntity entity) {
|
||||
List<Widget> result = [];
|
||||
if (entity.supportTargetHumidity) {
|
||||
result.addAll(<Widget>[
|
||||
Text(
|
||||
"$_tmpTargetHumidity%",
|
||||
style: Theme.of(context).textTheme.display1,
|
||||
style: TextStyle(fontSize: Sizes.largeFontSize),
|
||||
),
|
||||
Expanded(
|
||||
child: Slider(
|
||||
@ -383,7 +379,9 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||
child: Text("Target humidity", style: Theme.of(context).textTheme.body1),
|
||||
child: Text("Target humidity", style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
@ -405,7 +403,57 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_resetTimer?.cancel();
|
||||
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 CoverControlWidget extends StatefulWidget {
|
||||
|
||||
@ -18,7 +18,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
||||
setState(() {
|
||||
_tmpPosition = position.roundToDouble();
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(domain: entity.domain, service: "set_cover_position", entityId: entity.entityId, data: {"position": _tmpPosition.round()});
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_cover_position", entity.entityId,{"position": _tmpPosition.round()}));
|
||||
});
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
||||
setState(() {
|
||||
_tmpTiltPosition = position.roundToDouble();
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(domain: entity.domain, service: "set_cover_tilt_position", entityId: entity.entityId, data: {"tilt_position": _tmpTiltPosition.round()});
|
||||
eventBus.fire(new ServiceCallEvent(entity.domain, "set_cover_tilt_position", entity.entityId,{"tilt_position": _tmpTiltPosition.round()}));
|
||||
});
|
||||
}
|
||||
|
||||
@ -64,7 +64,9 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||
child: Text("Position"),
|
||||
child: Text("Position", style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
),
|
||||
Slider(
|
||||
value: _tmpPosition,
|
||||
@ -116,7 +118,9 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
||||
controls.insert(0, Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||
child: Text("Tilt position"),
|
||||
child: Text("Tilt position", style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
));
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -131,18 +135,18 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
||||
|
||||
class CoverTiltControlsWidget extends StatelessWidget {
|
||||
void _open(CoverEntity entity) {
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain, service: "open_cover_tilt", entityId: entity.entityId);
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "open_cover_tilt", entity.entityId, null));
|
||||
}
|
||||
|
||||
void _close(CoverEntity entity) {
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain, service: "close_cover_tilt", entityId: entity.entityId);
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "close_cover_tilt", entity.entityId, null));
|
||||
}
|
||||
|
||||
void _stop(CoverEntity entity) {
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain, service: "stop_cover_tilt", entityId: entity.entityId);
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "stop_cover_tilt", entity.entityId, null));
|
||||
}
|
||||
|
||||
@override
|
@ -1,4 +1,4 @@
|
||||
part of '../../../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class FanControlsWidget extends StatefulWidget {
|
||||
|
||||
@ -24,12 +24,9 @@ class _FanControlsWidgetState extends State<FanControlsWidget> {
|
||||
setState(() {
|
||||
_tmpOscillate = oscillate;
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(
|
||||
domain: "fan",
|
||||
service: "oscillate",
|
||||
entityId: entity.entityId,
|
||||
data: {"oscillating": oscillate}
|
||||
);
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
"fan", "oscillate", entity.entityId,
|
||||
{"oscillating": oscillate}));
|
||||
});
|
||||
}
|
||||
|
||||
@ -37,12 +34,9 @@ class _FanControlsWidgetState extends State<FanControlsWidget> {
|
||||
setState(() {
|
||||
_tmpDirectionForward = forward;
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(
|
||||
domain: "fan",
|
||||
service: "set_direction",
|
||||
entityId: entity.entityId,
|
||||
data: {"direction": forward ? "forward" : "reverse"}
|
||||
);
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
"fan", "set_direction", entity.entityId,
|
||||
{"direction": forward ? "forward" : "reverse"}));
|
||||
});
|
||||
}
|
||||
|
||||
@ -50,12 +44,9 @@ class _FanControlsWidgetState extends State<FanControlsWidget> {
|
||||
setState(() {
|
||||
_tmpSpeed = value;
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(
|
||||
domain: "fan",
|
||||
service: "set_speed",
|
||||
entityId: entity.entityId,
|
||||
data: {"speed": value}
|
||||
);
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
"fan", "set_speed", entity.entityId,
|
||||
{"speed": value}));
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
part of '../../../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class LightControlsWidget extends StatefulWidget {
|
||||
|
||||
@ -17,7 +17,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
String _tmpEffect;
|
||||
|
||||
void _resetState(LightEntity entity) {
|
||||
_tmpBrightness = entity.brightness ?? 1;
|
||||
_tmpBrightness = entity.brightness ?? 0;
|
||||
_tmpWhiteValue = entity.whiteValue ?? 0;
|
||||
_tmpColorTemp = entity.colorTemp ?? entity.minMireds?.toInt();
|
||||
_tmpColor = entity.color ?? _tmpColor;
|
||||
@ -28,12 +28,15 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
setState(() {
|
||||
_tmpBrightness = value.round();
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain,
|
||||
service: "turn_on",
|
||||
entityId: entity.entityId,
|
||||
data: {"brightness": _tmpBrightness}
|
||||
);
|
||||
if (_tmpBrightness > 0) {
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_on", entity.entityId,
|
||||
{"brightness": _tmpBrightness}));
|
||||
} else {
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_off", entity.entityId,
|
||||
null));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -41,12 +44,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
setState(() {
|
||||
_tmpWhiteValue = value.round();
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain,
|
||||
service: "turn_on",
|
||||
entityId: entity.entityId,
|
||||
data: {"white_value": _tmpWhiteValue}
|
||||
);
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_on", entity.entityId,
|
||||
{"white_value": _tmpWhiteValue}));
|
||||
|
||||
});
|
||||
}
|
||||
@ -55,12 +55,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
setState(() {
|
||||
_tmpColorTemp = value.round();
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain,
|
||||
service: "turn_on",
|
||||
entityId: entity.entityId,
|
||||
data: {"color_temp": _tmpColorTemp}
|
||||
);
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_on", entity.entityId,
|
||||
{"color_temp": _tmpColorTemp}));
|
||||
});
|
||||
}
|
||||
|
||||
@ -68,12 +65,10 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
setState(() {
|
||||
_tmpColor = color;
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain,
|
||||
service: "turn_on",
|
||||
entityId: entity.entityId,
|
||||
data: {"hs_color": [color.hue, color.saturation*100]}
|
||||
);
|
||||
Logger.d( "HS Color: [${color.hue}, ${color.saturation}]");
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_on", entity.entityId,
|
||||
{"hs_color": [color.hue, color.saturation*100]}));
|
||||
});
|
||||
}
|
||||
|
||||
@ -82,12 +77,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
_tmpEffect = value;
|
||||
_changedHere = true;
|
||||
if (_tmpEffect != null) {
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain,
|
||||
service: "turn_on",
|
||||
entityId: entity.entityId,
|
||||
data: {"effect": "$value"}
|
||||
);
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_on", entity.entityId,
|
||||
{"effect": "$value"}));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -114,19 +106,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
}
|
||||
|
||||
Widget _buildBrightnessControl(LightEntity entity) {
|
||||
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;
|
||||
}
|
||||
if ((entity.supportBrightness) && (_tmpBrightness != null)) {
|
||||
return UniversalSlider(
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
@ -134,10 +114,10 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
_tmpBrightness = value.round();
|
||||
});
|
||||
},
|
||||
min: 1.0,
|
||||
min: 0.0,
|
||||
max: 255.0,
|
||||
onChangeEnd: (value) => _setBrightness(entity, value),
|
||||
value: val,
|
||||
value: _tmpBrightness == null ? 0.0 : _tmpBrightness.toDouble(),
|
||||
leading: Icon(Icons.brightness_5),
|
||||
title: "Brightness",
|
||||
);
|
||||
@ -169,22 +149,10 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
|
||||
Widget _buildColorTempControl(LightEntity entity) {
|
||||
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(
|
||||
title: "Color temperature",
|
||||
leading: Text("Cold", style: Theme.of(context).textTheme.body1.copyWith(color: Colors.lightBlue)),
|
||||
value: val,
|
||||
leading: Text("Cold", style: TextStyle(color: Colors.lightBlue),),
|
||||
value: _tmpColorTemp == null ? entity.maxMireds : _tmpColorTemp.toDouble(),
|
||||
onChangeEnd: (value) => _setColorTemp(entity, value),
|
||||
max: entity.maxMireds,
|
||||
min: entity.minMireds,
|
||||
@ -194,7 +162,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
_tmpColorTemp = value.round();
|
||||
});
|
||||
},
|
||||
closing: Text("Warm", style: Theme.of(context).textTheme.body1.copyWith(color: Colors.amberAccent),),
|
||||
closing: Text("Warm", style: TextStyle(color: Colors.amberAccent),),
|
||||
);
|
||||
} else {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
@ -203,7 +171,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
|
||||
Widget _buildColorControl(LightEntity entity) {
|
||||
if (entity.supportColor) {
|
||||
HSVColor savedColor = HomeAssistant().savedColor;
|
||||
HSVColor savedColor = HomeAssistantModel.of(context)?.homeAssistant?.savedColor;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
@ -219,12 +187,15 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
child: Text('Copy color'),
|
||||
onPressed: _tmpColor == null ? null : () {
|
||||
setState(() {
|
||||
HomeAssistant().savedColor = _tmpColor;
|
||||
HomeAssistantModel
|
||||
.of(context)
|
||||
.homeAssistant
|
||||
.savedColor = _tmpColor;
|
||||
});
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
color: savedColor?.toColor() ?? Theme.of(context).backgroundColor,
|
||||
color: savedColor?.toColor() ?? Colors.transparent,
|
||||
child: Text('Paste color'),
|
||||
onPressed: savedColor == null ? null : () {
|
||||
_setColor(entity, savedColor);
|
||||
@ -241,14 +212,10 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
|
||||
Widget _buildEffectControl(LightEntity entity) {
|
||||
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(
|
||||
onChange: (effect) => _setEffect(entity, effect),
|
||||
caption: "Effect",
|
||||
options: list,
|
||||
options: entity.effectList,
|
||||
value: _tmpEffect
|
||||
);
|
||||
} else {
|
@ -1,4 +1,4 @@
|
||||
part of '../../../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class MediaPlayerWidget extends StatelessWidget {
|
||||
|
||||
@ -12,21 +12,21 @@ class MediaPlayerWidget extends StatelessWidget {
|
||||
Stack(
|
||||
alignment: AlignmentDirectional.topEnd,
|
||||
children: <Widget>[
|
||||
_buildImage(entity, context),
|
||||
_buildImage(entity),
|
||||
Positioned(
|
||||
bottom: 0.0,
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
child: Container(
|
||||
color: Colors.black45,
|
||||
child: _buildState(entity, context),
|
||||
child: _buildState(entity),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0.0,
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
child: MediaPlayerProgressBar()
|
||||
child: MediaPlayerProgressWidget()
|
||||
)
|
||||
],
|
||||
),
|
||||
@ -35,9 +35,12 @@ class MediaPlayerWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildState(MediaPlayerEntity entity, BuildContext context) {
|
||||
TextStyle style = Theme.of(context).textTheme.body1.copyWith(
|
||||
color: Colors.white
|
||||
Widget _buildState(MediaPlayerEntity entity) {
|
||||
TextStyle style = TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.2
|
||||
);
|
||||
List<Widget> states = [];
|
||||
states.add(Text("${entity.displayName}", style: style));
|
||||
@ -68,9 +71,9 @@ class MediaPlayerWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildImage(MediaPlayerEntity entity, BuildContext context) {
|
||||
Widget _buildImage(MediaPlayerEntity entity) {
|
||||
String state = entity.state;
|
||||
if (entity.entityPicture != null && state != EntityState.off && state != EntityState.unavailable && state != EntityState.idle) {
|
||||
if (homeAssistantWebHost != null && entity.entityPicture != null && state != EntityState.off && state != EntityState.unavailable && state != EntityState.idle) {
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
child: Row(
|
||||
@ -94,7 +97,7 @@ class MediaPlayerWidget extends StatelessWidget {
|
||||
Icon(
|
||||
MaterialDesignIcons.getIconDataFromIconName("mdi:movie"),
|
||||
size: 150.0,
|
||||
color: HAClientTheme().getColorByEntityState("$state", context),
|
||||
color: EntityColor.stateColor("$state"),
|
||||
)
|
||||
],
|
||||
);
|
||||
@ -115,28 +118,26 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
|
||||
|
||||
|
||||
void _setPower(MediaPlayerEntity entity) {
|
||||
if (entity.state != EntityState.unavailable && entity.state != EntityState.unknown) {
|
||||
if (entity.state == EntityState.off) {
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain,
|
||||
service: "turn_on",
|
||||
entityId: entity.entityId
|
||||
);
|
||||
Logger.d("${entity.entityId} turn_on");
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_on", entity.entityId,
|
||||
null));
|
||||
} else {
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain,
|
||||
service: "turn_off",
|
||||
entityId: entity.entityId
|
||||
);
|
||||
Logger.d("${entity.entityId} turn_off");
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "turn_off", entity.entityId,
|
||||
null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _callAction(MediaPlayerEntity entity, String action) {
|
||||
Logger.d("${entity.entityId} $action");
|
||||
ConnectionManager().callService(
|
||||
domain: entity.domain,
|
||||
service: "$action",
|
||||
entityId: entity.entityId
|
||||
);
|
||||
eventBus.fire(new ServiceCallEvent(
|
||||
entity.domain, "$action", entity.entityId,
|
||||
null));
|
||||
}
|
||||
|
||||
@override
|
||||
@ -228,7 +229,7 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||
"mdi:dots-vertical")),
|
||||
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity: entity))
|
||||
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity))
|
||||
)
|
||||
);
|
||||
} else if (entity.supportStop && entity.state != EntityState.off && entity.state != EntityState.unavailable) {
|
||||
@ -263,50 +264,27 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
||||
setState(() {
|
||||
_changedHere = true;
|
||||
_newVolumeLevel = value;
|
||||
ConnectionManager().callService(
|
||||
domain: "media_player",
|
||||
service: "volume_set",
|
||||
entityId: entityId,
|
||||
data: {"volume_level": value}
|
||||
);
|
||||
eventBus.fire(ServiceCallEvent("media_player", "volume_set", entityId, {"volume_level": value}));
|
||||
});
|
||||
}
|
||||
|
||||
void _setVolumeMute(bool isMuted, String entityId) {
|
||||
ConnectionManager().callService(
|
||||
domain: "media_player",
|
||||
service: "volume_mute",
|
||||
entityId: entityId,
|
||||
data: {"is_volume_muted": isMuted}
|
||||
);
|
||||
eventBus.fire(ServiceCallEvent("media_player", "volume_mute", entityId, {"is_volume_muted": isMuted}));
|
||||
}
|
||||
|
||||
void _setVolumeUp(String entityId) {
|
||||
ConnectionManager().callService(
|
||||
domain: "media_player",
|
||||
service: "volume_up",
|
||||
entityId: entityId
|
||||
);
|
||||
eventBus.fire(ServiceCallEvent("media_player", "volume_up", entityId, null));
|
||||
}
|
||||
|
||||
void _setVolumeDown(String entityId) {
|
||||
ConnectionManager().callService(
|
||||
domain: "media_player",
|
||||
service: "volume_down",
|
||||
entityId: entityId
|
||||
);
|
||||
eventBus.fire(ServiceCallEvent("media_player", "volume_down", entityId, null));
|
||||
}
|
||||
|
||||
void _setSoundMode(String value, String entityId) {
|
||||
setState(() {
|
||||
_newSoundMode = value;
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(
|
||||
domain: "media_player",
|
||||
service: "select_sound_mode",
|
||||
entityId: entityId,
|
||||
data: {"sound_mode": "$value"}
|
||||
);
|
||||
eventBus.fire(ServiceCallEvent("media_player", "select_sound_mode", entityId, {"sound_mode": "$value"}));
|
||||
});
|
||||
}
|
||||
|
||||
@ -314,12 +292,7 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
||||
setState(() {
|
||||
_newSource = source;
|
||||
_changedHere = true;
|
||||
ConnectionManager().callService(
|
||||
domain: "media_player",
|
||||
service: "select_source",
|
||||
entityId: entityId,
|
||||
data: {"source": "$source"}
|
||||
);
|
||||
eventBus.fire(ServiceCallEvent("media_player", "select_source", entityId, {"source": "$source"}));
|
||||
});
|
||||
}
|
||||
|
||||
@ -332,11 +305,6 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
||||
)
|
||||
];
|
||||
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 volumeStepWidget;
|
||||
if (entity.supportVolumeMute || entity.attributes["is_volume_muted"] != null) {
|
||||
@ -353,13 +321,13 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
||||
volumeStepWidget = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:minus")),
|
||||
onPressed: () => _setVolumeDown(entity.entityId)
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:plus")),
|
||||
onPressed: () => _setVolumeUp(entity.entityId)
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:minus")),
|
||||
onPressed: () => _setVolumeDown(entity.entityId)
|
||||
)
|
||||
],
|
||||
);
|
||||
@ -430,47 +398,69 @@ 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(
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
void _duplicateTo(entity) {
|
||||
if (entity.canCalculateActualPosition()) {
|
||||
HomeAssistant().savedPlayerPosition = entity.getActualPosition().toInt();
|
||||
} else {
|
||||
HomeAssistant().savedPlayerPosition = 0;
|
||||
}
|
||||
Navigator.of(context).pushNamed("/play-media", arguments: {
|
||||
"url": entity.attributes["media_content_id"],
|
||||
"type": entity.attributes["media_content_type"]
|
||||
|
||||
class MediaPlayerProgressWidget extends StatefulWidget {
|
||||
@override
|
||||
_MediaPlayerProgressWidgetState createState() => _MediaPlayerProgressWidgetState();
|
||||
}
|
||||
|
||||
class _MediaPlayerProgressWidgetState extends State<MediaPlayerProgressWidget> {
|
||||
|
||||
Timer _timer;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final EntityModel entityModel = EntityModel.of(context);
|
||||
final MediaPlayerEntity entity = entityModel.entityWrapper.entity;
|
||||
double progress;
|
||||
try {
|
||||
DateTime lastUpdated = DateTime.parse(
|
||||
entity.attributes["media_position_updated_at"]).toLocal();
|
||||
Duration duration = Duration(seconds: entity._getIntAttributeValue("media_duration") ?? 1);
|
||||
Duration position = Duration(seconds: entity._getIntAttributeValue("media_position") ?? 0);
|
||||
int currentPosition = position.inSeconds;
|
||||
if (entity.state == EntityState.playing) {
|
||||
_timer?.cancel();
|
||||
_timer = Timer(Duration(seconds: 1), () {
|
||||
setState(() {
|
||||
});
|
||||
});
|
||||
int differenceInSeconds = DateTime
|
||||
.now()
|
||||
.difference(lastUpdated)
|
||||
.inSeconds;
|
||||
currentPosition = currentPosition + differenceInSeconds;
|
||||
} else {
|
||||
_timer?.cancel();
|
||||
}
|
||||
progress = currentPosition / duration.inSeconds;
|
||||
return LinearProgressIndicator(
|
||||
value: progress,
|
||||
backgroundColor: Colors.black45,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(EntityColor.stateColor(EntityState.on)),
|
||||
);
|
||||
} catch (e) {
|
||||
_timer?.cancel();
|
||||
progress = 0.0;
|
||||
}
|
||||
return LinearProgressIndicator(
|
||||
value: progress,
|
||||
backgroundColor: Colors.black45,
|
||||
);
|
||||
}
|
||||
|
||||
void _switchTo(entity) {
|
||||
HomeAssistant().sendFromPlayerId = entity.entityId;
|
||||
_duplicateTo(entity);
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
part of '../../../main.dart';
|
||||
part of '../../main.dart';
|
||||
|
||||
class SliderControlsWidget extends StatefulWidget {
|
||||
|
||||
@ -18,12 +18,8 @@ class _SliderControlsWidgetState extends State<SliderControlsWidget> {
|
||||
_newValue = newValue;
|
||||
_changedHere = true;
|
||||
});
|
||||
ConnectionManager().callService(
|
||||
domain: domain,
|
||||
service: "set_value",
|
||||
entityId: entityId,
|
||||
data: {"value": "${newValue.toString()}"}
|
||||
);
|
||||
eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId,
|
||||
{"value": "${newValue.toString()}"}));
|
||||
}
|
||||
|
||||
@override
|
||||
@ -62,7 +58,8 @@ class _SliderControlsWidgetState extends State<SliderControlsWidget> {
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"$_newValue",
|
||||
style: Theme.of(context).textTheme.display1.copyWith(
|
||||
style: TextStyle(
|
||||
fontSize: Sizes.largeFontSize,
|
||||
color: Colors.blue
|
||||
),
|
||||
),
|
61
lib/entity_widgets/default_entity_container.dart
Normal file
61
lib/entity_widgets/default_entity_container.dart
Normal file
@ -0,0 +1,61 @@
|
||||
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: TextStyle(color: Colors.blue),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
return InkWell(
|
||||
onLongPress: () {
|
||||
if (entityModel.handleTap) {
|
||||
entityModel.entityWrapper.handleHold();
|
||||
}
|
||||
},
|
||||
onTap: () {
|
||||
if (entityModel.handleTap) {
|
||||
entityModel.entityWrapper.handleTap();
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
EntityIcon(),
|
||||
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
flex: 3,
|
||||
child: EntityName(
|
||||
padding: EdgeInsets.fromLTRB(10.0, 2.0, 10.0, 2.0),
|
||||
),
|
||||
),
|
||||
state
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
74
lib/entity_widgets/entity_colors.class.dart
Normal file
74
lib/entity_widgets/entity_colors.class.dart
Normal file
@ -0,0 +1,74 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class EntityColor {
|
||||
|
||||
static const defaultStateColor = Color.fromRGBO(68, 115, 158, 1.0);
|
||||
|
||||
static const badgeColors = {
|
||||
"default": Color.fromRGBO(223, 76, 30, 1.0),
|
||||
"binary_sensor": Color.fromRGBO(3, 155, 229, 1.0)
|
||||
};
|
||||
|
||||
static const _stateColors = {
|
||||
EntityState.on: Colors.amber,
|
||||
"auto": Colors.amber,
|
||||
EntityState.active: Colors.amber,
|
||||
EntityState.playing: Colors.amber,
|
||||
"above_horizon": Colors.amber,
|
||||
EntityState.home: Colors.amber,
|
||||
EntityState.open: Colors.amber,
|
||||
EntityState.off: defaultStateColor,
|
||||
EntityState.closed: defaultStateColor,
|
||||
"below_horizon": defaultStateColor,
|
||||
"default": defaultStateColor,
|
||||
EntityState.idle: defaultStateColor,
|
||||
"heat": Colors.redAccent,
|
||||
"cool": Colors.lightBlue,
|
||||
EntityState.unavailable: Colors.black26,
|
||||
EntityState.unknown: Colors.black26,
|
||||
EntityState.alarm_disarmed: Colors.green,
|
||||
EntityState.alarm_armed_away: Colors.redAccent,
|
||||
EntityState.alarm_armed_custom_bypass: Colors.redAccent,
|
||||
EntityState.alarm_armed_home: Colors.redAccent,
|
||||
EntityState.alarm_armed_night: Colors.redAccent,
|
||||
EntityState.alarm_triggered: Colors.redAccent,
|
||||
EntityState.alarm_arming: Colors.amber,
|
||||
EntityState.alarm_disarming: Colors.amber,
|
||||
EntityState.alarm_pending: Colors.amber,
|
||||
};
|
||||
|
||||
static Color stateColor(String state) {
|
||||
return _stateColors[state] ?? _stateColors["default"];
|
||||
}
|
||||
|
||||
static charts.Color chartHistoryStateColor(String state, int id) {
|
||||
Color c = _stateColors[state];
|
||||
if (c != null) {
|
||||
return charts.Color(
|
||||
r: c.red,
|
||||
g: c.green,
|
||||
b: c.blue,
|
||||
a: c.alpha
|
||||
);
|
||||
} else {
|
||||
double r = id.toDouble() % 10;
|
||||
return charts.MaterialPalette.getOrderedPalettes(10)[r.round()].shadeDefault;
|
||||
}
|
||||
}
|
||||
|
||||
static Color historyStateColor(String state, int id) {
|
||||
Color c = _stateColors[state];
|
||||
if (c != null) {
|
||||
return c;
|
||||
} else {
|
||||
if (id > -1) {
|
||||
double r = id.toDouble() % 10;
|
||||
charts.Color c1 = charts.MaterialPalette.getOrderedPalettes(10)[r.round()].shadeDefault;
|
||||
return Color.fromARGB(c1.a, c1.r, c1.g, c1.b);
|
||||
} else {
|
||||
return _stateColors[EntityState.on];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -67,7 +67,7 @@ class EntityIcon extends StatelessWidget {
|
||||
padding: padding,
|
||||
child: buildIcon(
|
||||
entityWrapper,
|
||||
color ?? HAClientTheme().getColorByEntityState(entityWrapper.entity.state, context)
|
||||
color ?? EntityColor.stateColor(entityWrapper.entity.state)
|
||||
),
|
||||
);
|
||||
}
|
@ -5,24 +5,18 @@ class EntityName extends StatelessWidget {
|
||||
final EdgeInsetsGeometry padding;
|
||||
final TextOverflow textOverflow;
|
||||
final bool wordsWrap;
|
||||
final double fontSize;
|
||||
final TextAlign textAlign;
|
||||
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.textStyle, this.wordsWrap: true, this.textAlign: TextAlign.left}) : super(key: key);
|
||||
const EntityName({Key key, this.maxLines, this.padding: const EdgeInsets.only(right: 10.0), this.textOverflow: TextOverflow.ellipsis, this.wordsWrap: true, this.fontSize: Sizes.nameFontSize, this.textAlign: TextAlign.left}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||
TextStyle tStyle;
|
||||
if (textStyle == null) {
|
||||
TextStyle textStyle = TextStyle(fontSize: fontSize);
|
||||
if (entityWrapper.entity.statelessType == StatelessEntityType.WEBLINK) {
|
||||
tStyle = HAClientTheme().getLinkTextStyle(context);
|
||||
} else {
|
||||
tStyle = Theme.of(context).textTheme.body1;
|
||||
}
|
||||
} else {
|
||||
tStyle = textStyle;
|
||||
textStyle = textStyle.apply(color: Colors.blue, decoration: TextDecoration.underline);
|
||||
}
|
||||
return Padding(
|
||||
padding: padding,
|
||||
@ -31,7 +25,7 @@ class EntityName extends StatelessWidget {
|
||||
overflow: textOverflow,
|
||||
softWrap: wordsWrap,
|
||||
maxLines: maxLines,
|
||||
style: tStyle,
|
||||
style: textStyle,
|
||||
textAlign: textAlign,
|
||||
),
|
||||
);
|
14
lib/entity_widgets/entity_page_container.dart
Normal file
14
lib/entity_widgets/entity_page_container.dart
Normal file
@ -0,0 +1,14 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class EntityPageContainer extends StatelessWidget {
|
||||
EntityPageContainer({Key key, @required this.children}) : super(key: key);
|
||||
|
||||
final List<Widget> children;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView(
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,19 +1,21 @@
|
||||
part of '../../main.dart';
|
||||
part of '../main.dart';
|
||||
|
||||
class GlanceCardEntityContainer extends StatelessWidget {
|
||||
class GlanceEntityContainer extends StatelessWidget {
|
||||
|
||||
final bool showName;
|
||||
final bool showState;
|
||||
final bool nameInTheBottom;
|
||||
final double iconSize;
|
||||
final double nameFontSize;
|
||||
final bool wordsWrapInName;
|
||||
|
||||
GlanceCardEntityContainer({
|
||||
GlanceEntityContainer({
|
||||
Key key,
|
||||
@required this.showName,
|
||||
@required this.showState,
|
||||
this.nameInTheBottom: false,
|
||||
this.iconSize: Sizes.iconSize,
|
||||
this.nameFontSize: Sizes.smallFontSize,
|
||||
this.wordsWrapInName: false
|
||||
}) : super(key: key);
|
||||
|
||||
@ -29,7 +31,7 @@ class GlanceCardEntityContainer extends StatelessWidget {
|
||||
List<Widget> result = [];
|
||||
if (!nameInTheBottom) {
|
||||
if (showName) {
|
||||
result.add(_buildName(context));
|
||||
result.add(_buildName());
|
||||
}
|
||||
} else {
|
||||
if (showState) {
|
||||
@ -47,29 +49,33 @@ class GlanceCardEntityContainer extends StatelessWidget {
|
||||
result.add(_buildState());
|
||||
}
|
||||
} else {
|
||||
result.add(_buildName(context));
|
||||
result.add(_buildName());
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: InkResponse(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minWidth: Sizes.iconSize * 2),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
//mainAxisAlignment: MainAxisAlignment.start,
|
||||
//crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: result,
|
||||
),
|
||||
),
|
||||
onTap: () => entityWrapper.handleTap(),
|
||||
onLongPress: () => entityWrapper.handleHold(),
|
||||
onDoubleTap: () => entityWrapper.handleDoubleTap(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildName(BuildContext context) {
|
||||
Widget _buildName() {
|
||||
return EntityName(
|
||||
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
|
||||
textOverflow: TextOverflow.ellipsis,
|
||||
wordsWrap: wordsWrapInName,
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: Theme.of(context).textTheme.body1,
|
||||
fontSize: nameFontSize,
|
||||
);
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
|
||||
});
|
||||
|
||||
if ((_selectedId == -1) && (numericDataLists.isNotEmpty)) {
|
||||
_selectedId = numericDataLists.length -1;
|
||||
_selectedId = 0;
|
||||
}
|
||||
List<charts.Series<EntityHistoryMoment, DateTime>> result = [];
|
||||
numericDataLists.forEach((attrName, dataList) {
|
||||
@ -156,8 +156,7 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
|
||||
result.add(
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: "value",
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) =>
|
||||
HAClientTheme().chartHistoryStateColor("_", historyMoment.colorId, context),
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor("_", historyMoment.colorId),
|
||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) {
|
||||
if (historyMoment.hiddenDot) {
|
||||
return 0.0;
|
||||
@ -180,8 +179,7 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'state',
|
||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 4.0,
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) =>
|
||||
HAClientTheme().chartHistoryStateColor(historyMoment.state, historyMoment.colorId, context),
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
domainLowerBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
domainUpperBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.endTime ?? DateTime.now(),
|
||||
@ -204,11 +202,6 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
|
||||
_selectedId -= 1;
|
||||
});
|
||||
}
|
||||
else {
|
||||
setState(() {
|
||||
_selectedId = _parsedHistory.first.data.length - 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _selectNext() {
|
||||
@ -217,12 +210,6 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
|
||||
_selectedId += 1;
|
||||
});
|
||||
}
|
||||
else {
|
||||
setState(() {
|
||||
_selectedId = 0;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelectionChanged(charts.SelectionModel model) {
|
@ -17,7 +17,9 @@ class EntityHistoryConfig {
|
||||
|
||||
class EntityHistoryWidget extends StatefulWidget {
|
||||
|
||||
const EntityHistoryWidget({Key key}) : super(key: key);
|
||||
final EntityHistoryConfig config;
|
||||
|
||||
const EntityHistoryWidget({Key key, @required this.config}) : super(key: key);
|
||||
|
||||
@override
|
||||
_EntityHistoryWidgetState createState() {
|
||||
@ -31,7 +33,6 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
||||
bool _needToUpdateHistory;
|
||||
DateTime _historyLastUpdated;
|
||||
bool _disposed = false;
|
||||
Entity entity;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -39,14 +40,14 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
||||
_needToUpdateHistory = true;
|
||||
}
|
||||
|
||||
void _loadHistory(String entityId) {
|
||||
void _loadHistory(HomeAssistant ha, String entityId) {
|
||||
DateTime now = DateTime.now();
|
||||
if (_historyLastUpdated != null) {
|
||||
Logger.d("History was updated ${now.difference(_historyLastUpdated).inSeconds} seconds ago");
|
||||
}
|
||||
if (_historyLastUpdated == null || now.difference(_historyLastUpdated).inSeconds > 30) {
|
||||
_historyLastUpdated = now;
|
||||
ConnectionManager().getHistory(entityId).then((history){
|
||||
ha.getHistory(entityId).then((history){
|
||||
if (!_disposed) {
|
||||
setState(() {
|
||||
_history = history.isNotEmpty ? history[0] : [];
|
||||
@ -67,17 +68,18 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final HomeAssistantModel homeAssistantModel = HomeAssistantModel.of(context);
|
||||
final EntityModel entityModel = EntityModel.of(context);
|
||||
final Entity entity = entityModel.entityWrapper.entity;
|
||||
if (!_needToUpdateHistory) {
|
||||
_needToUpdateHistory = true;
|
||||
} else {
|
||||
_loadHistory(entity.entityId);
|
||||
_loadHistory(homeAssistantModel.homeAssistant, entity.entityId);
|
||||
}
|
||||
return _buildChart(entity.historyConfig);
|
||||
return _buildChart();
|
||||
}
|
||||
|
||||
Widget _buildChart(EntityHistoryConfig config) {
|
||||
Widget _buildChart() {
|
||||
List<Widget> children = [];
|
||||
if (_history == null) {
|
||||
children.add(
|
||||
@ -89,7 +91,7 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
||||
);
|
||||
} else {
|
||||
children.add(
|
||||
_selectChartWidget(config)
|
||||
_selectChartWidget()
|
||||
);
|
||||
}
|
||||
children.add(Divider());
|
||||
@ -101,8 +103,8 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _selectChartWidget(EntityHistoryConfig config) {
|
||||
switch (config.chartType) {
|
||||
Widget _selectChartWidget() {
|
||||
switch (widget.config.chartType) {
|
||||
|
||||
case EntityHistoryWidgetType.simple: {
|
||||
return SimpleStateHistoryChartWidget(
|
||||
@ -113,14 +115,14 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
||||
case EntityHistoryWidgetType.numericState: {
|
||||
return NumericStateHistoryChartWidget(
|
||||
rawHistory: _history,
|
||||
config: config,
|
||||
config: widget.config,
|
||||
);
|
||||
}
|
||||
|
||||
case EntityHistoryWidgetType.numericAttributes: {
|
||||
return CombinedHistoryChartWidget(
|
||||
rawHistory: _history,
|
||||
config: config,
|
||||
config: widget.config,
|
||||
);
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user