Compare commits

..

3 Commits

Author SHA1 Message Date
5211d1ff46 v. 0.5.4 2019-05-09 14:15:21 +03:00
d1c9fddba6 v. 0.5.4 2019-05-09 13:17:30 +03:00
14cc55a2c7 Resolves #373 2019-05-09 13:16:35 +03:00
174 changed files with 7478 additions and 14793 deletions

View File

@ -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]

View File

@ -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:**

View File

@ -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.

View File

@ -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:**

View File

@ -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.

11
.gitignore vendored
View File

@ -9,14 +9,5 @@ build/
.flutter-plugins .flutter-plugins
.idea/ .idea/
.vscode/
.theia/
.project/
.settings/
flutter_export_environment.sh key.properties
.flutter-plugins-dependencies
key.properties
.secrets.dart
pubspec.lock

View File

@ -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"

View File

@ -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==

View File

@ -1,16 +1,12 @@
[![flutter](https://somegeeky.website/assets/badges/flutter_badge_v3.svg)](https://somegeeky.website/badges/flutter) [![dart](https://somegeeky.website/assets/badges/dart_badge_v3.svg)](https://somegeeky.website/badges/dart)
# HA Client # HA Client
## Native Android client for Home Assistant ## 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
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/estevez-dev/ha_client) Discuss it in [Home Assistant community](https://community.home-assistant.io/t/alpha-testing-ha-client-native-android-client-for-home-assistant/69912)
#### Pre-release CI build
[![Codemagic build status](https://api.codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5da8bdab9f20ef798f7c2c64/status_badge.svg)](https://codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5da8bdab9f20ef798f7c2c64/latest_build)
#### Beta CI build
[![Codemagic build status](https://api.codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5db1862025dc3f0b0288a57a/status_badge.svg)](https://codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5db1862025dc3f0b0288a57a/latest_build)

1
android/.gitignore vendored
View File

@ -8,4 +8,3 @@
/build /build
/captures /captures
GeneratedPluginRegistrant.java GeneratedPluginRegistrant.java
.project/

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -50,14 +50,6 @@ android {
} }
signingConfigs { signingConfigs {
if (!System.getenv()["CI"]) {
debug {
keyAlias keystoreProperties['debugKeyAlias']
keyPassword keystoreProperties['debugKeyPassword']
storeFile file(keystoreProperties['debugStoreFile'])
storePassword keystoreProperties['debugStorePassword']
}
}
release { release {
keyAlias keystoreProperties['keyAlias'] keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword'] keyPassword keystoreProperties['keyPassword']
@ -78,11 +70,7 @@ flutter {
} }
dependencies { dependencies {
implementation 'com.google.firebase:firebase-analytics:17.2.2'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
} }
apply plugin: 'io.fabric'
apply plugin: 'com.google.gms.google-services'

View File

@ -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"
}

View File

@ -1,15 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.keyboardcrumbs.hassclient"> package="com.keyboardcrumbs.hassclient">
<uses-feature android:name="android.hardware.touchscreen" <!-- The INTERNET permission is required for development. Specifically,
android:required="false" /> 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.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- io.flutter.app.FlutterApplication is an android.app.Application that <!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method. calls FlutterMain.startInitialization(this); in its onCreate method.
@ -17,18 +13,9 @@
additional functionality it is fine to subclass or reimplement additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. --> FlutterApplication and put your custom class here. -->
<application <application
android:name="io.flutter.app.FlutterApplication"
android:label="HA Client" android:label="HA Client"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher">
android:usesCleartextTraffic="true">
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="ha_notify" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
@ -36,35 +23,17 @@
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<meta-data <meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable" android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:resource="@drawable/launch_background" /> android:value="true" />
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>
<service
android:name="io.flutter.plugins.androidalarmmanager.AlarmService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false"/>
<receiver
android:name="io.flutter.plugins.androidalarmmanager.AlarmBroadcastReceiver"
android:exported="false"/>
<receiver
android:name="io.flutter.plugins.androidalarmmanager.RebootBroadcastReceiver"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"></action>
</intent-filter>
</receiver>
</application> </application>
</manifest> </manifest>

View File

@ -1,15 +1,13 @@
package com.keyboardcrumbs.hassclient; package com.keyboardcrumbs.hassclient;
import androidx.annotation.NonNull; import android.os.Bundle;
import io.flutter.embedding.android.FlutterActivity; import io.flutter.app.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.GeneratedPluginRegistrant; import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity { public class MainActivity extends FlutterActivity {
@Override
@Override protected void onCreate(Bundle savedInstanceState) {
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(flutterEngine); GeneratedPluginRegistrant.registerWith(this);
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 612 B

View File

@ -5,7 +5,4 @@
Flutter draws its first frame --> Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
</style> </style>
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources> </resources>

View File

@ -2,15 +2,10 @@ buildscript {
repositories { repositories {
google() google()
jcenter() jcenter()
maven {
url 'https://maven.fabric.io/public'
}
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.3.2' classpath 'com.android.tools.build:gradle:3.1.2'
classpath 'com.google.gms:google-services:4.3.3'
classpath 'io.fabric.tools:gradle:1.26.1'
} }
} }
@ -18,9 +13,6 @@ allprojects {
repositories { repositories {
google() google()
jcenter() jcenter()
maven {
url 'https://maven.fabric.io/public'
}
} }
} }

View File

@ -2,5 +2,4 @@ org.gradle.jvmargs=-Xmx2g
org.gradle.daemon=true org.gradle.daemon=true
org.gradle.caching=true org.gradle.caching=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=false
android.enableR8=true

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.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

View File

@ -1 +0,0 @@
include ':app'

View File

@ -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>

View File

@ -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>

View File

@ -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');
}
};

View File

@ -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!
```

View File

@ -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!
```

View File

@ -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!
```

Binary file not shown.

Binary file not shown.

View File

@ -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,
);
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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],
),
),
);
}
}

View File

@ -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();
}
}

View File

@ -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(),
)
],
)
],
);
}
}

View File

@ -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;
}
}
}

View File

@ -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,
);
}
}

View File

@ -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
),
);
}
}

View File

@ -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;
}
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -1,5 +0,0 @@
part of '../../main.dart';
class SunEntity extends Entity {
SunEntity(Map rawData, String webHost) : super(rawData, webHost);
}

View File

@ -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();
}
}

View File

@ -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,
),
);
}
}

View File

@ -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,
);
}
}

View File

@ -1,72 +1,62 @@
part of '../main.dart'; part of 'main.dart';
class EntityViewPage extends StatefulWidget { 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 String entityId;
final HomeAssistant homeAssistant;
@override @override
_EntityViewPageState createState() => new _EntityViewPageState(); _EntityViewPageState createState() => new _EntityViewPageState();
} }
class _EntityViewPageState extends State<EntityViewPage> { class _EntityViewPageState extends State<EntityViewPage> {
String _title;
StreamSubscription _refreshDataSubscription; StreamSubscription _refreshDataSubscription;
StreamSubscription _stateSubscription; StreamSubscription _stateSubscription;
Entity entity;
Entity forwardToMainPage;
bool _popScheduled = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) { _stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
if (event.entityId == widget.entityId) { if (event.entityId == widget.entityId) {
entity = HomeAssistant().entities.get(widget.entityId); Logger.d("State change event handled by entity page: ${event.entityId}");
Logger.d("[Entity page] State change event handled: ${event.entityId}");
setState(() {}); setState(() {});
} }
}); });
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) { _refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
entity = HomeAssistant().entities.get(widget.entityId);
setState(() {}); setState(() {});
}); });
entity = HomeAssistant().entities.get(widget.entityId); _prepareData();
} }
void _prepareData() async {
_title = widget.homeAssistant.entities.get(widget.entityId).displayName;
}
@override @override
Widget build(BuildContext context) { 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( return new Scaffold(
appBar: new AppBar( appBar: new AppBar(
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){ leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
Navigator.pop(context); 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 @override
void dispose(){ void dispose(){
if (_stateSubscription != null) _stateSubscription.cancel(); if (_stateSubscription != null) _stateSubscription.cancel();
if (_refreshDataSubscription != null) _refreshDataSubscription.cancel(); if (_refreshDataSubscription != null) _refreshDataSubscription.cancel();
eventBus.fire(ShowEntityPageEvent(entity: forwardToMainPage));
super.dispose(); super.dispose();
} }
} }

View File

@ -1,8 +1,7 @@
part of '../../main.dart'; part of '../main.dart';
class AlarmControlPanelEntity extends Entity { class AlarmControlPanelEntity extends Entity {
AlarmControlPanelEntity(Map rawData, String webHost) : super(rawData, webHost); AlarmControlPanelEntity(Map rawData) : super(rawData);
@override @override
Widget _buildAdditionalControlsForPage(BuildContext context) { Widget _buildAdditionalControlsForPage(BuildContext context) {

View File

@ -1,8 +1,7 @@
part of '../../main.dart'; part of '../main.dart';
class AutomationEntity extends Entity { class AutomationEntity extends Entity {
AutomationEntity(Map rawData, String webHost) : super(rawData, webHost); AutomationEntity(Map rawData) : super(rawData);
@override @override
Widget _buildStatePart(BuildContext context) { Widget _buildStatePart(BuildContext context) {

View File

@ -1,8 +1,7 @@
part of '../../main.dart'; part of '../main.dart';
class ButtonEntity extends Entity { class ButtonEntity extends Entity {
ButtonEntity(Map rawData, String webHost) : super(rawData, webHost); ButtonEntity(Map rawData) : super(rawData);
@override @override
Widget _buildStatePart(BuildContext context) { Widget _buildStatePart(BuildContext context) {

View File

@ -1,18 +1,14 @@
part of '../../main.dart'; part of '../main.dart';
class CameraEntity extends Entity { class CameraEntity extends Entity {
static const SUPPORT_ON_OFF = 1; static const SUPPORT_ON_OFF = 1;
static const SUPPORT_STREAM = 2;
CameraEntity(Map rawData, String webHost) : super(rawData, webHost); CameraEntity(Map rawData) : super(rawData);
bool get supportOnOff => ((supportedFeatures & bool get supportOnOff => ((supportedFeatures &
CameraEntity.SUPPORT_ON_OFF) == CameraEntity.SUPPORT_ON_OFF) ==
CameraEntity.SUPPORT_ON_OFF); CameraEntity.SUPPORT_ON_OFF);
bool get supportStream => ((supportedFeatures &
CameraEntity.SUPPORT_STREAM) ==
CameraEntity.SUPPORT_STREAM);
@override @override
Widget _buildAdditionalControlsForPage(BuildContext context) { Widget _buildAdditionalControlsForPage(BuildContext context) {

View File

@ -1,4 +1,4 @@
part of '../../main.dart'; part of '../main.dart';
class ClimateEntity extends Entity { class ClimateEntity extends Entity {
@ -10,57 +10,69 @@ class ClimateEntity extends Entity {
); );
static const SUPPORT_TARGET_TEMPERATURE = 1; static const SUPPORT_TARGET_TEMPERATURE = 1;
static const SUPPORT_TARGET_TEMPERATURE_RANGE = 2; static const SUPPORT_TARGET_TEMPERATURE_HIGH = 2;
static const SUPPORT_TARGET_HUMIDITY = 4; static const SUPPORT_TARGET_TEMPERATURE_LOW = 4;
static const SUPPORT_FAN_MODE = 8; static const SUPPORT_TARGET_HUMIDITY = 8;
static const SUPPORT_PRESET_MODE = 16; static const SUPPORT_TARGET_HUMIDITY_HIGH = 16;
static const SUPPORT_SWING_MODE = 32; static const SUPPORT_TARGET_HUMIDITY_LOW = 32;
static const SUPPORT_AUX_HEAT = 64; static const SUPPORT_FAN_MODE = 64;
static const SUPPORT_OPERATION_MODE = 128;
static const SUPPORT_HOLD_MODE = 256;
//static const SUPPORT_OPERATION_MODE = 16; static const SUPPORT_SWING_MODE = 512;
//static const SUPPORT_HOLD_MODE = 256; static const SUPPORT_AWAY_MODE = 1024;
//static const SUPPORT_AWAY_MODE = 1024; static const SUPPORT_AUX_HEAT = 2048;
//static const SUPPORT_ON_OFF = 4096; static const SUPPORT_ON_OFF = 4096;
ClimateEntity(Map rawData, String webHost) : super(rawData, webHost);
bool get supportTargetTemperature => ((supportedFeatures & bool get supportTargetTemperature => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_TEMPERATURE) == ClimateEntity.SUPPORT_TARGET_TEMPERATURE) ==
ClimateEntity.SUPPORT_TARGET_TEMPERATURE); ClimateEntity.SUPPORT_TARGET_TEMPERATURE);
bool get supportTargetTemperatureRange => ((supportedFeatures & bool get supportTargetTemperatureHigh => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_RANGE) == ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH) ==
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_RANGE); ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH);
bool get supportTargetTemperatureLow => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW) ==
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW);
bool get supportTargetHumidity => ((supportedFeatures & bool get supportTargetHumidity => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_HUMIDITY) == ClimateEntity.SUPPORT_TARGET_HUMIDITY) ==
ClimateEntity.SUPPORT_TARGET_HUMIDITY); ClimateEntity.SUPPORT_TARGET_HUMIDITY);
bool get supportTargetHumidityHigh => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH) ==
ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH);
bool get supportTargetHumidityLow => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW) ==
ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW);
bool get supportFanMode => bool get supportFanMode =>
((supportedFeatures & ClimateEntity.SUPPORT_FAN_MODE) == ((supportedFeatures & ClimateEntity.SUPPORT_FAN_MODE) ==
ClimateEntity.SUPPORT_FAN_MODE); ClimateEntity.SUPPORT_FAN_MODE);
bool get supportOperationMode => ((supportedFeatures &
ClimateEntity.SUPPORT_OPERATION_MODE) ==
ClimateEntity.SUPPORT_OPERATION_MODE);
bool get supportHoldMode =>
((supportedFeatures & ClimateEntity.SUPPORT_HOLD_MODE) ==
ClimateEntity.SUPPORT_HOLD_MODE);
bool get supportSwingMode => bool get supportSwingMode =>
((supportedFeatures & ClimateEntity.SUPPORT_SWING_MODE) == ((supportedFeatures & ClimateEntity.SUPPORT_SWING_MODE) ==
ClimateEntity.SUPPORT_SWING_MODE); ClimateEntity.SUPPORT_SWING_MODE);
bool get supportPresetMode => bool get supportAwayMode =>
((supportedFeatures & ClimateEntity.SUPPORT_PRESET_MODE) == ((supportedFeatures & ClimateEntity.SUPPORT_AWAY_MODE) ==
ClimateEntity.SUPPORT_PRESET_MODE); ClimateEntity.SUPPORT_AWAY_MODE);
bool get supportAuxHeat => bool get supportAuxHeat =>
((supportedFeatures & ClimateEntity.SUPPORT_AUX_HEAT) == ((supportedFeatures & ClimateEntity.SUPPORT_AUX_HEAT) ==
ClimateEntity.SUPPORT_AUX_HEAT); ClimateEntity.SUPPORT_AUX_HEAT);
bool get supportOnOff =>
((supportedFeatures & ClimateEntity.SUPPORT_ON_OFF) ==
ClimateEntity.SUPPORT_ON_OFF);
List<String> get hvacModes => attributes["hvac_modes"] != null List<String> get operationList => attributes["operation_list"] != null
? (attributes["hvac_modes"] as List).cast<String>() ? (attributes["operation_list"] as List).cast<String>()
: null; : null;
List<String> get fanModes => attributes["fan_modes"] != null List<String> get fanList => attributes["fan_list"] != null
? (attributes["fan_modes"] as List).cast<String>() ? (attributes["fan_list"] as List).cast<String>()
: null; : null;
List<String> get presetModes => attributes["preset_modes"] != null List<String> get swingList => attributes["swing_list"] != null
? (attributes["preset_modes"] as List).cast<String>() ? (attributes["swing_list"] as List).cast<String>()
: null;
List<String> get swingModes => attributes["swing_modes"] != null
? (attributes["swing_modes"] as List).cast<String>()
: null; : null;
double get temperature => _getDoubleAttributeValue('temperature'); double get temperature => _getDoubleAttributeValue('temperature');
double get currentTemperature => _getDoubleAttributeValue('current_temperature');
double get targetHigh => _getDoubleAttributeValue('target_temp_high'); double get targetHigh => _getDoubleAttributeValue('target_temp_high');
double get targetLow => _getDoubleAttributeValue('target_temp_low'); double get targetLow => _getDoubleAttributeValue('target_temp_low');
double get maxTemp => _getDoubleAttributeValue('max_temp') ?? 100.0; double get maxTemp => _getDoubleAttributeValue('max_temp') ?? 100.0;
@ -69,22 +81,25 @@ class ClimateEntity extends Entity {
double get maxHumidity => _getDoubleAttributeValue('max_humidity'); double get maxHumidity => _getDoubleAttributeValue('max_humidity');
double get minHumidity => _getDoubleAttributeValue('min_humidity'); double get minHumidity => _getDoubleAttributeValue('min_humidity');
double get temperatureStep => _getDoubleAttributeValue('target_temp_step') ?? 0.5; double get temperatureStep => _getDoubleAttributeValue('target_temp_step') ?? 0.5;
String get hvacAction => attributes['hvac_action']; String get operationMode => attributes['operation_mode'];
String get fanMode => attributes['fan_mode']; String get fanMode => attributes['fan_mode'];
String get presetMode => attributes['preset_mode'];
String get swingMode => attributes['swing_mode']; String get swingMode => attributes['swing_mode'];
bool get awayMode => attributes['away_mode'] == "on"; bool get awayMode => attributes['away_mode'] == "on";
//bool get isOff => state == EntityState.off; bool get isOff => state == EntityState.off;
bool get auxHeat => attributes['aux_heat'] == "on"; bool get auxHeat => attributes['aux_heat'] == "on";
ClimateEntity(Map rawData) : super(rawData);
@override @override
void update(Map rawData, String webHost) { void update(Map rawData) {
super.update(rawData, webHost); super.update(rawData);
if (supportTargetTemperature) { if (supportTargetTemperature) {
historyConfig.numericAttributesToShow.add("temperature"); historyConfig.numericAttributesToShow.add("temperature");
} }
if (supportTargetTemperatureRange) { if (supportTargetTemperatureHigh) {
historyConfig.numericAttributesToShow.add("target_temp_high"); historyConfig.numericAttributesToShow.add("target_temp_high");
}
if (supportTargetTemperatureLow) {
historyConfig.numericAttributesToShow.add("target_temp_low"); historyConfig.numericAttributesToShow.add("target_temp_low");
} }
} }

View File

@ -1,4 +1,4 @@
part of 'main.dart'; part of '../main.dart';
class EntityState { class EntityState {
static const on = 'on'; static const on = 'on';
@ -29,11 +29,6 @@ class EntityState {
static const ok = 'ok'; static const ok = 'ok';
static const problem = 'problem'; static const problem = 'problem';
static const active = 'active'; static const active = 'active';
static const cleaning = 'cleaning';
static const docked = 'docked';
static const returning = 'returning';
static const error = 'error';
} }
class EntityUIAction { class EntityUIAction {
@ -51,10 +46,6 @@ class EntityUIAction {
String holdNavigationPath; String holdNavigationPath;
String holdService; String holdService;
Map<String, dynamic> holdServiceData; Map<String, dynamic> holdServiceData;
String doubleTapAction = EntityUIAction.none;
String doubleTapNavigationPath;
String doubleTapService;
Map<String, dynamic> doubleTapServiceData;
EntityUIAction({rawEntityData}) { EntityUIAction({rawEntityData}) {
if (rawEntityData != null) { if (rawEntityData != null) {
@ -80,61 +71,29 @@ class EntityUIAction {
holdServiceData = rawEntityData["hold_action"]["service_data"]; holdServiceData = rawEntityData["hold_action"]["service_data"];
} }
} }
if (rawEntityData["double_tap_action"] != null) {
if (rawEntityData["double_tap_action"] is String) {
doubleTapAction = rawEntityData["double_tap_action"];
} else {
doubleTapAction =
rawEntityData["double_tap_action"]["action"] ?? EntityUIAction.none;
doubleTapNavigationPath = rawEntityData["double_tap_action"]["navigation_path"];
doubleTapService = rawEntityData["double_tap_action"]["service"];
doubleTapServiceData = rawEntityData["double_tap_action"]["service_data"];
}
}
} }
} }
} }
class CardType { class CardType {
static const HORIZONTAL_STACK = "horizontal-stack"; static const horizontalStack = "horizontal-stack";
static const VERTICAL_STACK = "vertical-stack"; static const verticalStack = "vertical-stack";
static const ENTITIES = "entities"; static const entities = "entities";
static const GLANCE = "glance"; static const glance = "glance";
static const MEDIA_CONTROL = "media-control"; static const mediaControl = "media-control";
static const WEATHER_FORECAST = "weather-forecast"; static const weatherForecast = "weather-forecast";
static const THERMOSTAT = "thermostat"; static const thermostat = "thermostat";
static const SENSOR = "sensor"; static const sensor = "sensor";
static const PLANT_STATUS = "plant-status"; static const plantStatus = "plant-status";
static const PICTURE_ENTITY = "picture-entity"; static const pictureEntity = "picture-entity";
static const PICTURE_ELEMENTS = "picture-elements"; static const pictureElements = "picture-elements";
static const PICTURE = "picture"; static const picture = "picture";
static const MAP = "map"; static const map = "map";
static const IFRAME = "iframe"; static const iframe = "iframe";
static const GAUGE = "gauge"; static const gauge = "gauge";
static const ENTITY_BUTTON = "entity-button"; static const entityButton = "entity-button";
static const CONDITIONAL = "conditional"; static const conditional = "conditional";
static const ALARM_PANEL = "alarm-panel"; static const alarmPanel = "alarm-panel";
static const MARKDOWN = "markdown"; static const markdown = "markdown";
static const LIGHT = "light";
}
class Sizes {
static const rightWidgetPadding = 10.0;
static const leftWidgetPadding = 10.0;
static const buttonPadding = 4.0;
static const extendedWidgetHeight = 50.0;
static const iconSize = 28.0;
static const largeIconSize = 46.0;
//static const stateFontSize = 15.0;
//static const nameFontSize = 15.0;
//static const smallFontSize = 14.0;
//static const largeFontSize = 24.0;
static const inputWidth = 160.0;
static const rowPadding = 10.0;
static const doubleRowPadding = rowPadding*2;
static const minViewColumnWidth = 350;
static const entityPageMaxWidth = 400.0;
static const mainPageScreenSeparatorWidth = 5.0;
static const tabletMinWidth = minViewColumnWidth + entityPageMaxWidth + 5;
} }

View File

@ -1,4 +1,4 @@
part of '../../main.dart'; part of '../main.dart';
class CoverEntity extends Entity { class CoverEntity extends Entity {
@ -11,8 +11,6 @@ class CoverEntity extends Entity {
static const SUPPORT_STOP_TILT = 64; static const SUPPORT_STOP_TILT = 64;
static const SUPPORT_SET_TILT_POSITION = 128; static const SUPPORT_SET_TILT_POSITION = 128;
CoverEntity(Map rawData, String webHost) : super(rawData, webHost);
bool get supportOpen => ((supportedFeatures & bool get supportOpen => ((supportedFeatures &
CoverEntity.SUPPORT_OPEN) == CoverEntity.SUPPORT_OPEN) ==
CoverEntity.SUPPORT_OPEN); CoverEntity.SUPPORT_OPEN);
@ -47,6 +45,8 @@ class CoverEntity extends Entity {
bool get canTiltBeOpened => currentTiltPosition < 100; bool get canTiltBeOpened => currentTiltPosition < 100;
bool get canTiltBeClosed => currentTiltPosition > 0; bool get canTiltBeClosed => currentTiltPosition > 0;
CoverEntity(Map rawData) : super(rawData);
@override @override
Widget _buildStatePart(BuildContext context) { Widget _buildStatePart(BuildContext context) {
return CoverStateWidget(); return CoverStateWidget();

View File

@ -1,8 +1,6 @@
part of '../../main.dart'; part of '../main.dart';
class DateTimeEntity extends Entity { class DateTimeEntity extends Entity {
DateTimeEntity(Map rawData, String webHost) : super(rawData, webHost);
bool get hasDate => attributes["has_date"] ?? false; bool get hasDate => attributes["has_date"] ?? false;
bool get hasTime => attributes["has_time"] ?? false; bool get hasTime => attributes["has_time"] ?? false;
int get year => attributes["year"] ?? 1970; int get year => attributes["year"] ?? 1970;
@ -14,6 +12,8 @@ class DateTimeEntity extends Entity {
String get formattedState => _getFormattedState(); String get formattedState => _getFormattedState();
DateTime get dateTimeState => _getDateTimeState(); DateTime get dateTimeState => _getDateTimeState();
DateTimeEntity(Map rawData) : super(rawData);
@override @override
Widget _buildStatePart(BuildContext context) { Widget _buildStatePart(BuildContext context) {
return DateTimeStateWidget(); return DateTimeStateWidget();
@ -35,7 +35,8 @@ class DateTimeEntity extends Entity {
return formattedState; return formattedState;
} }
void setNewState(Map newValue) { void setNewState(newValue) {
ConnectionManager().callService(domain: domain, service: "set_datetime", entityId: entityId, data: newValue); eventBus
.fire(new ServiceCallEvent(domain, "set_datetime", entityId, newValue));
} }
} }

View File

@ -27,7 +27,7 @@ class Entity {
"cold.on": "Cold", "cold.on": "Cold",
"cold.off": "Normal", "cold.off": "Normal",
"connectivity.on": "Connected", "connectivity.on": "Connected",
"connectivity.off": "Disconnected", "connectivity.off": "Diconnected",
"door.on": "Open", "door.on": "Open",
"door.off": "Closed", "door.off": "Closed",
"garage_door.on": "Open", "garage_door.on": "Open",
@ -73,7 +73,6 @@ class Entity {
Map attributes; Map attributes;
String domain; String domain;
String entityId; String entityId;
String entityPicture;
String state; String state;
String displayState; String displayState;
DateTime _lastUpdated; DateTime _lastUpdated;
@ -95,6 +94,7 @@ class Entity {
bool get isBadge => Entity.badgeDomains.contains(domain); bool get isBadge => Entity.badgeDomains.contains(domain);
String get icon => attributes["icon"] ?? ""; String get icon => attributes["icon"] ?? "";
bool get isOn => state == EntityState.on; bool get isOn => state == EntityState.on;
String get entityPicture => _getEntityPictureUrl();
String get unitOfMeasurement => attributes["unit_of_measurement"] ?? ""; String get unitOfMeasurement => attributes["unit_of_measurement"] ?? "";
List get childEntityIds => attributes["entity_id"] ?? []; List get childEntityIds => attributes["entity_id"] ?? [];
String get lastUpdated => _getLastUpdatedFormatted(); String get lastUpdated => _getLastUpdatedFormatted();
@ -102,21 +102,21 @@ class Entity {
double get doubleState => double.tryParse(state) ?? 0.0; double get doubleState => double.tryParse(state) ?? 0.0;
int get supportedFeatures => attributes["supported_features"] ?? 0; int get supportedFeatures => attributes["supported_features"] ?? 0;
String _getEntityPictureUrl(String webHost) { String _getEntityPictureUrl() {
String result = attributes["entity_picture"]; String result = attributes["entity_picture"];
if (result == null) return result; if (result == null) return result;
if (!result.startsWith("http")) { if (!result.startsWith("http")) {
if (result.startsWith("/")) { if (result.startsWith("/")) {
result = "$webHost$result"; result = "$homeAssistantWebHost$result";
} else { } else {
result = "$webHost/$result"; result = "$homeAssistantWebHost/$result";
} }
} }
return result; return result;
} }
Entity(Map rawData, String webHost) { Entity(Map rawData) {
update(rawData, webHost); update(rawData);
} }
Entity.missed(String entityId) { Entity.missed(String entityId) {
@ -148,15 +148,14 @@ class Entity {
attributes = {"hidden": false, "friendly_name": "${name ?? url}", "icon": "${icon ?? 'mdi:link'}"}; attributes = {"hidden": false, "friendly_name": "${name ?? url}", "icon": "${icon ?? 'mdi:link'}"};
} }
void update(Map rawData, String webHost) { void update(Map rawData) {
attributes = rawData["attributes"] ?? {}; attributes = rawData["attributes"] ?? {};
domain = rawData["entity_id"].split(".")[0]; domain = rawData["entity_id"].split(".")[0];
entityId = rawData["entity_id"]; entityId = rawData["entity_id"];
deviceClass = attributes["device_class"]; deviceClass = attributes["device_class"];
state = rawData["state"] is bool ? (rawData["state"] ? EntityState.on : EntityState.off) : rawData["state"]; state = rawData["state"];
displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? (state.toLowerCase() == 'unknown' ? '-' : state); displayState = Entity.StateByDeviceClass["$deviceClass.$state"] ?? state;
_lastUpdated = DateTime.tryParse(rawData["last_updated"]); _lastUpdated = DateTime.tryParse(rawData["last_updated"]);
entityPicture = _getEntityPictureUrl(webHost);
} }
double _getDoubleAttributeValue(String attributeName) { 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) { Widget buildBadgeWidget(BuildContext context) {
return EntityModel( return EntityModel(
entityWrapper: EntityWrapper(entity: this), entityWrapper: EntityWrapper(entity: this),
@ -221,7 +245,7 @@ class Entity {
String getAttribute(String attributeName) { String getAttribute(String attributeName) {
if (attributes != null) { if (attributes != null) {
return attributes["$attributeName"].toString(); return attributes["$attributeName"];
} }
return null; return null;
} }

View 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;
}
}
}
}

View File

@ -1,4 +1,4 @@
part of '../../main.dart'; part of '../main.dart';
class FanEntity extends Entity { class FanEntity extends Entity {
@ -6,7 +6,7 @@ class FanEntity extends Entity {
static const SUPPORT_OSCILLATE = 2; static const SUPPORT_OSCILLATE = 2;
static const SUPPORT_DIRECTION = 4; static const SUPPORT_DIRECTION = 4;
FanEntity(Map rawData, String webHost) : super(rawData, webHost); FanEntity(Map rawData) : super(rawData);
bool get supportSetSpeed => ((supportedFeatures & bool get supportSetSpeed => ((supportedFeatures &
FanEntity.SUPPORT_SET_SPEED) == FanEntity.SUPPORT_SET_SPEED) ==

View File

@ -1,13 +1,12 @@
part of '../../main.dart'; part of '../main.dart';
class GroupEntity extends Entity { class GroupEntity extends Entity {
GroupEntity(Map rawData) : super(rawData);
final List<String> _domainsForSwitchableGroup = ["switch", "light", "automation", "input_boolean"]; final List<String> _domainsForSwitchableGroup = ["switch", "light", "automation", "input_boolean"];
String mutualDomain; String mutualDomain;
bool switchable = false; bool switchable = false;
GroupEntity(Map rawData, String webHost) : super(rawData, webHost);
@override @override
Widget _buildStatePart(BuildContext context) { Widget _buildStatePart(BuildContext context) {
if (switchable) { if (switchable) {
@ -20,8 +19,8 @@ class GroupEntity extends Entity {
} }
@override @override
void update(Map rawData, String webHost) { void update(Map rawData) {
super.update(rawData, webHost); super.update(rawData);
if (_isOneDomain()) { if (_isOneDomain()) {
mutualDomain = attributes['entity_id'][0].split(".")[0]; mutualDomain = attributes['entity_id'][0].split(".")[0];
switchable = _domainsForSwitchableGroup.contains(mutualDomain); switchable = _domainsForSwitchableGroup.contains(mutualDomain);

View File

@ -1,4 +1,4 @@
part of '../../main.dart'; part of '../main.dart';
class LightEntity extends Entity { class LightEntity extends Entity {
@ -42,7 +42,7 @@ class LightEntity extends Entity {
bool get isAdditionalControls => ((supportedFeatures != null) && (supportedFeatures != 0)); bool get isAdditionalControls => ((supportedFeatures != null) && (supportedFeatures != 0));
List<String> get effectList => getStringListAttributeValue("effect_list"); List<String> get effectList => getStringListAttributeValue("effect_list");
LightEntity(Map rawData, String webHost) : super(rawData, webHost); LightEntity(Map rawData) : super(rawData);
HSVColor _getColor() { HSVColor _getColor() {
List hs = attributes["hs_color"]; List hs = attributes["hs_color"];

View File

@ -1,7 +1,7 @@
part of '../../main.dart'; part of '../main.dart';
class LockEntity extends Entity { class LockEntity extends Entity {
LockEntity(Map rawData, String webHost) : super(rawData, webHost); LockEntity(Map rawData) : super(rawData);
bool get isLocked => state == "locked"; bool get isLocked => state == "locked";

View File

@ -1,4 +1,4 @@
part of '../../main.dart'; part of '../main.dart';
class MediaPlayerEntity extends Entity { class MediaPlayerEntity extends Entity {
@ -20,7 +20,7 @@ class MediaPlayerEntity extends Entity {
static const SUPPORT_SHUFFLE_SET = 32768; static const SUPPORT_SHUFFLE_SET = 32768;
static const SUPPORT_SELECT_SOUND_MODE = 65536; static const SUPPORT_SELECT_SOUND_MODE = 65536;
MediaPlayerEntity(Map rawData, String webHost) : super(rawData, webHost); MediaPlayerEntity(Map rawData) : super(rawData);
bool get supportPause => ((supportedFeatures & bool get supportPause => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_PAUSE) == MediaPlayerEntity.SUPPORT_PAUSE) ==
@ -74,34 +74,10 @@ class MediaPlayerEntity extends Entity {
List<String> get soundModeList => getStringListAttributeValue("sound_mode_list"); List<String> get soundModeList => getStringListAttributeValue("sound_mode_list");
List<String> get sourceList => getStringListAttributeValue("source_list"); List<String> get sourceList => getStringListAttributeValue("source_list");
DateTime get positionLastUpdated => DateTime.tryParse("${attributes["media_position_updated_at"]}")?.toLocal();
int get durationSeconds => _getIntAttributeValue("media_duration");
int get positionSeconds => _getIntAttributeValue("media_position");
@override @override
Widget _buildAdditionalControlsForPage(BuildContext context) { Widget _buildAdditionalControlsForPage(BuildContext context) {
return MediaPlayerControls(); return MediaPlayerControls();
} }
bool canCalculateActualPosition() {
return positionLastUpdated != null && durationSeconds != null && positionSeconds != null && durationSeconds > 0;
}
double getActualPosition() {
double result = 0;
Duration durationD;
Duration positionD;
durationD = Duration(seconds: durationSeconds);
positionD = Duration(
seconds: positionSeconds);
result = positionD.inSeconds.toDouble();
int differenceInSeconds = DateTime
.now()
.difference(positionLastUpdated)
.inSeconds;
result = ((result + differenceInSeconds) <= durationD.inSeconds) ? (result + differenceInSeconds) : durationD.inSeconds.toDouble();
return result;
}
} }

View File

@ -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 { class SensorEntity extends Entity {
@ -8,6 +12,6 @@ class SensorEntity extends Entity {
numericState: true numericState: true
); );
SensorEntity(Map rawData, String webHost) : super(rawData, webHost); SensorEntity(Map rawData) : super(rawData);
} }

View File

@ -1,11 +1,11 @@
part of '../../main.dart'; part of '../main.dart';
class SelectEntity extends Entity { class SelectEntity extends Entity {
List<String> get listOptions => attributes["options"] != null List<String> get listOptions => attributes["options"] != null
? (attributes["options"] as List).cast<String>() ? (attributes["options"] as List).cast<String>()
: []; : [];
SelectEntity(Map rawData, String webHost) : super(rawData, webHost); SelectEntity(Map rawData) : super(rawData);
@override @override
Widget _buildStatePart(BuildContext context) { Widget _buildStatePart(BuildContext context) {

View File

@ -1,7 +1,7 @@
part of '../../main.dart'; part of '../main.dart';
class SliderEntity extends Entity { class SliderEntity extends Entity {
SliderEntity(Map rawData, String webHost) : super(rawData, webHost); SliderEntity(Map rawData) : super(rawData);
double get minValue => _getDoubleAttributeValue("min") ?? 0.0; double get minValue => _getDoubleAttributeValue("min") ?? 0.0;
double get maxValue =>_getDoubleAttributeValue("max") ?? 100.0; double get maxValue =>_getDoubleAttributeValue("max") ?? 100.0;

View File

@ -1,7 +1,7 @@
part of '../../main.dart'; part of '../main.dart';
class SwitchEntity extends Entity { class SwitchEntity extends Entity {
SwitchEntity(Map rawData, String webHost) : super(rawData, webHost); SwitchEntity(Map rawData) : super(rawData);
@override @override
Widget _buildStatePart(BuildContext context) { Widget _buildStatePart(BuildContext context) {

View File

@ -1,7 +1,7 @@
part of '../../main.dart'; part of '../main.dart';
class TextEntity extends Entity { class TextEntity extends Entity {
TextEntity(Map rawData, String webHost) : super(rawData, webHost); TextEntity(Map rawData) : super(rawData);
int get valueMinLength => attributes["min"] ?? -1; int get valueMinLength => attributes["min"] ?? -1;
int get valueMaxLength => attributes["max"] ?? -1; int get valueMaxLength => attributes["max"] ?? -1;

View File

@ -1,13 +1,13 @@
part of '../../main.dart'; part of '../main.dart';
class TimerEntity extends Entity { class TimerEntity extends Entity {
TimerEntity(Map rawData, String webHost) : super(rawData, webHost); TimerEntity(Map rawData) : super(rawData);
Duration duration; Duration duration;
@override @override
void update(Map rawData, String webHost) { void update(Map rawData) {
super.update(rawData, webHost); super.update(rawData);
String durationSource = "${attributes["duration"]}"; String durationSource = "${attributes["duration"]}";
if (durationSource != null && durationSource.isNotEmpty) { if (durationSource != null && durationSource.isNotEmpty) {
try { try {

View File

@ -2,15 +2,13 @@ part of 'main.dart';
class EntityCollection { class EntityCollection {
final homeAssistantWebHost;
Map<String, Entity> _allEntities; Map<String, Entity> _allEntities;
//Map<String, Entity> views; //Map<String, Entity> views;
bool get isEmpty => _allEntities.isEmpty; bool get isEmpty => _allEntities.isEmpty;
List<Entity> get viewEntities => _allEntities.values.where((entity) => entity.isView).toList(); List<Entity> get viewEntities => _allEntities.values.where((entity) => entity.isView).toList();
EntityCollection(this.homeAssistantWebHost) { EntityCollection() {
_allEntities = {}; _allEntities = {};
//views = {}; //views = {};
} }
@ -35,77 +33,70 @@ class EntityCollection {
}); });
} }
void clear() {
_allEntities.clear();
}
Entity _createEntityInstance(rawEntityData) { Entity _createEntityInstance(rawEntityData) {
switch (rawEntityData["entity_id"].split(".")[0]) { switch (rawEntityData["entity_id"].split(".")[0]) {
case 'sun': { case 'sun': {
return SunEntity(rawEntityData, homeAssistantWebHost); return SunEntity(rawEntityData);
} }
case "media_player": { case "media_player": {
return MediaPlayerEntity(rawEntityData, homeAssistantWebHost); return MediaPlayerEntity(rawEntityData);
} }
case 'sensor': { case 'sensor': {
return SensorEntity(rawEntityData, homeAssistantWebHost); return SensorEntity(rawEntityData);
} }
case 'lock': { case 'lock': {
return LockEntity(rawEntityData, homeAssistantWebHost); return LockEntity(rawEntityData);
} }
case "automation": { case "automation": {
return AutomationEntity(rawEntityData, homeAssistantWebHost); return AutomationEntity(rawEntityData);
} }
case "input_boolean": case "input_boolean":
case "switch": { case "switch": {
return SwitchEntity(rawEntityData, homeAssistantWebHost); return SwitchEntity(rawEntityData);
} }
case "light": { case "light": {
return LightEntity(rawEntityData, homeAssistantWebHost); return LightEntity(rawEntityData);
} }
case "group": { case "group": {
return GroupEntity(rawEntityData, homeAssistantWebHost); return GroupEntity(rawEntityData);
} }
case "script": case "script":
case "scene": { case "scene": {
return ButtonEntity(rawEntityData, homeAssistantWebHost); return ButtonEntity(rawEntityData);
} }
case "input_datetime": { case "input_datetime": {
return DateTimeEntity(rawEntityData, homeAssistantWebHost); return DateTimeEntity(rawEntityData);
} }
case "input_select": { case "input_select": {
return SelectEntity(rawEntityData, homeAssistantWebHost); return SelectEntity(rawEntityData);
} }
case "input_number": { case "input_number": {
return SliderEntity(rawEntityData, homeAssistantWebHost); return SliderEntity(rawEntityData);
} }
case "input_text": { case "input_text": {
return TextEntity(rawEntityData, homeAssistantWebHost); return TextEntity(rawEntityData);
} }
case "climate": { case "climate": {
return ClimateEntity(rawEntityData, homeAssistantWebHost); return ClimateEntity(rawEntityData);
} }
case "cover": { case "cover": {
return CoverEntity(rawEntityData, homeAssistantWebHost); return CoverEntity(rawEntityData);
} }
case "fan": { case "fan": {
return FanEntity(rawEntityData, homeAssistantWebHost); return FanEntity(rawEntityData);
} }
case "camera": { case "camera": {
return CameraEntity(rawEntityData, homeAssistantWebHost); return CameraEntity(rawEntityData);
} }
case "alarm_control_panel": { case "alarm_control_panel": {
return AlarmControlPanelEntity(rawEntityData, homeAssistantWebHost); return AlarmControlPanelEntity(rawEntityData);
} }
case "timer": { case "timer": {
return TimerEntity(rawEntityData, homeAssistantWebHost); return TimerEntity(rawEntityData);
}
case "vacuum": {
return VacuumEntity(rawEntityData, homeAssistantWebHost);
} }
default: { default: {
return Entity(rawEntityData, homeAssistantWebHost); return Entity(rawEntityData);
} }
} }
} }
@ -130,7 +121,7 @@ class EntityCollection {
} }
void updateFromRaw(Map rawEntityData) { void updateFromRaw(Map rawEntityData) {
get("${rawEntityData["entity_id"]}")?.update(rawEntityData, homeAssistantWebHost); get("${rawEntityData["entity_id"]}")?.update(rawEntityData);
} }
Entity get(String entityId) { Entity get(String entityId) {
@ -152,12 +143,32 @@ class EntityCollection {
return _allEntities[entityId] != null; return _allEntities[entityId] != null;
} }
List<Entity> getByDomains({List<String> includeDomains: const [], List<String> excludeDomains: const [], List<String> stateFiler}) { List<Entity> filterEntitiesForDefaultView() {
return _allEntities.values.where((entity) { List<Entity> result = [];
return List<Entity> groups = [];
(excludeDomains.isEmpty || !excludeDomains.contains(entity.domain)) && List<Entity> nonGroupEntities = [];
(includeDomains.isEmpty || includeDomains.contains(entity.domain)) && _allEntities.forEach((id, entity){
((stateFiler != null && stateFiler.contains(entity.state)) || stateFiler == null); if (entity.isGroup && (entity.attributes['auto'] == null || (entity.attributes['auto'] && !entity.isHidden)) && (!entity.isView)) {
}).toList(); 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;
} }
} }

View 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,
);
}
}

View File

@ -1,4 +1,4 @@
part of '../main.dart'; part of '../../main.dart';
class BadgeWidget extends StatelessWidget { class BadgeWidget extends StatelessWidget {
@override @override
@ -7,7 +7,8 @@ class BadgeWidget extends StatelessWidget {
double iconSize = 26.0; double iconSize = 26.0;
Widget badgeIcon; Widget badgeIcon;
String onBadgeTextValue; 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) { switch (entityModel.entityWrapper.entity.domain) {
case "sun": case "sun":
{ {
@ -29,43 +30,30 @@ class BadgeWidget extends StatelessWidget {
badgeIcon = EntityIcon( badgeIcon = EntityIcon(
padding: EdgeInsets.all(0.0), padding: EdgeInsets.all(0.0),
size: iconSize, size: iconSize,
color: Theme.of(context).textTheme.body1.color color: Colors.black
); );
break; break;
} }
case "device_tracker": case "device_tracker":
case "person":
{ {
badgeIcon = EntityIcon( badgeIcon = EntityIcon(
padding: EdgeInsets.all(0.0), padding: EdgeInsets.all(0.0),
size: iconSize, size: iconSize,
color: Theme.of(context).textTheme.body1.color color: Colors.black
); );
onBadgeTextValue = entityModel.entityWrapper.entity.displayState; onBadgeTextValue = entityModel.entityWrapper.entity.state;
break; break;
} }
default: default:
{ {
double stateFontSize; onBadgeTextValue = entityModel.entityWrapper.entity.unitOfMeasurement;
if (entityModel.entityWrapper.entity.displayState.length <= 3) {
stateFontSize = 18.0;
} else if (entityModel.entityWrapper.entity.displayState.length <= 4) {
stateFontSize = 15.0;
} else if (entityModel.entityWrapper.entity.displayState.length <= 6) {
stateFontSize = 10.0;
} else if (entityModel.entityWrapper.entity.displayState.length <= 10) {
stateFontSize = 8.0;
}
onBadgeTextValue = entityModel.entityWrapper.unitOfMeasurement;
badgeIcon = Center( badgeIcon = Center(
child: Text( child: Text(
"${entityModel.entityWrapper.entity.displayState}", "${entityModel.entityWrapper.entity.state}",
overflow: TextOverflow.fade, overflow: TextOverflow.fade,
softWrap: false, softWrap: false,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.body1.copyWith( style: TextStyle(fontSize: 17.0),
fontSize: stateFontSize
)
), ),
); );
break; break;
@ -78,9 +66,7 @@ class BadgeWidget extends StatelessWidget {
onBadgeText = Container( onBadgeText = Container(
padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0), padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0),
child: Text("$onBadgeTextValue", child: Text("$onBadgeTextValue",
style: Theme.of(context).textTheme.overline.copyWith( style: TextStyle(fontSize: 12.0, color: Colors.white),
color: HAClientTheme().getOnBadgeTextColor()
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
softWrap: false, softWrap: false,
overflow: TextOverflow.fade), overflow: TextOverflow.fade),
@ -101,7 +87,7 @@ class BadgeWidget extends StatelessWidget {
decoration: new BoxDecoration( decoration: new BoxDecoration(
// Circle shape // Circle shape
shape: BoxShape.circle, shape: BoxShape.circle,
color: Theme.of(context).cardColor, color: Colors.white,
// The border you want // The border you want
border: new Border.all( border: new Border.all(
width: 2.0, width: 2.0,
@ -134,7 +120,7 @@ class BadgeWidget extends StatelessWidget {
child: Text( child: Text(
"${entityModel.entityWrapper.displayName}", "${entityModel.entityWrapper.displayName}",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.caption, style: TextStyle(fontSize: 12.0),
softWrap: true, softWrap: true,
maxLines: 3, maxLines: 3,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -143,6 +129,6 @@ class BadgeWidget extends StatelessWidget {
], ],
), ),
onTap: () => onTap: () =>
eventBus.fire(new ShowEntityPageEvent(entity: entityModel.entityWrapper.entity))); eventBus.fire(new ShowEntityPageEvent(entityModel.entityWrapper.entity)));
} }
} }

View 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();
}
}

View File

@ -1,4 +1,4 @@
part of '../main.dart'; part of '../../main.dart';
class FlatServiceButton extends StatelessWidget { class FlatServiceButton extends StatelessWidget {
@ -6,6 +6,7 @@ class FlatServiceButton extends StatelessWidget {
final String serviceName; final String serviceName;
final String entityId; final String entityId;
final String text; final String text;
final double fontSize;
FlatServiceButton({ FlatServiceButton({
Key key, Key key,
@ -13,16 +14,17 @@ class FlatServiceButton extends StatelessWidget {
@required this.serviceName, @required this.serviceName,
@required this.entityId, @required this.entityId,
@required this.text, @required this.text,
this.fontSize: Sizes.stateFontSize
}) : super(key: key); }) : super(key: key);
void _setNewState() { void _setNewState() {
ConnectionManager().callService(domain: serviceDomain, service: serviceName, entityId: entityId); eventBus.fire(new ServiceCallEvent(serviceDomain, serviceName, entityId, null));
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(
height: Theme.of(context).textTheme.subhead.fontSize*2.5, height: fontSize*2.5,
child: FlatButton( child: FlatButton(
onPressed: (() { onPressed: (() {
_setNewState(); _setNewState();
@ -30,7 +32,8 @@ class FlatServiceButton extends StatelessWidget {
child: Text( child: Text(
text, text,
textAlign: TextAlign.right, textAlign: TextAlign.right,
style: HAClientTheme().getActionTextStyle(context), style:
new TextStyle(fontSize: fontSize, color: Colors.blue),
), ),
) )
); );

View File

@ -10,7 +10,8 @@ class LastUpdatedWidget extends StatelessWidget {
child: Text( child: Text(
'${entityModel.entityWrapper.entity.lastUpdated}', '${entityModel.entityWrapper.entity.lastUpdated}',
textAlign: TextAlign.left, textAlign: TextAlign.left,
style: Theme.of(context).textTheme.caption style: TextStyle(
fontSize: Sizes.smallFontSize, color: Colors.black26),
), ),
); );
} }

View File

@ -1,4 +1,4 @@
part of '../../../main.dart'; part of '../../main.dart';
class LightColorPicker extends StatefulWidget { class LightColorPicker extends StatefulWidget {
@ -27,6 +27,7 @@ class LightColorPickerState extends State<LightColorPicker> {
List<Widget> colorRows = []; List<Widget> colorRows = [];
Border border; Border border;
bool isSomethingSelected = false; bool isSomethingSelected = false;
Logger.d("Current colotfor picker: [${widget.color.hue}, ${widget.color.saturation}]");
for (double saturation = 1.0; saturation >= (0.0 + widget.saturationStep); saturation = double.parse((saturation - widget.saturationStep).toStringAsFixed(2))) { for (double saturation = 1.0; saturation >= (0.0 + widget.saturationStep); saturation = double.parse((saturation - widget.saturationStep).toStringAsFixed(2))) {
List<Widget> rowChildren = []; List<Widget> rowChildren = [];
//Logger.d("$saturation"); //Logger.d("$saturation");

View File

@ -1,10 +1,12 @@
part of '../../../main.dart'; part of '../../main.dart';
class ModeSelectorWidget extends StatelessWidget { class ModeSelectorWidget extends StatelessWidget {
final String caption; final String caption;
final List options; final List<String> options;
final String value; final String value;
final double captionFontSize;
final double valueFontSize;
final onChange; final onChange;
final EdgeInsets padding; final EdgeInsets padding;
@ -14,6 +16,8 @@ class ModeSelectorWidget extends StatelessWidget {
@required this.options, @required this.options,
this.value, this.value,
@required this.onChange, @required this.onChange,
this.captionFontSize,
this.valueFontSize,
this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0), this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
}) : super(key: key); }) : super(key: key);
@ -24,7 +28,9 @@ class ModeSelectorWidget extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text("$caption", style: Theme.of(context).textTheme.body1), Text("$caption", style: TextStyle(
fontSize: captionFontSize ?? Sizes.stateFontSize
)),
Row( Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
@ -34,12 +40,15 @@ class ModeSelectorWidget extends StatelessWidget {
value: value, value: value,
iconSize: 30.0, iconSize: 30.0,
isExpanded: true, isExpanded: true,
style: Theme.of(context).textTheme.title, style: TextStyle(
fontSize: valueFontSize ?? Sizes.largeFontSize,
color: Colors.black,
),
hint: Text("Select ${caption.toLowerCase()}"), hint: Text("Select ${caption.toLowerCase()}"),
items: options.map((value) { items: options.map((String value) {
return new DropdownMenuItem<String>( return new DropdownMenuItem<String>(
value: '$value', value: value,
child: Text('$value'), child: Text(value),
); );
}).toList(), }).toList(),
onChanged: (mode) => onChange(mode), onChanged: (mode) => onChange(mode),

View File

@ -1,9 +1,10 @@
part of '../../../main.dart'; part of '../../main.dart';
class ModeSwitchWidget extends StatelessWidget { class ModeSwitchWidget extends StatelessWidget {
final String caption; final String caption;
final onChange; final onChange;
final double captionFontSize;
final bool value; final bool value;
final bool expanded; final bool expanded;
final EdgeInsets padding; final EdgeInsets padding;
@ -12,6 +13,7 @@ class ModeSwitchWidget extends StatelessWidget {
Key key, Key key,
@required this.caption, @required this.caption,
@required this.onChange, @required this.onChange,
this.captionFontSize,
this.value, this.value,
this.expanded: true, this.expanded: true,
this.padding: const EdgeInsets.only(left: Sizes.leftWidgetPadding, right: Sizes.rightWidgetPadding) this.padding: const EdgeInsets.only(left: Sizes.leftWidgetPadding, right: Sizes.rightWidgetPadding)
@ -23,7 +25,7 @@ class ModeSwitchWidget extends StatelessWidget {
padding: this.padding, padding: this.padding,
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
_buildCaption(context), _buildCaption(),
Switch( Switch(
onChanged: (value) => onChange(value), onChanged: (value) => onChange(value),
value: value ?? false, value: value ?? false,
@ -33,10 +35,12 @@ class ModeSwitchWidget extends StatelessWidget {
); );
} }
Widget _buildCaption(BuildContext context) { Widget _buildCaption() {
Widget captionWidget = Text( Widget captionWidget = Text(
"$caption", "$caption",
style: Theme.of(context).textTheme.body1, style: TextStyle(
fontSize: captionFontSize ?? Sizes.stateFontSize
),
); );
if (expanded) { if (expanded) {
return Expanded( return Expanded(

View File

@ -1,4 +1,4 @@
part of '../main.dart'; part of '../../main.dart';
class UniversalSlider extends StatelessWidget { class UniversalSlider extends StatelessWidget {
@ -40,7 +40,10 @@ class UniversalSlider extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Container(height: Sizes.rowPadding,), Container(height: Sizes.rowPadding,),
Text("$title"), Text(
"$title",
style: TextStyle(fontSize: Sizes.stateFontSize),
),
Container(height: Sizes.rowPadding,), Container(height: Sizes.rowPadding,),
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,

View File

@ -1,4 +1,4 @@
part of '../../../main.dart'; part of '../../main.dart';
class AlarmControlPanelControlsWidget extends StatefulWidget { class AlarmControlPanelControlsWidget extends StatefulWidget {
@ -25,12 +25,9 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
void _callService(AlarmControlPanelEntity entity, String service) { void _callService(AlarmControlPanelEntity entity, String service) {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, entity.domain, service, entity.entityId,
service: service, {"code": "$code"}));
entityId: entity.entityId,
data: {"code": "$code"}
);
setState(() { setState(() {
code = ""; code = "";
}); });
@ -61,11 +58,7 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
FlatButton( FlatButton(
child: new Text("Yes"), child: new Text("Yes"),
onPressed: () { onPressed: () {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(entity.domain, "alarm_trigger", entity.entityId, null));
domain: entity.domain,
service: "alarm_trigger",
entityId: entity.entityId
);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
@ -248,9 +241,7 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
FlatButton( FlatButton(
child: Text( child: Text(
"TRIGGER", "TRIGGER",
style: Theme.of(context).textTheme.subhead.copyWith( style: TextStyle(color: Colors.redAccent)
color: Theme.of(context).errorColor
)
), ),
onPressed: () => _askToTrigger(entity), onPressed: () => _askToTrigger(entity),
) )

View File

@ -1,4 +1,4 @@
part of '../../../main.dart'; part of '../../main.dart';
class ClimateControlWidget extends StatefulWidget { class ClimateControlWidget extends StatefulWidget {
@ -10,35 +10,35 @@ class ClimateControlWidget extends StatefulWidget {
class _ClimateControlWidgetState extends State<ClimateControlWidget> { class _ClimateControlWidgetState extends State<ClimateControlWidget> {
bool _temperaturePending = false; bool _showPending = false;
bool _changedHere = false; bool _changedHere = false;
Timer _resetTimer;
Timer _tempThrottleTimer; Timer _tempThrottleTimer;
Timer _targetTempThrottleTimer; Timer _targetTempThrottleTimer;
double _tmpTemperature = 0.0; double _tmpTemperature = 0.0;
double _tmpTargetLow = 0.0; double _tmpTargetLow = 0.0;
double _tmpTargetHigh = 0.0; double _tmpTargetHigh = 0.0;
double _tmpTargetHumidity = 0.0; double _tmpTargetHumidity = 0.0;
String _tmpHVACMode; String _tmpOperationMode;
String _tmpFanMode; String _tmpFanMode;
String _tmpSwingMode; String _tmpSwingMode;
String _tmpPresetMode; bool _tmpAwayMode = false;
//bool _tmpIsOff = false; bool _tmpIsOff = false;
bool _tmpAuxHeat = false; bool _tmpAuxHeat = false;
void _resetVars(ClimateEntity entity) { void _resetVars(ClimateEntity entity) {
if (!_temperaturePending) { _tmpTemperature = entity.temperature;
_tmpTemperature = entity.temperature; _tmpTargetHigh = entity.targetHigh;
_tmpTargetHigh = entity.targetHigh; _tmpTargetLow = entity.targetLow;
_tmpTargetLow = entity.targetLow; _tmpOperationMode = entity.operationMode;
}
_tmpHVACMode = entity.state;
_tmpFanMode = entity.fanMode; _tmpFanMode = entity.fanMode;
_tmpSwingMode = entity.swingMode; _tmpSwingMode = entity.swingMode;
_tmpPresetMode = entity.presetMode; _tmpAwayMode = entity.awayMode;
//_tmpIsOff = entity.isOff; _tmpIsOff = entity.isOff;
_tmpAuxHeat = entity.auxHeat; _tmpAuxHeat = entity.auxHeat;
_tmpTargetHumidity = entity.targetHumidity; _tmpTargetHumidity = entity.targetHumidity;
_showPending = false;
_changedHere = false; _changedHere = false;
} }
@ -73,44 +73,36 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
} }
void _setTemperature(ClimateEntity entity) { void _setTemperature(ClimateEntity entity) {
_tempThrottleTimer?.cancel(); if (_tempThrottleTimer!=null) {
_tempThrottleTimer.cancel();
}
setState(() { setState(() {
_changedHere = true; _changedHere = true;
_temperaturePending = true;
_tmpTemperature = double.parse(_tmpTemperature.toStringAsFixed(1)); _tmpTemperature = double.parse(_tmpTemperature.toStringAsFixed(1));
}); });
_tempThrottleTimer = Timer(Duration(seconds: 2), () { _tempThrottleTimer = Timer(Duration(seconds: 2), () {
setState(() { setState(() {
_changedHere = true; _changedHere = true;
_temperaturePending = false; eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"temperature": "${_tmpTemperature.toStringAsFixed(1)}"}));
ConnectionManager().callService( _resetStateTimer(entity);
domain: entity.domain,
service: "set_temperature",
entityId: entity.entityId,
data: {"temperature": "${_tmpTemperature.toStringAsFixed(1)}"}
);
}); });
}); });
} }
void _setTargetTemp(ClimateEntity entity) { void _setTargetTemp(ClimateEntity entity) {
_targetTempThrottleTimer?.cancel(); if (_targetTempThrottleTimer!=null) {
_targetTempThrottleTimer.cancel();
}
setState(() { setState(() {
_changedHere = true; _changedHere = true;
_temperaturePending = true;
_tmpTargetLow = double.parse(_tmpTargetLow.toStringAsFixed(1)); _tmpTargetLow = double.parse(_tmpTargetLow.toStringAsFixed(1));
_tmpTargetHigh = double.parse(_tmpTargetHigh.toStringAsFixed(1)); _tmpTargetHigh = double.parse(_tmpTargetHigh.toStringAsFixed(1));
}); });
_targetTempThrottleTimer = Timer(Duration(seconds: 2), () { _targetTempThrottleTimer = Timer(Duration(seconds: 2), () {
setState(() { setState(() {
_changedHere = true; _changedHere = true;
_temperaturePending = false; eventBus.fire(new ServiceCallEvent(entity.domain, "set_temperature", entity.entityId,{"target_temp_high": "${_tmpTargetHigh.toStringAsFixed(1)}", "target_temp_low": "${_tmpTargetLow.toStringAsFixed(1)}"}));
ConnectionManager().callService( _resetStateTimer(entity);
domain: entity.domain,
service: "set_temperature",
entityId: entity.entityId,
data: {"target_temp_high": "${_tmpTargetHigh.toStringAsFixed(1)}", "target_temp_low": "${_tmpTargetLow.toStringAsFixed(1)}"}
);
}); });
}); });
} }
@ -119,25 +111,17 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
setState(() { setState(() {
_tmpTargetHumidity = value.roundToDouble(); _tmpTargetHumidity = value.roundToDouble();
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(entity.domain, "set_humidity", entity.entityId,{"humidity": "$_tmpTargetHumidity"}));
domain: entity.domain, _resetStateTimer(entity);
service: "set_humidity",
entityId: entity.entityId,
data: {"humidity": "$_tmpTargetHumidity"}
);
}); });
} }
void _setHVACMode(ClimateEntity entity, value) { void _setOperationMode(ClimateEntity entity, value) {
setState(() { setState(() {
_tmpHVACMode = value; _tmpOperationMode = value;
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(entity.domain, "set_operation_mode", entity.entityId,{"operation_mode": "$_tmpOperationMode"}));
domain: entity.domain, _resetStateTimer(entity);
service: "set_hvac_mode",
entityId: entity.entityId,
data: {"hvac_mode": "$_tmpHVACMode"}
);
}); });
} }
@ -145,12 +129,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
setState(() { setState(() {
_tmpSwingMode = value; _tmpSwingMode = value;
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(entity.domain, "set_swing_mode", entity.entityId,{"swing_mode": "$_tmpSwingMode"}));
domain: entity.domain, _resetStateTimer(entity);
service: "set_swing_mode",
entityId: entity.entityId,
data: {"swing_mode": "$_tmpSwingMode"}
);
}); });
} }
@ -158,32 +138,45 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
setState(() { setState(() {
_tmpFanMode = value; _tmpFanMode = value;
_changedHere = true; _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(() { setState(() {
_tmpPresetMode = value; _tmpAwayMode = value;
_changedHere = true; _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(() { setState(() {
_tmpIsOff = !value; _tmpIsOff = !value;
_changedHere = true; _changedHere = true;
eventBus.fire(new ServiceCallEvent(entity.domain, "${_tmpIsOff ? 'turn_off' : 'turn_on'}", entity.entityId, null)); eventBus.fire(new ServiceCallEvent(entity.domain, "${_tmpIsOff ? 'turn_off' : 'turn_on'}", entity.entityId, null));
_resetStateTimer(entity); _resetStateTimer(entity);
}); });
}*/ }
void _setAuxHeat(ClimateEntity entity, value) { void _setAuxHeat(ClimateEntity entity, value) {
setState(() { setState(() {
_tmpAuxHeat = value; _tmpAuxHeat = value;
_changedHere = true; _changedHere = true;
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) { Widget build(BuildContext context) {
final entityModel = EntityModel.of(context); final entityModel = EntityModel.of(context);
final ClimateEntity entity = entityModel.entityWrapper.entity; final ClimateEntity entity = entityModel.entityWrapper.entity;
Logger.d("[Climate widget build] changed here = $_changedHere");
if (_changedHere) { if (_changedHere) {
//_showPending = (_tmpTemperature != entity.temperature || _tmpTargetHigh != entity.targetHigh || _tmpTargetLow != entity.targetLow); _showPending = (_tmpTemperature != entity.temperature || _tmpTargetHigh != entity.targetHigh || _tmpTargetLow != entity.targetLow);
_changedHere = false; _changedHere = false;
} else { } else {
_resetTimer?.cancel();
_resetVars(entity); _resetVars(entity);
} }
return Padding( return Padding(
@ -203,34 +196,33 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
//_buildOnOffControl(entity), _buildOnOffControl(entity),
_buildTemperatureControls(entity, context), _buildTemperatureControls(entity),
_buildTargetTemperatureControls(entity, context), _buildTargetTemperatureControls(entity),
_buildHumidityControls(entity, context), _buildHumidityControls(entity),
_buildOperationControl(entity, context), _buildOperationControl(entity),
_buildFanControl(entity, context), _buildFanControl(entity),
_buildSwingControl(entity, context), _buildSwingControl(entity),
_buildPresetModeControl(entity, context), _buildAwayModeControl(entity),
_buildAuxHeatControl(entity, context) _buildAuxHeatControl(entity)
], ],
), ),
); );
} }
Widget _buildPresetModeControl(ClimateEntity entity, BuildContext context) { Widget _buildAwayModeControl(ClimateEntity entity) {
if (entity.supportPresetMode) { if (entity.supportAwayMode) {
return ModeSelectorWidget( return ModeSwitchWidget(
options: entity.presetModes, caption: "Away mode",
onChange: (mode) => _setPresetMode(entity, mode), onChange: (value) => _setAwayMode(entity, value),
caption: "Preset", value: _tmpAwayMode,
value: _tmpPresetMode,
); );
} else { } else {
return Container(height: 0.0, width: 0.0,); return Container(height: 0.0, width: 0.0,);
} }
} }
/*Widget _buildOnOffControl(ClimateEntity entity) { Widget _buildOnOffControl(ClimateEntity entity) {
if (entity.supportOnOff) { if (entity.supportOnOff) {
return ModeSwitchWidget( return ModeSwitchWidget(
onChange: (value) => _setOnOf(entity, value), onChange: (value) => _setOnOf(entity, value),
@ -240,9 +232,9 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
} else { } else {
return Container(height: 0.0, width: 0.0,); return Container(height: 0.0, width: 0.0,);
} }
}*/ }
Widget _buildAuxHeatControl(ClimateEntity entity, BuildContext context) { Widget _buildAuxHeatControl(ClimateEntity entity) {
if (entity.supportAuxHeat ) { if (entity.supportAuxHeat ) {
return ModeSwitchWidget( return ModeSwitchWidget(
caption: "Aux heat", caption: "Aux heat",
@ -254,23 +246,23 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
} }
} }
Widget _buildOperationControl(ClimateEntity entity, BuildContext context) { Widget _buildOperationControl(ClimateEntity entity) {
if (entity.hvacModes != null) { if (entity.supportOperationMode) {
return ModeSelectorWidget( return ModeSelectorWidget(
onChange: (mode) => _setHVACMode(entity, mode), onChange: (mode) => _setOperationMode(entity, mode),
options: entity.hvacModes, options: entity.operationList,
caption: "Operation", caption: "Operation",
value: _tmpHVACMode, value: _tmpOperationMode,
); );
} else { } else {
return Container(height: 0.0, width: 0.0); return Container(height: 0.0, width: 0.0);
} }
} }
Widget _buildFanControl(ClimateEntity entity, BuildContext context) { Widget _buildFanControl(ClimateEntity entity) {
if (entity.supportFanMode) { if (entity.supportFanMode) {
return ModeSelectorWidget( return ModeSelectorWidget(
options: entity.fanModes, options: entity.fanList,
onChange: (mode) => _setFanMode(entity, mode), onChange: (mode) => _setFanMode(entity, mode),
caption: "Fan mode", caption: "Fan mode",
value: _tmpFanMode, value: _tmpFanMode,
@ -280,11 +272,11 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
} }
} }
Widget _buildSwingControl(ClimateEntity entity, BuildContext context) { Widget _buildSwingControl(ClimateEntity entity) {
if (entity.supportSwingMode) { if (entity.supportSwingMode) {
return ModeSelectorWidget( return ModeSelectorWidget(
onChange: (mode) => _setSwingMode(entity, mode), onChange: (mode) => _setSwingMode(entity, mode),
options: entity.swingModes, options: entity.swingList,
value: _tmpSwingMode, value: _tmpSwingMode,
caption: "Swing mode" 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)) { if ((entity.supportTargetTemperature) && (entity.temperature != null)) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text("Target temperature", style: Theme.of(context).textTheme.body1), Text("Target temperature", style: TextStyle(
fontSize: Sizes.stateFontSize
)),
TemperatureControlWidget( TemperatureControlWidget(
value: _tmpTemperature, value: _tmpTemperature,
active: _temperaturePending, fontColor: _showPending ? Colors.red : Colors.black,
onDec: () => _temperatureDown(entity), onDec: () => _temperatureDown(entity),
onInc: () => _temperatureUp(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 = []; List<Widget> controls = [];
if ((entity.supportTargetTemperatureRange) && (entity.targetLow != null)) { if ((entity.supportTargetTemperatureLow) && (entity.targetLow != null)) {
controls.addAll(<Widget>[ controls.addAll(<Widget>[
TemperatureControlWidget( TemperatureControlWidget(
value: _tmpTargetLow, value: _tmpTargetLow,
active: _temperaturePending, fontColor: _showPending ? Colors.red : Colors.black,
onDec: () => _targetLowDown(entity), onDec: () => _targetLowDown(entity),
onInc: () => _targetLowUp(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( controls.add(
TemperatureControlWidget( TemperatureControlWidget(
value: _tmpTargetHigh, value: _tmpTargetHigh,
active: _temperaturePending, fontColor: _showPending ? Colors.red : Colors.black,
onDec: () => _targetHighDown(entity), onDec: () => _targetHighDown(entity),
onInc: () => _targetHighUp(entity), onInc: () => _targetHighUp(entity),
) )
@ -341,7 +335,9 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text("Target temperature range", style: Theme.of(context).textTheme.body1), Text("Target temperature range", style: TextStyle(
fontSize: Sizes.stateFontSize
)),
Row( Row(
children: controls, children: controls,
) )
@ -352,13 +348,13 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
} }
} }
Widget _buildHumidityControls(ClimateEntity entity, BuildContext context) { Widget _buildHumidityControls(ClimateEntity entity) {
List<Widget> result = []; List<Widget> result = [];
if (entity.supportTargetHumidity) { if (entity.supportTargetHumidity) {
result.addAll(<Widget>[ result.addAll(<Widget>[
Text( Text(
"$_tmpTargetHumidity%", "$_tmpTargetHumidity%",
style: Theme.of(context).textTheme.display1, style: TextStyle(fontSize: Sizes.largeFontSize),
), ),
Expanded( Expanded(
child: Slider( child: Slider(
@ -383,7 +379,9 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
Padding( Padding(
padding: EdgeInsets.fromLTRB( padding: EdgeInsets.fromLTRB(
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding), 0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
child: Text("Target humidity", style: Theme.of(context).textTheme.body1), child: Text("Target humidity", style: TextStyle(
fontSize: Sizes.stateFontSize
)),
), ),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
@ -405,7 +403,57 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
@override @override
void dispose() { void dispose() {
_resetTimer?.cancel();
super.dispose(); super.dispose();
} }
}
class TemperatureControlWidget extends StatelessWidget {
final double value;
final double fontSize;
final Color fontColor;
final onInc;
final onDec;
TemperatureControlWidget(
{Key key,
@required this.value,
@required this.onInc,
@required this.onDec,
this.fontSize,
this.fontColor})
: super(key: key);
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
"$value",
style: TextStyle(
fontSize: fontSize ?? 24.0,
color: fontColor ?? Colors.black
),
),
Column(
children: <Widget>[
IconButton(
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
'mdi:chevron-up')),
iconSize: 30.0,
onPressed: () => onInc(),
),
IconButton(
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
'mdi:chevron-down')),
iconSize: 30.0,
onPressed: () => onDec(),
)
],
)
],
);
}
} }

View File

@ -1,4 +1,4 @@
part of '../../../main.dart'; part of '../../main.dart';
class CoverControlWidget extends StatefulWidget { class CoverControlWidget extends StatefulWidget {
@ -18,7 +18,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
setState(() { setState(() {
_tmpPosition = position.roundToDouble(); _tmpPosition = position.roundToDouble();
_changedHere = true; _changedHere = true;
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(() { setState(() {
_tmpTiltPosition = position.roundToDouble(); _tmpTiltPosition = position.roundToDouble();
_changedHere = true; _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(
padding: EdgeInsets.fromLTRB( padding: EdgeInsets.fromLTRB(
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding), 0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
child: Text("Position"), child: Text("Position", style: TextStyle(
fontSize: Sizes.stateFontSize
)),
), ),
Slider( Slider(
value: _tmpPosition, value: _tmpPosition,
@ -116,7 +118,9 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
controls.insert(0, Padding( controls.insert(0, Padding(
padding: EdgeInsets.fromLTRB( padding: EdgeInsets.fromLTRB(
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding), 0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
child: Text("Tilt position"), child: Text("Tilt position", style: TextStyle(
fontSize: Sizes.stateFontSize
)),
)); ));
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -131,18 +135,18 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
class CoverTiltControlsWidget extends StatelessWidget { class CoverTiltControlsWidget extends StatelessWidget {
void _open(CoverEntity entity) { void _open(CoverEntity entity) {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, service: "open_cover_tilt", entityId: entity.entityId); entity.domain, "open_cover_tilt", entity.entityId, null));
} }
void _close(CoverEntity entity) { void _close(CoverEntity entity) {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, service: "close_cover_tilt", entityId: entity.entityId); entity.domain, "close_cover_tilt", entity.entityId, null));
} }
void _stop(CoverEntity entity) { void _stop(CoverEntity entity) {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, service: "stop_cover_tilt", entityId: entity.entityId); entity.domain, "stop_cover_tilt", entity.entityId, null));
} }
@override @override

View File

@ -1,4 +1,4 @@
part of '../../../main.dart'; part of '../../main.dart';
class FanControlsWidget extends StatefulWidget { class FanControlsWidget extends StatefulWidget {
@ -24,12 +24,9 @@ class _FanControlsWidgetState extends State<FanControlsWidget> {
setState(() { setState(() {
_tmpOscillate = oscillate; _tmpOscillate = oscillate;
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: "fan", "fan", "oscillate", entity.entityId,
service: "oscillate", {"oscillating": oscillate}));
entityId: entity.entityId,
data: {"oscillating": oscillate}
);
}); });
} }
@ -37,12 +34,9 @@ class _FanControlsWidgetState extends State<FanControlsWidget> {
setState(() { setState(() {
_tmpDirectionForward = forward; _tmpDirectionForward = forward;
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: "fan", "fan", "set_direction", entity.entityId,
service: "set_direction", {"direction": forward ? "forward" : "reverse"}));
entityId: entity.entityId,
data: {"direction": forward ? "forward" : "reverse"}
);
}); });
} }
@ -50,12 +44,9 @@ class _FanControlsWidgetState extends State<FanControlsWidget> {
setState(() { setState(() {
_tmpSpeed = value; _tmpSpeed = value;
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: "fan", "fan", "set_speed", entity.entityId,
service: "set_speed", {"speed": value}));
entityId: entity.entityId,
data: {"speed": value}
);
}); });
} }

View File

@ -1,4 +1,4 @@
part of '../../../main.dart'; part of '../../main.dart';
class LightControlsWidget extends StatefulWidget { class LightControlsWidget extends StatefulWidget {
@ -17,7 +17,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
String _tmpEffect; String _tmpEffect;
void _resetState(LightEntity entity) { void _resetState(LightEntity entity) {
_tmpBrightness = entity.brightness ?? 1; _tmpBrightness = entity.brightness ?? 0;
_tmpWhiteValue = entity.whiteValue ?? 0; _tmpWhiteValue = entity.whiteValue ?? 0;
_tmpColorTemp = entity.colorTemp ?? entity.minMireds?.toInt(); _tmpColorTemp = entity.colorTemp ?? entity.minMireds?.toInt();
_tmpColor = entity.color ?? _tmpColor; _tmpColor = entity.color ?? _tmpColor;
@ -28,12 +28,15 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
setState(() { setState(() {
_tmpBrightness = value.round(); _tmpBrightness = value.round();
_changedHere = true; _changedHere = true;
ConnectionManager().callService( if (_tmpBrightness > 0) {
domain: entity.domain, eventBus.fire(new ServiceCallEvent(
service: "turn_on", entity.domain, "turn_on", entity.entityId,
entityId: entity.entityId, {"brightness": _tmpBrightness}));
data: {"brightness": _tmpBrightness} } else {
); eventBus.fire(new ServiceCallEvent(
entity.domain, "turn_off", entity.entityId,
null));
}
}); });
} }
@ -41,12 +44,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
setState(() { setState(() {
_tmpWhiteValue = value.round(); _tmpWhiteValue = value.round();
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, entity.domain, "turn_on", entity.entityId,
service: "turn_on", {"white_value": _tmpWhiteValue}));
entityId: entity.entityId,
data: {"white_value": _tmpWhiteValue}
);
}); });
} }
@ -55,12 +55,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
setState(() { setState(() {
_tmpColorTemp = value.round(); _tmpColorTemp = value.round();
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, entity.domain, "turn_on", entity.entityId,
service: "turn_on", {"color_temp": _tmpColorTemp}));
entityId: entity.entityId,
data: {"color_temp": _tmpColorTemp}
);
}); });
} }
@ -68,12 +65,10 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
setState(() { setState(() {
_tmpColor = color; _tmpColor = color;
_changedHere = true; _changedHere = true;
ConnectionManager().callService( Logger.d( "HS Color: [${color.hue}, ${color.saturation}]");
domain: entity.domain, eventBus.fire(new ServiceCallEvent(
service: "turn_on", entity.domain, "turn_on", entity.entityId,
entityId: entity.entityId, {"hs_color": [color.hue, color.saturation*100]}));
data: {"hs_color": [color.hue, color.saturation*100]}
);
}); });
} }
@ -82,12 +77,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
_tmpEffect = value; _tmpEffect = value;
_changedHere = true; _changedHere = true;
if (_tmpEffect != null) { if (_tmpEffect != null) {
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, entity.domain, "turn_on", entity.entityId,
service: "turn_on", {"effect": "$value"}));
entityId: entity.entityId,
data: {"effect": "$value"}
);
} }
}); });
} }
@ -114,19 +106,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
} }
Widget _buildBrightnessControl(LightEntity entity) { Widget _buildBrightnessControl(LightEntity entity) {
if (entity.supportBrightness) { if ((entity.supportBrightness) && (_tmpBrightness != null)) {
double val;
if (_tmpBrightness != null) {
if (_tmpBrightness > 255) {
val = 255;
} else if (_tmpBrightness < 1) {
val = 1;
} else {
val = _tmpBrightness.toDouble();
}
} else {
val = 1;
}
return UniversalSlider( return UniversalSlider(
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
@ -134,10 +114,10 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
_tmpBrightness = value.round(); _tmpBrightness = value.round();
}); });
}, },
min: 1.0, min: 0.0,
max: 255.0, max: 255.0,
onChangeEnd: (value) => _setBrightness(entity, value), onChangeEnd: (value) => _setBrightness(entity, value),
value: val, value: _tmpBrightness == null ? 0.0 : _tmpBrightness.toDouble(),
leading: Icon(Icons.brightness_5), leading: Icon(Icons.brightness_5),
title: "Brightness", title: "Brightness",
); );
@ -169,22 +149,10 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
Widget _buildColorTempControl(LightEntity entity) { Widget _buildColorTempControl(LightEntity entity) {
if (entity.supportColorTemp) { if (entity.supportColorTemp) {
double val;
if (_tmpColorTemp != null) {
if (_tmpColorTemp > entity.maxMireds) {
val = entity.maxMireds;
} else if (_tmpColorTemp < entity.minMireds) {
val = entity.minMireds;
} else {
val = _tmpColorTemp.toDouble();
}
} else {
val = entity.minMireds;
}
return UniversalSlider( return UniversalSlider(
title: "Color temperature", title: "Color temperature",
leading: Text("Cold", style: Theme.of(context).textTheme.body1.copyWith(color: Colors.lightBlue)), leading: Text("Cold", style: TextStyle(color: Colors.lightBlue),),
value: val, value: _tmpColorTemp == null ? entity.maxMireds : _tmpColorTemp.toDouble(),
onChangeEnd: (value) => _setColorTemp(entity, value), onChangeEnd: (value) => _setColorTemp(entity, value),
max: entity.maxMireds, max: entity.maxMireds,
min: entity.minMireds, min: entity.minMireds,
@ -194,7 +162,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
_tmpColorTemp = value.round(); _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 { } else {
return Container(width: 0.0, height: 0.0); return Container(width: 0.0, height: 0.0);
@ -203,7 +171,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
Widget _buildColorControl(LightEntity entity) { Widget _buildColorControl(LightEntity entity) {
if (entity.supportColor) { if (entity.supportColor) {
HSVColor savedColor = HomeAssistant().savedColor; HSVColor savedColor = HomeAssistantModel.of(context)?.homeAssistant?.savedColor;
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
@ -219,12 +187,15 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
child: Text('Copy color'), child: Text('Copy color'),
onPressed: _tmpColor == null ? null : () { onPressed: _tmpColor == null ? null : () {
setState(() { setState(() {
HomeAssistant().savedColor = _tmpColor; HomeAssistantModel
.of(context)
.homeAssistant
.savedColor = _tmpColor;
}); });
}, },
), ),
FlatButton( FlatButton(
color: savedColor?.toColor() ?? Theme.of(context).backgroundColor, color: savedColor?.toColor() ?? Colors.transparent,
child: Text('Paste color'), child: Text('Paste color'),
onPressed: savedColor == null ? null : () { onPressed: savedColor == null ? null : () {
_setColor(entity, savedColor); _setColor(entity, savedColor);
@ -241,14 +212,10 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
Widget _buildEffectControl(LightEntity entity) { Widget _buildEffectControl(LightEntity entity) {
if ((entity.supportEffect) && (entity.effectList != null)) { if ((entity.supportEffect) && (entity.effectList != null)) {
List<String> list = List.from(entity.effectList);
if (_tmpEffect!= null && !list.contains(_tmpEffect)) {
list.insert(0, _tmpEffect);
}
return ModeSelectorWidget( return ModeSelectorWidget(
onChange: (effect) => _setEffect(entity, effect), onChange: (effect) => _setEffect(entity, effect),
caption: "Effect", caption: "Effect",
options: list, options: entity.effectList,
value: _tmpEffect value: _tmpEffect
); );
} else { } else {

View File

@ -1,7 +1,7 @@
part of '../../../main.dart'; part of '../../main.dart';
class MediaPlayerWidget extends StatelessWidget { class MediaPlayerWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final EntityModel entityModel = EntityModel.of(context); final EntityModel entityModel = EntityModel.of(context);
@ -12,21 +12,21 @@ class MediaPlayerWidget extends StatelessWidget {
Stack( Stack(
alignment: AlignmentDirectional.topEnd, alignment: AlignmentDirectional.topEnd,
children: <Widget>[ children: <Widget>[
_buildImage(entity, context), _buildImage(entity),
Positioned( Positioned(
bottom: 0.0, bottom: 0.0,
left: 0.0, left: 0.0,
right: 0.0, right: 0.0,
child: Container( child: Container(
color: Colors.black45, color: Colors.black45,
child: _buildState(entity, context), child: _buildState(entity),
), ),
), ),
Positioned( Positioned(
bottom: 0.0, bottom: 0.0,
left: 0.0, left: 0.0,
right: 0.0, right: 0.0,
child: MediaPlayerProgressBar() child: MediaPlayerProgressWidget()
) )
], ],
), ),
@ -35,9 +35,12 @@ class MediaPlayerWidget extends StatelessWidget {
); );
} }
Widget _buildState(MediaPlayerEntity entity, BuildContext context) { Widget _buildState(MediaPlayerEntity entity) {
TextStyle style = Theme.of(context).textTheme.body1.copyWith( TextStyle style = TextStyle(
color: Colors.white fontSize: 14.0,
color: Colors.white,
fontWeight: FontWeight.normal,
height: 1.2
); );
List<Widget> states = []; List<Widget> states = [];
states.add(Text("${entity.displayName}", style: style)); states.add(Text("${entity.displayName}", style: style));
@ -68,9 +71,9 @@ class MediaPlayerWidget extends StatelessWidget {
); );
} }
Widget _buildImage(MediaPlayerEntity entity, BuildContext context) { Widget _buildImage(MediaPlayerEntity entity) {
String state = entity.state; 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( return Container(
color: Colors.black, color: Colors.black,
child: Row( child: Row(
@ -94,7 +97,7 @@ class MediaPlayerWidget extends StatelessWidget {
Icon( Icon(
MaterialDesignIcons.getIconDataFromIconName("mdi:movie"), MaterialDesignIcons.getIconDataFromIconName("mdi:movie"),
size: 150.0, size: 150.0,
color: HAClientTheme().getColorByEntityState("$state", context), color: EntityColor.stateColor("$state"),
) )
], ],
); );
@ -115,28 +118,26 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
void _setPower(MediaPlayerEntity entity) { void _setPower(MediaPlayerEntity entity) {
if (entity.state != EntityState.unavailable && entity.state != EntityState.unknown) {
if (entity.state == EntityState.off) { if (entity.state == EntityState.off) {
ConnectionManager().callService( Logger.d("${entity.entityId} turn_on");
domain: entity.domain, eventBus.fire(new ServiceCallEvent(
service: "turn_on", entity.domain, "turn_on", entity.entityId,
entityId: entity.entityId null));
);
} else { } else {
ConnectionManager().callService( Logger.d("${entity.entityId} turn_off");
domain: entity.domain, eventBus.fire(new ServiceCallEvent(
service: "turn_off", entity.domain, "turn_off", entity.entityId,
entityId: entity.entityId null));
);
} }
}
} }
void _callAction(MediaPlayerEntity entity, String action) { void _callAction(MediaPlayerEntity entity, String action) {
Logger.d("${entity.entityId} $action"); Logger.d("${entity.entityId} $action");
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(
domain: entity.domain, entity.domain, "$action", entity.entityId,
service: "$action", null));
entityId: entity.entityId
);
} }
@override @override
@ -228,7 +229,7 @@ class MediaPlayerPlaybackControls extends StatelessWidget {
IconButton( IconButton(
icon: Icon(MaterialDesignIcons.getIconDataFromIconName( icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
"mdi:dots-vertical")), "mdi:dots-vertical")),
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity: entity)) onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity))
) )
); );
} else if (entity.supportStop && entity.state != EntityState.off && entity.state != EntityState.unavailable) { } else if (entity.supportStop && entity.state != EntityState.off && entity.state != EntityState.unavailable) {
@ -263,50 +264,27 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
setState(() { setState(() {
_changedHere = true; _changedHere = true;
_newVolumeLevel = value; _newVolumeLevel = value;
ConnectionManager().callService( eventBus.fire(ServiceCallEvent("media_player", "volume_set", entityId, {"volume_level": value}));
domain: "media_player",
service: "volume_set",
entityId: entityId,
data: {"volume_level": value}
);
}); });
} }
void _setVolumeMute(bool isMuted, String entityId) { void _setVolumeMute(bool isMuted, String entityId) {
ConnectionManager().callService( eventBus.fire(ServiceCallEvent("media_player", "volume_mute", entityId, {"is_volume_muted": isMuted}));
domain: "media_player",
service: "volume_mute",
entityId: entityId,
data: {"is_volume_muted": isMuted}
);
} }
void _setVolumeUp(String entityId) { void _setVolumeUp(String entityId) {
ConnectionManager().callService( eventBus.fire(ServiceCallEvent("media_player", "volume_up", entityId, null));
domain: "media_player",
service: "volume_up",
entityId: entityId
);
} }
void _setVolumeDown(String entityId) { void _setVolumeDown(String entityId) {
ConnectionManager().callService( eventBus.fire(ServiceCallEvent("media_player", "volume_down", entityId, null));
domain: "media_player",
service: "volume_down",
entityId: entityId
);
} }
void _setSoundMode(String value, String entityId) { void _setSoundMode(String value, String entityId) {
setState(() { setState(() {
_newSoundMode = value; _newSoundMode = value;
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(ServiceCallEvent("media_player", "select_sound_mode", entityId, {"sound_mode": "$value"}));
domain: "media_player",
service: "select_sound_mode",
entityId: entityId,
data: {"sound_mode": "$value"}
);
}); });
} }
@ -314,12 +292,7 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
setState(() { setState(() {
_newSource = source; _newSource = source;
_changedHere = true; _changedHere = true;
ConnectionManager().callService( eventBus.fire(ServiceCallEvent("media_player", "select_source", entityId, {"source": "$source"}));
domain: "media_player",
service: "select_source",
entityId: entityId,
data: {"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.state != EntityState.off && entity.state != EntityState.unknown && entity.state != EntityState.unavailable) {
if (entity.supportSeek) {
children.add(MediaPlayerSeekBar());
} else {
children.add(MediaPlayerProgressBar());
}
Widget muteWidget; Widget muteWidget;
Widget volumeStepWidget; Widget volumeStepWidget;
if (entity.supportVolumeMute || entity.attributes["is_volume_muted"] != null) { if (entity.supportVolumeMute || entity.attributes["is_volume_muted"] != null) {
@ -353,13 +321,13 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
volumeStepWidget = Row( volumeStepWidget = Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
IconButton(
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:minus")),
onPressed: () => _setVolumeDown(entity.entityId)
),
IconButton( IconButton(
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:plus")), icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:plus")),
onPressed: () => _setVolumeUp(entity.entityId) 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( return Column(
children: children, children: children,
); );
} }
void _duplicateTo(entity) { }
if (entity.canCalculateActualPosition()) {
HomeAssistant().savedPlayerPosition = entity.getActualPosition().toInt(); class MediaPlayerProgressWidget extends StatefulWidget {
} else { @override
HomeAssistant().savedPlayerPosition = 0; _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;
} }
Navigator.of(context).pushNamed("/play-media", arguments: { return LinearProgressIndicator(
"url": entity.attributes["media_content_id"], value: progress,
"type": entity.attributes["media_content_type"] backgroundColor: Colors.black45,
}); );
} }
void _switchTo(entity) { @override
HomeAssistant().sendFromPlayerId = entity.entityId; void dispose() {
_duplicateTo(entity); _timer?.cancel();
super.dispose();
} }
} }

View File

@ -1,4 +1,4 @@
part of '../../../main.dart'; part of '../../main.dart';
class SliderControlsWidget extends StatefulWidget { class SliderControlsWidget extends StatefulWidget {
@ -18,12 +18,8 @@ class _SliderControlsWidgetState extends State<SliderControlsWidget> {
_newValue = newValue; _newValue = newValue;
_changedHere = true; _changedHere = true;
}); });
ConnectionManager().callService( eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId,
domain: domain, {"value": "${newValue.toString()}"}));
service: "set_value",
entityId: entityId,
data: {"value": "${newValue.toString()}"}
);
} }
@override @override
@ -62,7 +58,8 @@ class _SliderControlsWidgetState extends State<SliderControlsWidget> {
children: <Widget>[ children: <Widget>[
Text( Text(
"$_newValue", "$_newValue",
style: Theme.of(context).textTheme.display1.copyWith( style: TextStyle(
fontSize: Sizes.largeFontSize,
color: Colors.blue color: Colors.blue
), ),
), ),

View 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
],
),
);
}
}

View 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];
}
}
}
}

View File

@ -67,7 +67,7 @@ class EntityIcon extends StatelessWidget {
padding: padding, padding: padding,
child: buildIcon( child: buildIcon(
entityWrapper, entityWrapper,
color ?? HAClientTheme().getColorByEntityState(entityWrapper.entity.state, context) color ?? EntityColor.stateColor(entityWrapper.entity.state)
), ),
); );
} }

View File

@ -5,24 +5,18 @@ class EntityName extends StatelessWidget {
final EdgeInsetsGeometry padding; final EdgeInsetsGeometry padding;
final TextOverflow textOverflow; final TextOverflow textOverflow;
final bool wordsWrap; final bool wordsWrap;
final double fontSize;
final TextAlign textAlign; final TextAlign textAlign;
final int maxLines; final int maxLines;
final TextStyle textStyle;
const EntityName({Key key, this.maxLines, this.padding: const EdgeInsets.only(right: 10.0), this.textOverflow: TextOverflow.ellipsis, this.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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper; final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
TextStyle tStyle; TextStyle textStyle = TextStyle(fontSize: fontSize);
if (textStyle == null) { if (entityWrapper.entity.statelessType == StatelessEntityType.WEBLINK) {
if (entityWrapper.entity.statelessType == StatelessEntityType.WEBLINK) { textStyle = textStyle.apply(color: Colors.blue, decoration: TextDecoration.underline);
tStyle = HAClientTheme().getLinkTextStyle(context);
} else {
tStyle = Theme.of(context).textTheme.body1;
}
} else {
tStyle = textStyle;
} }
return Padding( return Padding(
padding: padding, padding: padding,
@ -31,7 +25,7 @@ class EntityName extends StatelessWidget {
overflow: textOverflow, overflow: textOverflow,
softWrap: wordsWrap, softWrap: wordsWrap,
maxLines: maxLines, maxLines: maxLines,
style: tStyle, style: textStyle,
textAlign: textAlign, textAlign: textAlign,
), ),
); );

View 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,
);
}
}

View File

@ -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 showName;
final bool showState; final bool showState;
final bool nameInTheBottom; final bool nameInTheBottom;
final double iconSize; final double iconSize;
final double nameFontSize;
final bool wordsWrapInName; final bool wordsWrapInName;
GlanceCardEntityContainer({ GlanceEntityContainer({
Key key, Key key,
@required this.showName, @required this.showName,
@required this.showState, @required this.showState,
this.nameInTheBottom: false, this.nameInTheBottom: false,
this.iconSize: Sizes.iconSize, this.iconSize: Sizes.iconSize,
this.nameFontSize: Sizes.smallFontSize,
this.wordsWrapInName: false this.wordsWrapInName: false
}) : super(key: key); }) : super(key: key);
@ -29,7 +31,7 @@ class GlanceCardEntityContainer extends StatelessWidget {
List<Widget> result = []; List<Widget> result = [];
if (!nameInTheBottom) { if (!nameInTheBottom) {
if (showName) { if (showName) {
result.add(_buildName(context)); result.add(_buildName());
} }
} else { } else {
if (showState) { if (showState) {
@ -37,39 +39,43 @@ class GlanceCardEntityContainer extends StatelessWidget {
} }
} }
result.add( result.add(
EntityIcon( EntityIcon(
padding: EdgeInsets.all(0.0), padding: EdgeInsets.all(0.0),
size: iconSize, size: iconSize,
) )
); );
if (!nameInTheBottom) { if (!nameInTheBottom) {
if (showState) { if (showState) {
result.add(_buildState()); result.add(_buildState());
} }
} else { } else {
result.add(_buildName(context)); result.add(_buildName());
} }
return Center( return Center(
child: InkResponse( child: InkResponse(
child: Column( child: ConstrainedBox(
mainAxisSize: MainAxisSize.min, constraints: BoxConstraints(minWidth: Sizes.iconSize * 2),
children: result, child: Column(
mainAxisSize: MainAxisSize.min,
//mainAxisAlignment: MainAxisAlignment.start,
//crossAxisAlignment: CrossAxisAlignment.center,
children: result,
),
), ),
onTap: () => entityWrapper.handleTap(), onTap: () => entityWrapper.handleTap(),
onLongPress: () => entityWrapper.handleHold(), onLongPress: () => entityWrapper.handleHold(),
onDoubleTap: () => entityWrapper.handleDoubleTap(),
), ),
); );
} }
Widget _buildName(BuildContext context) { Widget _buildName() {
return EntityName( return EntityName(
padding: EdgeInsets.only(bottom: Sizes.rowPadding), padding: EdgeInsets.only(bottom: Sizes.rowPadding),
textOverflow: TextOverflow.ellipsis, textOverflow: TextOverflow.ellipsis,
wordsWrap: wordsWrapInName, wordsWrap: wordsWrapInName,
textAlign: TextAlign.center, textAlign: TextAlign.center,
textStyle: Theme.of(context).textTheme.body1, fontSize: nameFontSize,
); );
} }

View File

@ -148,7 +148,7 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
}); });
if ((_selectedId == -1) && (numericDataLists.isNotEmpty)) { if ((_selectedId == -1) && (numericDataLists.isNotEmpty)) {
_selectedId = numericDataLists.length -1; _selectedId = 0;
} }
List<charts.Series<EntityHistoryMoment, DateTime>> result = []; List<charts.Series<EntityHistoryMoment, DateTime>> result = [];
numericDataLists.forEach((attrName, dataList) { numericDataLists.forEach((attrName, dataList) {
@ -156,8 +156,7 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
result.add( result.add(
new charts.Series<EntityHistoryMoment, DateTime>( new charts.Series<EntityHistoryMoment, DateTime>(
id: "value", id: "value",
colorFn: (EntityHistoryMoment historyMoment, __) => colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor("_", historyMoment.colorId),
HAClientTheme().chartHistoryStateColor("_", historyMoment.colorId, context),
radiusPxFn: (EntityHistoryMoment historyMoment, __) { radiusPxFn: (EntityHistoryMoment historyMoment, __) {
if (historyMoment.hiddenDot) { if (historyMoment.hiddenDot) {
return 0.0; return 0.0;
@ -180,8 +179,7 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
new charts.Series<EntityHistoryMoment, DateTime>( new charts.Series<EntityHistoryMoment, DateTime>(
id: 'state', id: 'state',
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 4.0, radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 4.0,
colorFn: (EntityHistoryMoment historyMoment, __) => colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
HAClientTheme().chartHistoryStateColor(historyMoment.state, historyMoment.colorId, context),
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime, domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
domainLowerBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime, domainLowerBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
domainUpperBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.endTime ?? DateTime.now(), domainUpperBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.endTime ?? DateTime.now(),
@ -204,11 +202,6 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
_selectedId -= 1; _selectedId -= 1;
}); });
} }
else {
setState(() {
_selectedId = _parsedHistory.first.data.length - 1;
});
}
} }
void _selectNext() { void _selectNext() {
@ -217,12 +210,6 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
_selectedId += 1; _selectedId += 1;
}); });
} }
else {
setState(() {
_selectedId = 0;
});
}
} }
void _onSelectionChanged(charts.SelectionModel model) { void _onSelectionChanged(charts.SelectionModel model) {

View File

@ -17,7 +17,9 @@ class EntityHistoryConfig {
class EntityHistoryWidget extends StatefulWidget { 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 @override
_EntityHistoryWidgetState createState() { _EntityHistoryWidgetState createState() {
@ -31,7 +33,6 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
bool _needToUpdateHistory; bool _needToUpdateHistory;
DateTime _historyLastUpdated; DateTime _historyLastUpdated;
bool _disposed = false; bool _disposed = false;
Entity entity;
@override @override
void initState() { void initState() {
@ -39,14 +40,14 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
_needToUpdateHistory = true; _needToUpdateHistory = true;
} }
void _loadHistory(String entityId) { void _loadHistory(HomeAssistant ha, String entityId) {
DateTime now = DateTime.now(); DateTime now = DateTime.now();
if (_historyLastUpdated != null) { if (_historyLastUpdated != null) {
Logger.d("History was updated ${now.difference(_historyLastUpdated).inSeconds} seconds ago"); Logger.d("History was updated ${now.difference(_historyLastUpdated).inSeconds} seconds ago");
} }
if (_historyLastUpdated == null || now.difference(_historyLastUpdated).inSeconds > 30) { if (_historyLastUpdated == null || now.difference(_historyLastUpdated).inSeconds > 30) {
_historyLastUpdated = now; _historyLastUpdated = now;
ConnectionManager().getHistory(entityId).then((history){ ha.getHistory(entityId).then((history){
if (!_disposed) { if (!_disposed) {
setState(() { setState(() {
_history = history.isNotEmpty ? history[0] : []; _history = history.isNotEmpty ? history[0] : [];
@ -67,17 +68,18 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final HomeAssistantModel homeAssistantModel = HomeAssistantModel.of(context);
final EntityModel entityModel = EntityModel.of(context); final EntityModel entityModel = EntityModel.of(context);
final Entity entity = entityModel.entityWrapper.entity; final Entity entity = entityModel.entityWrapper.entity;
if (!_needToUpdateHistory) { if (!_needToUpdateHistory) {
_needToUpdateHistory = true; _needToUpdateHistory = true;
} else { } else {
_loadHistory(entity.entityId); _loadHistory(homeAssistantModel.homeAssistant, entity.entityId);
} }
return _buildChart(entity.historyConfig); return _buildChart();
} }
Widget _buildChart(EntityHistoryConfig config) { Widget _buildChart() {
List<Widget> children = []; List<Widget> children = [];
if (_history == null) { if (_history == null) {
children.add( children.add(
@ -89,7 +91,7 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
); );
} else { } else {
children.add( children.add(
_selectChartWidget(config) _selectChartWidget()
); );
} }
children.add(Divider()); children.add(Divider());
@ -101,8 +103,8 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
); );
} }
Widget _selectChartWidget(EntityHistoryConfig config) { Widget _selectChartWidget() {
switch (config.chartType) { switch (widget.config.chartType) {
case EntityHistoryWidgetType.simple: { case EntityHistoryWidgetType.simple: {
return SimpleStateHistoryChartWidget( return SimpleStateHistoryChartWidget(
@ -113,14 +115,14 @@ class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
case EntityHistoryWidgetType.numericState: { case EntityHistoryWidgetType.numericState: {
return NumericStateHistoryChartWidget( return NumericStateHistoryChartWidget(
rawHistory: _history, rawHistory: _history,
config: config, config: widget.config,
); );
} }
case EntityHistoryWidgetType.numericAttributes: { case EntityHistoryWidgetType.numericAttributes: {
return CombinedHistoryChartWidget( return CombinedHistoryChartWidget(
rawHistory: _history, rawHistory: _history,
config: config, config: widget.config,
); );
} }

Some files were not shown because too many files have changed in this diff Show More