Compare commits
1 Commits
rc/1.1.0-b
...
beta/0.7.1
Author | SHA1 | Date | |
---|---|---|---|
3234ffc20c |
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -7,16 +7,35 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**HA Client version:** [Main menu -> About HA Client]
|
<!--
|
||||||
|
Please provide as much information as possible.
|
||||||
|
-->
|
||||||
|
**HA Client version:** <!-- Main app menu => About HA Client -->
|
||||||
|
|
||||||
**Home Assistant version:**
|
**Home Assistant version:** <!-- 0.94.1 for example -->
|
||||||
|
|
||||||
**Device name:**
|
**Device name:** <!-- Pixel 2 for example -->
|
||||||
|
|
||||||
**Android version:**
|
**Android version:** <!-- 8.1 for example -->
|
||||||
|
|
||||||
|
**Connection type:** <!-- For example "Local IP" or "Remote UI" or "Own domain"-->
|
||||||
|
|
||||||
|
**Login type:** <!-- For example "HA Login" or "Manual token"-->
|
||||||
|
|
||||||
**Description**
|
**Description**
|
||||||
[Replace with description]
|
<!--
|
||||||
|
Describe your issue here
|
||||||
|
-->
|
||||||
|
|
||||||
**Screenshots**
|
**Screenshots**
|
||||||
[Replace with screenshots]
|
<!--
|
||||||
|
Please provide screenshots if it is a UI issue. Also you can attach screenshot from Home Assistant web UI as an expected result
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Logs**
|
||||||
|
<!--
|
||||||
|
Right after issue reproduced go to app menu and tap "Log". Copy log with a "Copy" button in the upper-right corner and post it below
|
||||||
|
-->
|
||||||
|
```
|
||||||
|
[Replace this text with your logs]
|
||||||
|
```
|
||||||
|
4
.gitignore
vendored
@ -15,9 +15,7 @@ build/
|
|||||||
.settings/
|
.settings/
|
||||||
|
|
||||||
flutter_export_environment.sh
|
flutter_export_environment.sh
|
||||||
.flutter-plugins-dependencies
|
|
||||||
|
|
||||||
key.properties
|
key.properties
|
||||||
.secrets.dart
|
premium_features_manager.class.dart
|
||||||
pubspec.lock
|
pubspec.lock
|
||||||
google-services.json
|
|
||||||
|
@ -4,5 +4,9 @@ ENV ANDROID_HOME=/workspace/android-sdk \
|
|||||||
FLUTTER_ROOT=/workspace/flutter \
|
FLUTTER_ROOT=/workspace/flutter \
|
||||||
FLUTTER_HOME=/workspace/flutter
|
FLUTTER_HOME=/workspace/flutter
|
||||||
|
|
||||||
RUN bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh \
|
USER root
|
||||||
&& sdk install java 8.0.242.j9-adpt"
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get -y install build-essential libkrb5-dev gcc make gradle openjdk-8-jdk && \
|
||||||
|
apt-get clean && \
|
||||||
|
apt-get -y autoremove
|
@ -3,12 +3,12 @@ image:
|
|||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- before: |
|
- before: |
|
||||||
export PATH=$FLUTTER_HOME/bin:$FLUTTER_HOME/bin/cache/dart-sdk/bin:$ANDROID_HOME/bin:$ANDROID_HOME/platform-tools:$PATH
|
export PATH=$FLUTTER_HOME/bin:$ANDROID_HOME/bin:$ANDROID_HOME/platform-tools:$PATH
|
||||||
mkdir -p /home/gitpod/.android
|
mkdir -p /home/gitpod/.android
|
||||||
touch /home/gitpod/.android/repositories.cfg
|
touch /home/gitpod/.android/repositories.cfg
|
||||||
init: |
|
init: |
|
||||||
echo "Installing Flutter SDK..."
|
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
|
cd /workspace && wget -qO flutter_sdk.tar.xz https://storage.googleapis.com/flutter_infra/releases/stable/linux/flutter_linux_v1.9.1+hotfix.4-stable.tar.xz && tar -xf flutter_sdk.tar.xz && rm -f flutter_sdk.tar.xz
|
||||||
echo "Installing Android SDK..."
|
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
|
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"
|
/workspace/android-sdk/tools/bin/sdkmanager "platform-tools" "platforms;android-28" "build-tools;28.0.3"
|
||||||
@ -18,6 +18,7 @@ tasks:
|
|||||||
flutter doctor --android-licenses
|
flutter doctor --android-licenses
|
||||||
flutter pub get
|
flutter pub get
|
||||||
command: |
|
command: |
|
||||||
|
flutter pub upgrade
|
||||||
echo "Ready to go!"
|
echo "Ready to go!"
|
||||||
flutter doctor
|
flutter doctor
|
||||||
vscode:
|
vscode:
|
||||||
|
19
README.md
@ -1,18 +1,15 @@
|
|||||||
|
[](https://gitpod.io/#https://github.com/estevez-dev/ha_client)
|
||||||
# HA Client
|
# HA Client
|
||||||
## Native Android client for Home Assistant
|
## Native Android client for Home Assistant
|
||||||
### With actionable notifications, location tracking and Lovelace UI support
|
### With notifications and 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/store/apps/details?id=com.keyboardcrumbs.haclient)
|
Download the app from [Google Play](https://play.google.com/apps/testing/com.keyboardcrumbs.haclient)
|
||||||
|
|
||||||
Discuss it on [Discord](https://discord.gg/u9vq7QE) or at [Home Assistant community](https://community.home-assistant.io/c/mobile-apps/ha-client-android)
|
Discuss it in [Home Assistant community](https://community.home-assistant.io/t/alpha-testing-ha-client-native-android-client-for-home-assistant/69912) or on [Discord server](https://discord.gg/AUzEvwn)
|
||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/estevez-dev/ha_client)
|
#### Pre-release CI build
|
||||||
|
[](https://codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5da8bdab9f20ef798f7c2c64/latest_build)
|
||||||
#### Last release build status
|
#### Beta CI build
|
||||||
[](https://codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5db1862025dc3f0b0288a57a/latest_build)
|
[](https://codemagic.io/apps/5da8bdab9f20ef798f7c2c65/5db1862025dc3f0b0288a57a/latest_build)
|
||||||
|
|
||||||
#### Special thanks to
|
|
||||||
- [Crewski](https://github.com/Crewski) for his [HANotify](https://github.com/Crewski/HANotify)
|
|
||||||
- [Home Assistant](https://github.com/home-assistant) for some support and [Home Assistant](https://www.home-assistant.io/)
|
|
@ -78,13 +78,10 @@ flutter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.google.firebase:firebase-analytics:17.2.2'
|
implementation 'com.google.firebase:firebase-core:16.0.8'
|
||||||
implementation 'com.google.firebase:firebase-messaging:20.2.0'
|
|
||||||
implementation 'androidx.work:work-runtime:2.3.4'
|
|
||||||
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'
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
64
android/app/google-services.json
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "441874387819",
|
||||||
|
"firebase_url": "https://ha-client-c73c4.firebaseio.com",
|
||||||
|
"project_id": "ha-client-c73c4",
|
||||||
|
"storage_bucket": "ha-client-c73c4.appspot.com"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:441874387819:android:92c7efc892dc3d45",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.keyboardcrumbs.haclient"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "441874387819-uqmkibhf361828od1982o2jhl0n3m0ov.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.keyboardcrumbs.haclient",
|
||||||
|
"certificate_hash": "bebe4d970fbebf0bff2c93244fdc7fcbcefb3470"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "441874387819-5q7vmimci4s2jl3v0ncugv1ocp4m48nb.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.keyboardcrumbs.haclient",
|
||||||
|
"certificate_hash": "0ea12348468be44bc2aa5792ee7e8924c633da81"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "441874387819-joi8plo5345ebt8i1dug27u2aenv5tg7.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.keyboardcrumbs.haclient",
|
||||||
|
"certificate_hash": "fcbc805d965ccf6a4d5417398d191edc9c9890b0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "441874387819-id0hqsfprj3b5kc312faqv3lmdfpm7l8.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyBsl9cjBY633IrdrTyCsLFlO9lfsYJ0OJU"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "441874387819-id0hqsfprj3b5kc312faqv3lmdfpm7l8.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration_version": "1"
|
||||||
|
}
|
@ -3,28 +3,29 @@
|
|||||||
|
|
||||||
<uses-feature android:name="android.hardware.touchscreen"
|
<uses-feature android:name="android.hardware.touchscreen"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
flutter needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
|
|
||||||
|
|
||||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||||
In most cases you can leave this as-is, but you if you want to provide
|
In most cases you can leave this as-is, but you if you want to provide
|
||||||
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=".Application"
|
||||||
android:label="HA Client"
|
android:label="HA Client"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
|
||||||
android:usesCleartextTraffic="true">
|
android:usesCleartextTraffic="true">
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="flutterEmbedding"
|
|
||||||
android:value="2" />
|
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||||
android:value="ha_notify" />
|
android:value="ha_notify" />
|
||||||
@ -36,32 +37,31 @@
|
|||||||
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
|
<intent-filter>
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
|
||||||
android:resource="@style/NormalTheme" />
|
<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>
|
||||||
|
<intent-filter android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data
|
||||||
|
android:scheme="haclient"
|
||||||
|
android:host="auth" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service
|
|
||||||
android:name=".MessagingService"
|
|
||||||
android:exported="false">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
|
||||||
<receiver android:name=".NotificationActionReceiver" android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
|
||||||
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="io.flutter.plugins.androidalarmmanager.AlarmService"
|
android:name="io.flutter.plugins.androidalarmmanager.AlarmService"
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.keyboardcrumbs.hassclient;
|
||||||
|
|
||||||
|
import io.flutter.app.FlutterApplication;
|
||||||
|
import io.flutter.plugin.common.PluginRegistry;
|
||||||
|
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback;
|
||||||
|
import io.flutter.plugins.GeneratedPluginRegistrant;
|
||||||
|
import be.tramckrijte.workmanager.WorkmanagerPlugin;
|
||||||
|
|
||||||
|
public class Application extends FlutterApplication implements PluginRegistrantCallback {
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
WorkmanagerPlugin.setPluginRegistrantCallback(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerWith(PluginRegistry registry) {
|
||||||
|
GeneratedPluginRegistrant.registerWith(registry);
|
||||||
|
}
|
||||||
|
}
|
@ -1,71 +1,15 @@
|
|||||||
package com.keyboardcrumbs.hassclient;
|
package com.keyboardcrumbs.hassclient;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import io.flutter.embedding.android.FlutterActivity;
|
|
||||||
import io.flutter.embedding.engine.FlutterEngine;
|
|
||||||
import io.flutter.plugins.GeneratedPluginRegistrant;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.SharedPreferences.Editor;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import io.flutter.app.FlutterActivity;
|
||||||
|
import io.flutter.plugins.GeneratedPluginRegistrant;
|
||||||
|
import io.flutter.plugins.share.FlutterShareReceiverActivity;
|
||||||
|
|
||||||
import io.flutter.plugin.common.MethodCall;
|
public class MainActivity extends FlutterShareReceiverActivity {
|
||||||
import io.flutter.plugin.common.MethodChannel;
|
|
||||||
|
|
||||||
import com.google.android.gms.tasks.OnCompleteListener;
|
|
||||||
import com.google.android.gms.tasks.Task;
|
|
||||||
import com.google.android.gms.common.GoogleApiAvailability;
|
|
||||||
import com.google.android.gms.common.ConnectionResult;
|
|
||||||
import com.google.firebase.iid.FirebaseInstanceId;
|
|
||||||
import com.google.firebase.iid.InstanceIdResult;
|
|
||||||
import com.google.firebase.messaging.FirebaseMessaging;
|
|
||||||
|
|
||||||
public class MainActivity extends FlutterActivity {
|
|
||||||
|
|
||||||
private static final String CHANNEL = "com.keyboardcrumbs.hassclient/native";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
|
|
||||||
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
|
||||||
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL).setMethodCallHandler(
|
|
||||||
new MethodChannel.MethodCallHandler() {
|
|
||||||
@Override
|
|
||||||
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
|
|
||||||
Context context = getActivity();
|
|
||||||
if (call.method.equals("getFCMToken")) {
|
|
||||||
if (checkPlayServices()) {
|
|
||||||
FirebaseInstanceId.getInstance().getInstanceId()
|
|
||||||
.addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
|
|
||||||
@Override
|
|
||||||
public void onComplete(@NonNull Task<InstanceIdResult> task) {
|
|
||||||
if (task.isSuccessful()) {
|
|
||||||
String token = task.getResult().getToken();
|
|
||||||
UpdateTokenTask updateTokenTask = new UpdateTokenTask(context);
|
|
||||||
updateTokenTask.execute(token);
|
|
||||||
result.success(token);
|
|
||||||
} else {
|
|
||||||
result.error("fcm_error", task.getException().getMessage(), task.getException());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
result.error("google_play_service_error", "Google Play Services unavailable", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkPlayServices() {
|
|
||||||
return (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
GeneratedPluginRegistrant.registerWith(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,152 +0,0 @@
|
|||||||
package com.keyboardcrumbs.hassclient;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import android.app.NotificationChannel;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.media.RingtoneManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
|
|
||||||
import com.google.firebase.messaging.FirebaseMessagingService;
|
|
||||||
import com.google.firebase.messaging.RemoteMessage;
|
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.webkit.URLUtil;
|
|
||||||
|
|
||||||
|
|
||||||
public class MessagingService extends FirebaseMessagingService {
|
|
||||||
|
|
||||||
private static final String TAG = "MessagingService";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMessageReceived(RemoteMessage remoteMessage) {
|
|
||||||
Map<String, String> data = remoteMessage.getData();
|
|
||||||
if (data.size() > 0) {
|
|
||||||
if (data.containsKey("body") || data.containsKey("title")) {
|
|
||||||
sendNotification(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNewToken(String token) {
|
|
||||||
UpdateTokenTask updateTokenTask = new UpdateTokenTask(this);
|
|
||||||
updateTokenTask.execute(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendNotification(Map<String, String> data) {
|
|
||||||
String channelId, messageBody, messageTitle, imageUrl, nTag, channelDescription;
|
|
||||||
boolean autoCancel;
|
|
||||||
if (!data.containsKey("channelId")) {
|
|
||||||
channelId = "ha_notify";
|
|
||||||
channelDescription = "Default notification channel";
|
|
||||||
} else {
|
|
||||||
channelId = data.get("channelId");
|
|
||||||
channelDescription = channelId;
|
|
||||||
}
|
|
||||||
if (!data.containsKey("body")) {
|
|
||||||
messageBody = "";
|
|
||||||
} else {
|
|
||||||
messageBody = data.get("body");
|
|
||||||
}
|
|
||||||
if (!data.containsKey("title")) {
|
|
||||||
messageTitle = "HA Client";
|
|
||||||
} else {
|
|
||||||
messageTitle = data.get("title");
|
|
||||||
}
|
|
||||||
if (!data.containsKey("tag")) {
|
|
||||||
nTag = String.valueOf(System.currentTimeMillis());
|
|
||||||
} else {
|
|
||||||
nTag = data.get("tag");
|
|
||||||
}
|
|
||||||
if (data.containsKey("dismiss")) {
|
|
||||||
try {
|
|
||||||
boolean dismiss = Boolean.parseBoolean(data.get("dismiss"));
|
|
||||||
if (dismiss) {
|
|
||||||
NotificationManager notificationManager =
|
|
||||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
notificationManager.cancel(nTag, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
//nope
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data.containsKey("autoDismiss")) {
|
|
||||||
try {
|
|
||||||
autoCancel = Boolean.parseBoolean(data.get("autoDismiss"));
|
|
||||||
} catch (Exception e) {
|
|
||||||
autoCancel = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
autoCancel = true;
|
|
||||||
}
|
|
||||||
imageUrl = data.get("image");
|
|
||||||
Intent intent = new Intent(this, MainActivity.class);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
|
|
||||||
PendingIntent.FLAG_ONE_SHOT);
|
|
||||||
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
|
|
||||||
NotificationCompat.Builder notificationBuilder =
|
|
||||||
new NotificationCompat.Builder(this, channelId)
|
|
||||||
.setSmallIcon(R.drawable.mini_icon)
|
|
||||||
.setContentTitle(messageTitle)
|
|
||||||
.setContentText(messageBody)
|
|
||||||
.setAutoCancel(autoCancel)
|
|
||||||
.setSound(defaultSoundUri)
|
|
||||||
.setContentIntent(pendingIntent);
|
|
||||||
if (URLUtil.isValidUrl(imageUrl)) {
|
|
||||||
Bitmap image = getBitmapFromURL(imageUrl);
|
|
||||||
if (image != null) {
|
|
||||||
notificationBuilder.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(image).bigLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.blank_icon)));
|
|
||||||
notificationBuilder.setLargeIcon(image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = 1; i <= 3; i++) {
|
|
||||||
if (data.containsKey("action" + i)) {
|
|
||||||
Intent broadcastIntent = new Intent(this, NotificationActionReceiver.class);
|
|
||||||
if (autoCancel) {
|
|
||||||
broadcastIntent.putExtra("tag", nTag);
|
|
||||||
}
|
|
||||||
broadcastIntent.putExtra("actionData", data.get("action" + i + "_data"));
|
|
||||||
PendingIntent actionIntent = PendingIntent.getBroadcast(this, i, broadcastIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
|
||||||
notificationBuilder.addAction(R.drawable.mini_icon, data.get("action" + i), actionIntent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NotificationManager notificationManager =
|
|
||||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
|
|
||||||
// Since android Oreo notification channel is needed.
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
NotificationChannel channel = new NotificationChannel(channelId,
|
|
||||||
channelDescription,
|
|
||||||
NotificationManager.IMPORTANCE_HIGH);
|
|
||||||
notificationManager.createNotificationChannel(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationManager.notify(nTag, 0 /* ID of notification */, notificationBuilder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Bitmap getBitmapFromURL(String imageUrl) {
|
|
||||||
try {
|
|
||||||
URL url = new URL(imageUrl);
|
|
||||||
URLConnection connection = url.openConnection();
|
|
||||||
connection.setDoInput(true);
|
|
||||||
connection.connect();
|
|
||||||
InputStream input = connection.getInputStream();
|
|
||||||
return BitmapFactory.decodeStream(input);
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
package com.keyboardcrumbs.hassclient;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
|
|
||||||
import android.webkit.URLUtil;
|
|
||||||
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
|
|
||||||
public class NotificationActionReceiver extends BroadcastReceiver {
|
|
||||||
|
|
||||||
private static final String TAG = "NotificationActionReceiver";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
String rawActionData = intent.getStringExtra("actionData");
|
|
||||||
if (intent.hasExtra("tag")) {
|
|
||||||
String notificationTag = intent.getStringExtra("tag");
|
|
||||||
NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
notificationManager.cancel(notificationTag, 0);
|
|
||||||
}
|
|
||||||
SharedPreferences prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE);
|
|
||||||
String webhookId = prefs.getString("flutter.app-webhook-id", null);
|
|
||||||
if (webhookId != null) {
|
|
||||||
try {
|
|
||||||
String requestUrl = prefs.getString("flutter.hassio-res-protocol", "") +
|
|
||||||
"://" +
|
|
||||||
prefs.getString("flutter.hassio-domain", "") +
|
|
||||||
":" +
|
|
||||||
prefs.getString("flutter.hassio-port", "") + "/api/webhook/" + webhookId;
|
|
||||||
JSONObject actionData = new JSONObject(rawActionData);
|
|
||||||
if (URLUtil.isValidUrl(requestUrl)) {
|
|
||||||
JSONObject dataToSend = new JSONObject();
|
|
||||||
JSONObject requestData = new JSONObject();
|
|
||||||
if (actionData.getString("action").equals("call-service")) {
|
|
||||||
dataToSend.put("type", "call_service");
|
|
||||||
requestData.put("domain", actionData.getString("service").split("\\.")[0]);
|
|
||||||
requestData.put("service", actionData.getString("service").split("\\.")[1]);
|
|
||||||
if (actionData.has("service_data")) {
|
|
||||||
requestData.put("service_data", actionData.get("service_data"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dataToSend.put("type", "fire_event");
|
|
||||||
requestData.put("event_type", "ha_client_event");
|
|
||||||
JSONObject eventData = new JSONObject();
|
|
||||||
eventData.put("action", actionData.getString("action"));
|
|
||||||
requestData.put("event_data", eventData);
|
|
||||||
}
|
|
||||||
dataToSend.put("data", requestData);
|
|
||||||
String stringRequest = dataToSend.toString();
|
|
||||||
SendTask sendTask = new SendTask();
|
|
||||||
sendTask.execute(requestUrl, stringRequest);
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "Invalid HA url");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Error handling notification action", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "Webhook id not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
package com.keyboardcrumbs.hassclient;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public class SendTask extends AsyncTask<String, String, String> {
|
|
||||||
|
|
||||||
private static final String TAG = "SendTask";
|
|
||||||
|
|
||||||
public SendTask(){
|
|
||||||
//set context variables if required
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
super.onPreExecute();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String doInBackground(String... params) {
|
|
||||||
String urlString = params[0];
|
|
||||||
String data = params[1];
|
|
||||||
|
|
||||||
try {
|
|
||||||
URL url = new URL(urlString);
|
|
||||||
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
|
||||||
urlConnection.setRequestMethod("POST");
|
|
||||||
urlConnection.setRequestProperty("Content-Type", "application/json");
|
|
||||||
urlConnection.setDoOutput(true);
|
|
||||||
byte[] outputBytes = data.getBytes("UTF-8");
|
|
||||||
OutputStream os = urlConnection.getOutputStream();
|
|
||||||
os.write(outputBytes);
|
|
||||||
|
|
||||||
int responseCode = urlConnection.getResponseCode();
|
|
||||||
|
|
||||||
urlConnection.disconnect();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Error sending data", e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
package com.keyboardcrumbs.hassclient;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import android.webkit.URLUtil;
|
|
||||||
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.Context;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
|
|
||||||
|
|
||||||
public class UpdateTokenTask extends AsyncTask<String, String, String> {
|
|
||||||
|
|
||||||
private static final String TAG = "UpdateTokenTask";
|
|
||||||
|
|
||||||
private WeakReference<Context> contextRef;
|
|
||||||
|
|
||||||
public UpdateTokenTask(Context context){
|
|
||||||
contextRef = new WeakReference<>(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
super.onPreExecute();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String doInBackground(String... params) {
|
|
||||||
Log.d(TAG, "Updating push token");
|
|
||||||
Context context = contextRef.get();
|
|
||||||
if (context != null) {
|
|
||||||
String token = params[0];
|
|
||||||
SharedPreferences prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE);
|
|
||||||
SharedPreferences.Editor editor = prefs.edit();
|
|
||||||
editor.putString("flutter.npush-token", token);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 461 B |
@ -1,27 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
|
|
||||||
<group>
|
|
||||||
<clip-path
|
|
||||||
android:pathData="M 0 0 H 108 V 108 H 0 V 0 Z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#709ac1"
|
|
||||||
android:fillAlpha="0"
|
|
||||||
android:pathData="M 0 0 H 108 V 108 H 0 V 0 Z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#000000"
|
|
||||||
android:fillAlpha="0.12"
|
|
||||||
android:pathData="M 70.506 38.389 L 108 72.466 L 108 108 L 77 108 L 35.066 72.466 L 38.373 63.769 L 36.268 50.216 L 43.335 44.523 L 51.841 34.578 L 63.096 42.478 L 68.586 42.478 L 70.506 38.389 Z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#000000"
|
|
||||||
android:fillAlpha="0.12"
|
|
||||||
android:pathData="M 28.979 53.708 L 47.736 67.31 L 38.373 58.563 L 36.268 51.52 L 28.979 53.708 Z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#ffffff"
|
|
||||||
android:pathData="M 77.131 54.24 L 72.878 54.24 L 72.878 72.415 L 56.339 72.415 L 56.339 64.85 L 62.931 58.511 L 64.609 58.784 C 67.349 58.784 69.57 56.649 69.57 54.013 C 69.57 51.378 67.349 49.242 64.609 49.242 C 61.868 49.242 59.647 51.378 59.647 54.013 L 59.883 55.626 L 56.339 59.079 L 56.339 46.63 C 57.898 45.812 58.938 44.244 58.938 42.427 C 58.938 39.792 56.717 37.656 53.976 37.656 C 51.236 37.656 49.015 39.792 49.015 42.427 C 49.015 44.244 50.054 45.812 51.614 46.63 L 51.614 59.079 L 48.07 55.626 L 48.306 54.013 C 48.306 51.378 46.084 49.242 43.344 49.242 C 40.604 49.242 38.383 51.378 38.383 54.013 C 38.383 56.648 40.604 58.784 43.344 58.784 L 45.022 58.511 L 51.614 64.85 L 51.614 72.415 L 35.075 72.415 L 35.075 54.24 L 30.94 54.24 C 29.948 54.24 28.979 54.24 28.979 53.763 C 29.003 53.263 29.995 52.309 31.011 51.332 L 51.614 31.522 C 52.393 30.772 53.197 30 53.976 30 C 54.756 30 55.559 30.772 56.339 31.522 L 65.79 40.609 L 65.79 38.338 L 70.515 38.338 L 70.515 45.153 L 77.084 51.469 C 78.029 52.377 78.997 53.309 79.021 53.786 C 79.021 54.24 78.076 54.24 77.131 54.24 Z M 43.344 51.969 C 43.908 51.969 44.449 52.184 44.848 52.567 C 45.247 52.951 45.471 53.471 45.471 54.013 C 45.471 54.555 45.247 55.076 44.848 55.459 C 44.449 55.842 43.908 56.058 43.344 56.058 C 42.78 56.058 42.239 55.842 41.841 55.459 C 41.442 55.076 41.218 54.555 41.218 54.013 C 41.218 53.471 41.442 52.951 41.841 52.567 C 42.239 52.184 42.78 51.969 43.344 51.969 Z M 64.609 51.969 C 65.79 51.969 66.735 52.877 66.735 54.013 C 66.735 55.149 65.79 56.058 64.609 56.058 C 64.045 56.058 63.504 55.842 63.105 55.459 C 62.706 55.076 62.482 54.555 62.482 54.013 C 62.482 53.471 62.706 52.951 63.105 52.567 C 63.504 52.184 64.045 51.969 64.609 51.969 Z M 53.976 40.382 C 55.158 40.382 56.103 41.291 56.103 42.427 C 56.103 43.563 55.158 44.472 53.976 44.472 C 52.795 44.472 51.85 43.563 51.85 42.427 C 51.85 41.291 52.795 40.382 53.976 40.382 Z" />
|
|
||||||
</group>
|
|
||||||
</vector>
|
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Modify this file to customize your launch splash screen -->
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="@color/main_color" />
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
<!-- You can insert your own image assets here -->
|
<!-- You can insert your own image assets here -->
|
||||||
<!-- <item>
|
<!-- <item>
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/main_color"/>
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/main_color"/>
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 20 KiB |
@ -1,12 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="main_color">#709AC1</color>
|
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
Flutter draws its first frame -->
|
Flutter draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -2,15 +2,11 @@ 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.3.2'
|
||||||
classpath 'com.google.gms:google-services:4.3.3'
|
classpath 'com.google.gms:google-services:4.2.0'
|
||||||
classpath 'io.fabric.tools:gradle:1.26.1'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,9 +14,6 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
maven {
|
|
||||||
url 'https://maven.fabric.io/public'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,4 +3,3 @@ org.gradle.daemon=true
|
|||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
android.enableR8=true
|
|
||||||
|
1232
android/hs_err_pid766.log
Normal file
@ -1 +0,0 @@
|
|||||||
include ':app'
|
|
@ -1,61 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
widows: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
video {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
var messageChannel = '{{message_channel}}';
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<video id="screen" width="100%" controls></video>
|
|
||||||
<script>
|
|
||||||
if (Hls.isSupported()) {
|
|
||||||
var video = document.getElementById('screen');
|
|
||||||
var hls = new Hls();
|
|
||||||
hls.on(Hls.Events.ERROR, function (event, data) {
|
|
||||||
if (data.fatal) {
|
|
||||||
switch(data.type) {
|
|
||||||
case Hls.ErrorTypes.NETWORK_ERROR:
|
|
||||||
// try to recover network error
|
|
||||||
console.log("fatal network error encountered, try to recover");
|
|
||||||
hls.startLoad();
|
|
||||||
break;
|
|
||||||
case Hls.ErrorTypes.MEDIA_ERROR:
|
|
||||||
console.log("fatal media error encountered, try to recover");
|
|
||||||
hls.recoverMediaError();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// cannot recover
|
|
||||||
hls.destroy();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// bind them together
|
|
||||||
hls.attachMedia(video);
|
|
||||||
hls.on(Hls.Events.MEDIA_ATTACHED, function () {
|
|
||||||
console.log("video and hls.js are now bound together !");
|
|
||||||
hls.loadSource("{{stream_url}}");
|
|
||||||
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
|
|
||||||
console.log("manifest loaded, found " + data.levels.length + " quality level");
|
|
||||||
video.play();
|
|
||||||
video.onloadedmetadata = function() {
|
|
||||||
window[messageChannel].postMessage(document.body.clientWidth / video.offsetHeight);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,28 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
widows: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
var messageChannel = '{{message_channel}}';
|
|
||||||
window.onload = function() {
|
|
||||||
var img = document.getElementById('screen');
|
|
||||||
if (img) {
|
|
||||||
window[messageChannel].postMessage(document.body.clientWidth / img.offsetHeight);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<img id="screen" src="{{stream_url}}">
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -11,27 +11,6 @@ window.externalApp.getExternalAuth = function(options) {
|
|||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
console.log("Calling a callback");
|
console.log("Calling a callback");
|
||||||
window[options.callback](true, responseData);
|
window[options.callback](true, responseData);
|
||||||
}, 900);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
/*
|
|
||||||
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);
|
}, 500);
|
||||||
} else if (messageObj.type == "config_screen/show") {
|
|
||||||
HAClient.postMessage('show-settings');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
*/
|
|
BIN
images/hassio-192x192.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
images/icon/icon.png
Normal file
After Width: | Height: | Size: 24 KiB |
@ -1,62 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class AlarmPanelCard extends StatelessWidget {
|
|
||||||
final AlarmPanelCardData card;
|
|
||||||
|
|
||||||
const AlarmPanelCard({Key key, this.card}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (card.entity.entity.statelessType == StatelessEntityType.missed) {
|
|
||||||
return EntityModel(
|
|
||||||
entityWrapper: card.entity,
|
|
||||||
child: MissedEntityWidget(),
|
|
||||||
handleTap: false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
List<Widget> body = [];
|
|
||||||
body.add(CardHeader(
|
|
||||||
name: card.name ?? "",
|
|
||||||
subtitle: Text("${card.entity.entity.displayState}",
|
|
||||||
),
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
EntityIcon(
|
|
||||||
size: 50.0,
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: 26.0,
|
|
||||||
child: IconButton(
|
|
||||||
padding: EdgeInsets.all(0.0),
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
|
||||||
"mdi:dots-vertical")),
|
|
||||||
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entityId: card.entity.entity.entityId))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
));
|
|
||||||
body.add(
|
|
||||||
AlarmControlPanelControlsWidget(
|
|
||||||
extended: true,
|
|
||||||
states: card.states,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return CardWrapper(
|
|
||||||
child: EntityModel(
|
|
||||||
entityWrapper: card.entity,
|
|
||||||
handleTap: null,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: body
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,196 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class Badges extends StatelessWidget {
|
|
||||||
final BadgesData badges;
|
|
||||||
|
|
||||||
const Badges({Key key, this.badges}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
List<EntityWrapper> entitiesToShow = badges.getEntitiesToShow();
|
|
||||||
|
|
||||||
if (entitiesToShow.isNotEmpty) {
|
|
||||||
if (AppSettings().scrollBadges) {
|
|
||||||
return ConstrainedBox(
|
|
||||||
constraints: BoxConstraints.tightFor(height: 112),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: entitiesToShow.map((entity) =>
|
|
||||||
EntityModel(
|
|
||||||
entityWrapper: entity,
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(5, 10, 5, 10),
|
|
||||||
child: BadgeWidget(),
|
|
||||||
),
|
|
||||||
handleTap: true,
|
|
||||||
)).toList()
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(5, 10, 5, 10),
|
|
||||||
child: Wrap(
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
spacing: 10.0,
|
|
||||||
runSpacing: 5,
|
|
||||||
children: entitiesToShow.map((entity) =>
|
|
||||||
EntityModel(
|
|
||||||
entityWrapper: entity,
|
|
||||||
child: BadgeWidget(),
|
|
||||||
handleTap: true,
|
|
||||||
)).toList(),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Container(height: 0.0, width: 0.0,);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BadgeWidget extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final entityModel = EntityModel.of(context);
|
|
||||||
Widget badgeIcon;
|
|
||||||
String onBadgeTextValue;
|
|
||||||
Color iconColor = HAClientTheme().getBadgeColor(entityModel.entityWrapper.entity.domain);
|
|
||||||
switch (entityModel.entityWrapper.entity.domain) {
|
|
||||||
case "sun":
|
|
||||||
{
|
|
||||||
IconData iconData;
|
|
||||||
if (entityModel.entityWrapper.entity.state == "below_horizon") {
|
|
||||||
iconData = MaterialDesignIcons.getIconDataFromIconCode(0xf0dc);
|
|
||||||
} else {
|
|
||||||
iconData = MaterialDesignIcons.getIconDataFromIconCode(0xf5a8);
|
|
||||||
}
|
|
||||||
badgeIcon = Padding(
|
|
||||||
padding: EdgeInsets.all(10),
|
|
||||||
child: Icon(
|
|
||||||
iconData,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "camera":
|
|
||||||
case "media_player":
|
|
||||||
case "binary_sensor":
|
|
||||||
{
|
|
||||||
badgeIcon = EntityIcon(
|
|
||||||
imagePadding: EdgeInsets.all(0.0),
|
|
||||||
iconPadding: EdgeInsets.all(10),
|
|
||||||
color: Theme.of(context).textTheme.body2.color
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "device_tracker":
|
|
||||||
case "person":
|
|
||||||
{
|
|
||||||
badgeIcon = EntityIcon(
|
|
||||||
imagePadding: EdgeInsets.all(0.0),
|
|
||||||
iconPadding: EdgeInsets.all(10),
|
|
||||||
color: Theme.of(context).textTheme.body2.color
|
|
||||||
);
|
|
||||||
onBadgeTextValue = entityModel.entityWrapper.entity.displayState;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
onBadgeTextValue = entityModel.entityWrapper.unitOfMeasurement;
|
|
||||||
badgeIcon = Padding(
|
|
||||||
padding: EdgeInsets.all(4),
|
|
||||||
child: Text(
|
|
||||||
"${entityModel.entityWrapper.entity.displayState}",
|
|
||||||
overflow: TextOverflow.fade,
|
|
||||||
softWrap: false,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: Theme.of(context).textTheme.body1
|
|
||||||
)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Widget onBadgeText;
|
|
||||||
if (onBadgeTextValue == null || onBadgeTextValue.length == 0) {
|
|
||||||
onBadgeText = Container(width: 0.0, height: 0.0);
|
|
||||||
} else {
|
|
||||||
onBadgeText = Container(
|
|
||||||
constraints: BoxConstraints(maxWidth: 50),
|
|
||||||
padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0),
|
|
||||||
child: Text("$onBadgeTextValue",
|
|
||||||
style: Theme.of(context).textTheme.overline.copyWith(
|
|
||||||
color: HAClientTheme().getOnBadgeTextColor()
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
softWrap: false,
|
|
||||||
overflow: TextOverflow.ellipsis
|
|
||||||
),
|
|
||||||
decoration: new BoxDecoration(
|
|
||||||
color: iconColor,
|
|
||||||
borderRadius: BorderRadius.circular(9.0),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return GestureDetector(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
Stack(
|
|
||||||
overflow: Overflow.visible,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
width: 45,
|
|
||||||
height: 45,
|
|
||||||
decoration: new BoxDecoration(
|
|
||||||
// Circle shape
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
color: Theme.of(context).cardColor,
|
|
||||||
// The border you want
|
|
||||||
border: Border.all(
|
|
||||||
width: 2.0,
|
|
||||||
color: iconColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 41,
|
|
||||||
height: 41,
|
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: badgeIcon,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
bottom: -6,
|
|
||||||
child: onBadgeText
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
constraints: BoxConstraints(maxWidth: 45),
|
|
||||||
padding: EdgeInsets.only(top: 10),
|
|
||||||
child: Text(
|
|
||||||
"${entityModel.entityWrapper.displayName}",
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: Theme.of(context).textTheme.caption.copyWith(
|
|
||||||
fontSize: 10
|
|
||||||
),
|
|
||||||
softWrap: true,
|
|
||||||
maxLines: 3,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () => entityModel.entityWrapper.handleTap(),
|
|
||||||
onDoubleTap: () => entityModel.entityWrapper.handleDoubleTap(),
|
|
||||||
onLongPress: () => entityModel.entityWrapper.handleHold(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,666 +1,64 @@
|
|||||||
part of '../main.dart';
|
part of '../main.dart';
|
||||||
|
|
||||||
class CardData {
|
class HACard {
|
||||||
|
|
||||||
String type;
|
|
||||||
List<EntityWrapper> entities = [];
|
List<EntityWrapper> entities = [];
|
||||||
List conditions;
|
List<HACard> childCards = [];
|
||||||
|
EntityWrapper linkedEntityWrapper;
|
||||||
|
String name;
|
||||||
|
String id;
|
||||||
|
String type;
|
||||||
|
bool showName;
|
||||||
|
bool showState;
|
||||||
bool showEmpty;
|
bool showEmpty;
|
||||||
|
int columnsCount;
|
||||||
List stateFilter;
|
List stateFilter;
|
||||||
bool stateColor = true;
|
List states;
|
||||||
|
List conditions;
|
||||||
|
String content;
|
||||||
|
String unit;
|
||||||
|
int min;
|
||||||
|
int max;
|
||||||
|
Map severity;
|
||||||
|
|
||||||
EntityWrapper get entity => entities.isNotEmpty ? entities[0] : null;
|
HACard({
|
||||||
|
this.name,
|
||||||
factory CardData.parse(rawData) {
|
this.id,
|
||||||
try {
|
this.linkedEntityWrapper,
|
||||||
if (rawData['type'] == null) {
|
this.columnsCount: 4,
|
||||||
rawData['type'] = CardType.ENTITIES;
|
this.showName: true,
|
||||||
} else if (!(rawData['type'] is String)) {
|
this.showState: true,
|
||||||
return CardData(null);
|
this.stateFilter: const [],
|
||||||
}
|
this.showEmpty: true,
|
||||||
switch (rawData['type']) {
|
this.content,
|
||||||
case CardType.ENTITIES:
|
this.states,
|
||||||
case CardType.HISTORY_GRAPH:
|
this.conditions: const [],
|
||||||
case CardType.MAP:
|
this.unit,
|
||||||
case CardType.PICTURE_GLANCE:
|
this.min,
|
||||||
case CardType.SENSOR:
|
this.max,
|
||||||
case CardType.ENTITY:
|
this.severity,
|
||||||
case CardType.WEATHER_FORECAST:
|
@required this.type
|
||||||
case CardType.PLANT_STATUS:
|
}) {
|
||||||
if (rawData['entity'] != null) {
|
if (this.columnsCount <= 0) {
|
||||||
rawData['entities'] = [rawData['entity']];
|
this.columnsCount = 4;
|
||||||
}
|
|
||||||
return EntitiesCardData(rawData);
|
|
||||||
break;
|
|
||||||
case CardType.ALARM_PANEL:
|
|
||||||
return AlarmPanelCardData(rawData);
|
|
||||||
break;
|
|
||||||
case CardType.LIGHT:
|
|
||||||
return LightCardData(rawData);
|
|
||||||
break;
|
|
||||||
case CardType.PICTURE_ELEMENTS:
|
|
||||||
//TODO temporary solution
|
|
||||||
if (rawData.containsKey('camera_image')) {
|
|
||||||
rawData['entity'] = rawData['camera_image'];
|
|
||||||
return ButtonCardData(rawData);
|
|
||||||
} else {
|
|
||||||
return CardData(null);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case CardType.ENTITY_BUTTON:
|
|
||||||
case CardType.BUTTON:
|
|
||||||
case CardType.PICTURE_ENTITY:
|
|
||||||
return ButtonCardData(rawData);
|
|
||||||
break;
|
|
||||||
case CardType.CONDITIONAL:
|
|
||||||
return CardData.parse(rawData['card']);
|
|
||||||
break;
|
|
||||||
case CardType.ENTITY_FILTER:
|
|
||||||
Map cardData = Map.from(rawData);
|
|
||||||
cardData.remove('type');
|
|
||||||
if (rawData.containsKey('card')) {
|
|
||||||
cardData.addAll(rawData['card']);
|
|
||||||
}
|
|
||||||
cardData['type'] ??= CardType.ENTITIES;
|
|
||||||
return CardData.parse(cardData);
|
|
||||||
break;
|
|
||||||
case CardType.GAUGE:
|
|
||||||
return GaugeCardData(rawData);
|
|
||||||
break;
|
|
||||||
case CardType.GLANCE:
|
|
||||||
case CardType.THERMOSTAT:
|
|
||||||
if (rawData['entity'] != null) {
|
|
||||||
rawData['entities'] = [rawData['entity']];
|
|
||||||
}
|
|
||||||
return GlanceCardData(rawData);
|
|
||||||
break;
|
|
||||||
case CardType.HORIZONTAL_STACK:
|
|
||||||
return HorizontalStackCardData(rawData);
|
|
||||||
break;
|
|
||||||
case CardType.VERTICAL_STACK:
|
|
||||||
return VerticalStackCardData(rawData);
|
|
||||||
break;
|
|
||||||
case CardType.MARKDOWN:
|
|
||||||
return MarkdownCardData(rawData);
|
|
||||||
break;
|
|
||||||
case CardType.MEDIA_CONTROL:
|
|
||||||
return MediaControlCardData(rawData);
|
|
||||||
break;
|
|
||||||
case CardType.BADGES:
|
|
||||||
return BadgesData(rawData);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return CardData(null);
|
|
||||||
}
|
|
||||||
} catch (error, stacktrace) {
|
|
||||||
Logger.e('Error parsing card $rawData: $error', stacktrace: stacktrace);
|
|
||||||
return ErrorCardData(rawData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CardData(rawData) {
|
|
||||||
if (rawData != null && rawData is Map) {
|
|
||||||
type = rawData['type'];
|
|
||||||
conditions = rawData['conditions'] ?? [];
|
|
||||||
showEmpty = rawData['show_empty'] ?? true;
|
|
||||||
stateFilter = rawData['state_filter'] ?? [];
|
|
||||||
} else {
|
|
||||||
type = CardType.UNKNOWN;
|
|
||||||
conditions = [];
|
|
||||||
showEmpty = true;
|
|
||||||
stateFilter = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildCardWidget() {
|
|
||||||
return UnsupportedCard(card: this);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<EntityWrapper> getEntitiesToShow() {
|
List<EntityWrapper> getEntitiesToShow() {
|
||||||
return entities.where((entityWrapper) {
|
return entities.where((entityWrapper) {
|
||||||
if (entityWrapper.entity.isHidden) {
|
if (!ConnectionManager().useLovelace && entityWrapper.entity.isHidden) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
List currentStateFilter;
|
if (stateFilter.isNotEmpty) {
|
||||||
if (entityWrapper.stateFilter != null && entityWrapper.stateFilter.isNotEmpty) {
|
return stateFilter.contains(entityWrapper.entity.state);
|
||||||
currentStateFilter = entityWrapper.stateFilter;
|
|
||||||
} else {
|
|
||||||
currentStateFilter = stateFilter;
|
|
||||||
}
|
}
|
||||||
bool showByFilter = currentStateFilter.isEmpty;
|
return true;
|
||||||
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, stacktrace) {
|
|
||||||
Logger.e('Error filtering ${entityWrapper.entity.entityId} by $allowedState: $e', stacktrace: stacktrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return showByFilter;
|
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
Widget build(BuildContext context) {
|
||||||
|
return CardWidget(
|
||||||
class BadgesData extends CardData {
|
card: this,
|
||||||
|
);
|
||||||
String title;
|
|
||||||
String icon;
|
|
||||||
bool showHeaderToggle;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildCardWidget() {
|
|
||||||
return Badges(badges: this);
|
|
||||||
}
|
|
||||||
|
|
||||||
BadgesData(rawData) : super(rawData) {
|
|
||||||
if (rawData['badges'] is List) {
|
|
||||||
rawData['badges'].forEach((dynamic rawBadge) {
|
|
||||||
if (rawBadge is String && HomeAssistant().entities.isExist(rawBadge)) {
|
|
||||||
entities.add(EntityWrapper(entity: HomeAssistant().entities.get(rawBadge)));
|
|
||||||
} else if (rawBadge is Map && rawBadge.containsKey('entity') && HomeAssistant().entities.isExist(rawBadge['entity'])) {
|
|
||||||
entities.add(
|
|
||||||
EntityWrapper(
|
|
||||||
entity: HomeAssistant().entities.get(rawBadge['entity']),
|
|
||||||
overrideName: rawBadge["name"]?.toString(),
|
|
||||||
overrideIcon: rawBadge["icon"],
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else if (rawBadge is Map && rawBadge.containsKey('entities')) {
|
|
||||||
_parseEntities(rawBadge);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _parseEntities(rawData) {
|
|
||||||
var rawEntities = rawData['entities'] ?? [];
|
|
||||||
rawEntities.forEach((rawEntity) {
|
|
||||||
if (rawEntity is String) {
|
|
||||||
if (HomeAssistant().entities.isExist(rawEntity)) {
|
|
||||||
entities.add(EntityWrapper(
|
|
||||||
entity: HomeAssistant().entities.get(rawEntity),
|
|
||||||
stateFilter: rawData['state_filter'] ?? [],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else if (HomeAssistant().entities.isExist('${rawEntity['entity']}')) {
|
|
||||||
Entity e = HomeAssistant().entities.get(rawEntity["entity"]);
|
|
||||||
entities.add(
|
|
||||||
EntityWrapper(
|
|
||||||
entity: e,
|
|
||||||
overrideName: rawEntity["name"]?.toString(),
|
|
||||||
overrideIcon: rawEntity["icon"],
|
|
||||||
stateFilter: rawEntity['state_filter'] ?? (rawData['state_filter'] ?? []),
|
|
||||||
uiAction: EntityUIAction(rawEntityData: rawEntity)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class EntitiesCardData extends CardData {
|
|
||||||
|
|
||||||
String title;
|
|
||||||
String icon;
|
|
||||||
bool showHeaderToggle;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildCardWidget() {
|
|
||||||
return EntitiesCard(card: this);
|
|
||||||
}
|
|
||||||
|
|
||||||
EntitiesCardData(rawData) : super(rawData) {
|
|
||||||
//Parsing card data
|
|
||||||
title = rawData['title']?.toString();
|
|
||||||
icon = rawData['icon'] is String ? rawData['icon'] : null;
|
|
||||||
stateColor = rawData['state_color'] ?? false;
|
|
||||||
showHeaderToggle = rawData['show_header_toggle'] ?? false;
|
|
||||||
//Parsing entities
|
|
||||||
var rawEntities = rawData['entities'] ?? [];
|
|
||||||
rawEntities.forEach((rawEntity) {
|
|
||||||
if (rawEntity is String) {
|
|
||||||
if (HomeAssistant().entities.isExist(rawEntity)) {
|
|
||||||
entities.add(EntityWrapper(entity: HomeAssistant().entities.get(rawEntity)));
|
|
||||||
} else {
|
|
||||||
entities.add(EntityWrapper(entity: Entity.missed(rawEntity)));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (rawEntity["type"] == "divider") {
|
|
||||||
entities.add(EntityWrapper(entity: Entity.divider()));
|
|
||||||
} else if (rawEntity["type"] == "section") {
|
|
||||||
entities.add(EntityWrapper(entity: Entity.section(rawEntity["label"] ?? "")));
|
|
||||||
} else if (rawEntity["type"] == "call-service") {
|
|
||||||
Map uiActionData = {
|
|
||||||
"tap_action": {
|
|
||||||
"action": EntityUIAction.callService,
|
|
||||||
"service": rawEntity["service"],
|
|
||||||
"service_data": rawEntity["service_data"]
|
|
||||||
},
|
|
||||||
"hold_action": EntityUIAction.none
|
|
||||||
};
|
|
||||||
entities.add(
|
|
||||||
EntityWrapper(
|
|
||||||
entity: Entity.callService(
|
|
||||||
icon: rawEntity["icon"],
|
|
||||||
name: rawEntity["name"]?.toString(),
|
|
||||||
service: rawEntity["service"],
|
|
||||||
actionName: rawEntity["action_name"]
|
|
||||||
),
|
|
||||||
stateColor: rawEntity["state_color"] ?? stateColor,
|
|
||||||
uiAction: EntityUIAction(rawEntityData: uiActionData)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else if (rawEntity["type"] == "weblink") {
|
|
||||||
Map uiActionData = {
|
|
||||||
"tap_action": {
|
|
||||||
"action": EntityUIAction.navigate,
|
|
||||||
"service": rawEntity["url"]
|
|
||||||
},
|
|
||||||
"hold_action": EntityUIAction.none
|
|
||||||
};
|
|
||||||
entities.add(EntityWrapper(
|
|
||||||
entity: Entity.weblink(
|
|
||||||
icon: rawEntity["icon"],
|
|
||||||
name: rawEntity["name"]?.toString(),
|
|
||||||
url: rawEntity["url"]
|
|
||||||
),
|
|
||||||
stateColor: rawEntity["state_color"] ?? stateColor,
|
|
||||||
uiAction: EntityUIAction(rawEntityData: uiActionData)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else if (HomeAssistant().entities.isExist(rawEntity["entity"])) {
|
|
||||||
Entity e = HomeAssistant().entities.get(rawEntity["entity"]);
|
|
||||||
entities.add(
|
|
||||||
EntityWrapper(
|
|
||||||
entity: e,
|
|
||||||
stateColor: rawEntity["state_color"] ?? stateColor,
|
|
||||||
overrideName: rawEntity["name"]?.toString(),
|
|
||||||
overrideIcon: rawEntity["icon"],
|
|
||||||
stateFilter: rawEntity['state_filter'] ?? [],
|
|
||||||
uiAction: EntityUIAction(rawEntityData: rawEntity)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
entities.add(EntityWrapper(entity: Entity.missed(rawEntity["entity"])));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class AlarmPanelCardData extends CardData {
|
|
||||||
|
|
||||||
String name;
|
|
||||||
List<dynamic> states;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildCardWidget() {
|
|
||||||
return AlarmPanelCard(card: this);
|
|
||||||
}
|
|
||||||
|
|
||||||
AlarmPanelCardData(rawData) : super(rawData) {
|
|
||||||
//Parsing card data
|
|
||||||
name = rawData['name']?.toString();
|
|
||||||
states = rawData['states'];
|
|
||||||
//Parsing entity
|
|
||||||
var entitiId = rawData["entity"];
|
|
||||||
if (entitiId != null && entitiId is String) {
|
|
||||||
if (HomeAssistant().entities.isExist(entitiId)) {
|
|
||||||
entities.add(EntityWrapper(
|
|
||||||
entity: HomeAssistant().entities.get(entitiId),
|
|
||||||
stateColor: true,
|
|
||||||
overrideName: name
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
entities.add(EntityWrapper(entity: Entity.missed(entitiId)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class LightCardData extends CardData {
|
|
||||||
|
|
||||||
String name;
|
|
||||||
String icon;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildCardWidget() {
|
|
||||||
return LightCard(card: this);
|
|
||||||
}
|
|
||||||
|
|
||||||
LightCardData(rawData) : super(rawData) {
|
|
||||||
//Parsing card data
|
|
||||||
name = rawData['name']?.toString();
|
|
||||||
icon = rawData['icon'] is String ? rawData['icon'] : null;
|
|
||||||
//Parsing entity
|
|
||||||
var entitiId = rawData["entity"];
|
|
||||||
if (entitiId != null && entitiId is String) {
|
|
||||||
if (HomeAssistant().entities.isExist(entitiId)) {
|
|
||||||
entities.add(EntityWrapper(
|
|
||||||
entity: HomeAssistant().entities.get(entitiId),
|
|
||||||
overrideName: name,
|
|
||||||
overrideIcon: icon,
|
|
||||||
uiAction: EntityUIAction()..tapAction = EntityUIAction.toggle
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
entities.add(EntityWrapper(entity: Entity.missed(entitiId)));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
entities.add(EntityWrapper(entity: Entity.missed('$entitiId')));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ButtonCardData extends CardData {
|
|
||||||
|
|
||||||
String name;
|
|
||||||
String icon;
|
|
||||||
bool showName;
|
|
||||||
bool showIcon;
|
|
||||||
double iconHeightPx = 0;
|
|
||||||
double iconHeightRem = 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildCardWidget() {
|
|
||||||
return EntityButtonCard(card: this);
|
|
||||||
}
|
|
||||||
|
|
||||||
ButtonCardData(rawData) : super(rawData) {
|
|
||||||
//Parsing card data
|
|
||||||
name = rawData['name']?.toString();
|
|
||||||
icon = rawData['icon'] is String ? rawData['icon'] : null;
|
|
||||||
showName = rawData['show_name'] ?? true;
|
|
||||||
showIcon = rawData['show_icon'] ?? true;
|
|
||||||
stateColor = rawData['state_color'] ?? true;
|
|
||||||
var rawHeight = rawData['icon_height'];
|
|
||||||
if (rawHeight != null && rawHeight is String) {
|
|
||||||
if (rawHeight.contains('px')) {
|
|
||||||
iconHeightPx = double.tryParse(rawHeight.replaceFirst('px', '')) ?? 0;
|
|
||||||
} else if (rawHeight.contains('rem')) {
|
|
||||||
iconHeightRem = double.tryParse(rawHeight.replaceFirst('rem', '')) ?? 0;
|
|
||||||
} else if (rawHeight.contains('em')) {
|
|
||||||
iconHeightRem = double.tryParse(rawHeight.replaceFirst('em', '')) ?? 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Parsing entity
|
|
||||||
var entitiId = rawData["entity"];
|
|
||||||
if (entitiId != null && entitiId is String) {
|
|
||||||
if (HomeAssistant().entities.isExist(entitiId)) {
|
|
||||||
entities.add(EntityWrapper(
|
|
||||||
entity: HomeAssistant().entities.get(entitiId),
|
|
||||||
overrideName: name,
|
|
||||||
overrideIcon: icon,
|
|
||||||
stateColor: stateColor,
|
|
||||||
uiAction: EntityUIAction(
|
|
||||||
rawEntityData: rawData
|
|
||||||
)
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
entities.add(EntityWrapper(entity: Entity.missed(entitiId)));
|
|
||||||
}
|
|
||||||
} else if (entitiId == null) {
|
|
||||||
entities.add(
|
|
||||||
EntityWrapper(
|
|
||||||
entity: Entity.ghost(
|
|
||||||
name,
|
|
||||||
icon,
|
|
||||||
),
|
|
||||||
stateColor: stateColor,
|
|
||||||
uiAction: EntityUIAction(
|
|
||||||
rawEntityData: rawData
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GaugeCardData extends CardData {
|
|
||||||
|
|
||||||
String name;
|
|
||||||
String unit;
|
|
||||||
double min;
|
|
||||||
double max;
|
|
||||||
Map severity;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildCardWidget() {
|
|
||||||
return GaugeCard(card: this);
|
|
||||||
}
|
|
||||||
|
|
||||||
GaugeCardData(rawData) : super(rawData) {
|
|
||||||
//Parsing card data
|
|
||||||
name = rawData['name']?.toString();
|
|
||||||
unit = rawData['unit'];
|
|
||||||
if (rawData['min'] is int) {
|
|
||||||
min = rawData['min'].toDouble();
|
|
||||||
} else if (rawData['min'] is double) {
|
|
||||||
min = rawData['min'];
|
|
||||||
} else {
|
|
||||||
min = 0;
|
|
||||||
}
|
|
||||||
if (rawData['max'] is int) {
|
|
||||||
max = rawData['max'].toDouble();
|
|
||||||
} else if (rawData['max'] is double) {
|
|
||||||
max = rawData['max'];
|
|
||||||
} else {
|
|
||||||
max = 100;
|
|
||||||
}
|
|
||||||
severity = rawData['severity'];
|
|
||||||
//Parsing entity
|
|
||||||
var entitiId = rawData["entity"] is List ? rawData["entity"][0] : rawData["entity"];
|
|
||||||
if (entitiId != null && entitiId is String) {
|
|
||||||
if (HomeAssistant().entities.isExist(entitiId)) {
|
|
||||||
entities.add(EntityWrapper(
|
|
||||||
entity: HomeAssistant().entities.get(entitiId),
|
|
||||||
overrideName: name
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
entities.add(EntityWrapper(entity: Entity.missed(entitiId)));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
entities.add(EntityWrapper(entity: Entity.missed('$entitiId')));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class GlanceCardData extends CardData {
|
|
||||||
|
|
||||||
String title;
|
|
||||||
bool showName;
|
|
||||||
bool showIcon;
|
|
||||||
bool showState;
|
|
||||||
bool stateColor;
|
|
||||||
int columnsCount;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildCardWidget() {
|
|
||||||
return GlanceCard(card: this);
|
|
||||||
}
|
|
||||||
|
|
||||||
GlanceCardData(rawData) : super(rawData) {
|
|
||||||
//Parsing card data
|
|
||||||
title = rawData["title"]?.toString();
|
|
||||||
showName = rawData['show_name'] ?? true;
|
|
||||||
showIcon = rawData['show_icon'] ?? true;
|
|
||||||
showState = rawData['show_state'] ?? true;
|
|
||||||
stateColor = rawData['state_color'] ?? true;
|
|
||||||
columnsCount = rawData['columns'] ?? 4;
|
|
||||||
//Parsing entities
|
|
||||||
var rawEntities = rawData["entities"] ?? [];
|
|
||||||
rawEntities.forEach((rawEntity) {
|
|
||||||
if (rawEntity is String) {
|
|
||||||
if (HomeAssistant().entities.isExist(rawEntity)) {
|
|
||||||
entities.add(EntityWrapper(entity: HomeAssistant().entities.get(rawEntity)));
|
|
||||||
} else {
|
|
||||||
entities.add(EntityWrapper(entity: Entity.missed(rawEntity)));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (HomeAssistant().entities.isExist(rawEntity["entity"])) {
|
|
||||||
Entity e = HomeAssistant().entities.get(rawEntity["entity"]);
|
|
||||||
entities.add(
|
|
||||||
EntityWrapper(
|
|
||||||
entity: e,
|
|
||||||
stateColor: stateColor,
|
|
||||||
overrideName: rawEntity["name"]?.toString(),
|
|
||||||
overrideIcon: rawEntity["icon"],
|
|
||||||
stateFilter: rawEntity['state_filter'] ?? [],
|
|
||||||
uiAction: EntityUIAction(rawEntityData: rawEntity)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
entities.add(EntityWrapper(entity: Entity.missed(rawEntity["entity"])));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class HorizontalStackCardData extends CardData {
|
|
||||||
|
|
||||||
List<CardData> childCards;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildCardWidget() {
|
|
||||||
return HorizontalStackCard(card: this);
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizontalStackCardData(rawData) : super(rawData) {
|
|
||||||
if (rawData.containsKey('cards') && rawData['cards'] is List) {
|
|
||||||
childCards = rawData['cards'].map<CardData>((childCard) {
|
|
||||||
return CardData.parse(childCard);
|
|
||||||
}).toList();
|
|
||||||
} else {
|
|
||||||
childCards = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class VerticalStackCardData extends CardData {
|
|
||||||
|
|
||||||
List<CardData> childCards;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildCardWidget() {
|
|
||||||
return VerticalStackCard(card: this);
|
|
||||||
}
|
|
||||||
|
|
||||||
VerticalStackCardData(rawData) : super(rawData) {
|
|
||||||
if (rawData.containsKey('cards') && rawData['cards'] is List) {
|
|
||||||
childCards = rawData['cards'].map<CardData>((childCard) {
|
|
||||||
return CardData.parse(childCard);
|
|
||||||
}).toList();
|
|
||||||
} else {
|
|
||||||
childCards = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class MarkdownCardData extends CardData {
|
|
||||||
|
|
||||||
String title;
|
|
||||||
String content;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildCardWidget() {
|
|
||||||
return MarkdownCard(card: this);
|
|
||||||
}
|
|
||||||
|
|
||||||
MarkdownCardData(rawData) : super(rawData) {
|
|
||||||
//Parsing card data
|
|
||||||
title = rawData['title'];
|
|
||||||
content = rawData['content'];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class MediaControlCardData extends CardData {
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildCardWidget() {
|
|
||||||
return MediaControlsCard(card: this);
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaControlCardData(rawData) : super(rawData) {
|
|
||||||
var entitiId = rawData["entity"];
|
|
||||||
if (entitiId != null && entitiId is String) {
|
|
||||||
if (HomeAssistant().entities.isExist(entitiId)) {
|
|
||||||
entities.add(EntityWrapper(
|
|
||||||
entity: HomeAssistant().entities.get(entitiId),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
entities.add(EntityWrapper(entity: Entity.missed(entitiId)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class ErrorCardData extends CardData {
|
|
||||||
|
|
||||||
String cardConfig;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildCardWidget() {
|
|
||||||
return ErrorCard(card: this);
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCardData(rawData) : super(rawData) {
|
|
||||||
cardConfig = '$rawData';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
361
lib/cards/card_widget.dart
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class CardWidget extends StatelessWidget {
|
||||||
|
|
||||||
|
final HACard card;
|
||||||
|
|
||||||
|
const CardWidget({
|
||||||
|
Key key,
|
||||||
|
this.card
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (card.linkedEntityWrapper!= null) {
|
||||||
|
if (card.linkedEntityWrapper.entity.isHidden) {
|
||||||
|
return Container(width: 0.0, height: 0.0,);
|
||||||
|
}
|
||||||
|
if (card.linkedEntityWrapper.entity.statelessType == StatelessEntityType.MISSED) {
|
||||||
|
return EntityModel(
|
||||||
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
|
child: MissedEntityWidget(),
|
||||||
|
handleTap: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (card.conditions.isNotEmpty) {
|
||||||
|
bool showCardByConditions = true;
|
||||||
|
for (var condition in card.conditions) {
|
||||||
|
Entity conditionEntity = HomeAssistant().entities.get(condition['entity']);
|
||||||
|
if (conditionEntity != null &&
|
||||||
|
((condition['state'] != null && conditionEntity.state != condition['state']) ||
|
||||||
|
(condition['state_not'] != null && conditionEntity.state == condition['state_not']))
|
||||||
|
) {
|
||||||
|
showCardByConditions = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!showCardByConditions) {
|
||||||
|
return Container(width: 0.0, height: 0.0,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (card.type) {
|
||||||
|
|
||||||
|
case CardType.ENTITIES: {
|
||||||
|
return _buildEntitiesCard(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
case CardType.GLANCE: {
|
||||||
|
return _buildGlanceCard(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
case CardType.MEDIA_CONTROL: {
|
||||||
|
return _buildMediaControlsCard(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
case CardType.ENTITY_BUTTON: {
|
||||||
|
return _buildEntityButtonCard(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
case CardType.GAUGE: {
|
||||||
|
return _buildGaugeCard(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* case CardType.LIGHT: {
|
||||||
|
return _buildLightCard(context);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
case CardType.MARKDOWN: {
|
||||||
|
return _buildMarkdownCard(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
case CardType.ALARM_PANEL: {
|
||||||
|
return _buildAlarmPanelCard(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
case CardType.HORIZONTAL_STACK: {
|
||||||
|
if (card.childCards.isNotEmpty) {
|
||||||
|
List<Widget> children = [];
|
||||||
|
card.childCards.forEach((card) {
|
||||||
|
if (card.getEntitiesToShow().isNotEmpty || card.showEmpty) {
|
||||||
|
children.add(
|
||||||
|
Flexible(
|
||||||
|
fit: FlexFit.tight,
|
||||||
|
child: card.build(context),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container(height: 0.0, width: 0.0,);
|
||||||
|
}
|
||||||
|
|
||||||
|
case CardType.VERTICAL_STACK: {
|
||||||
|
if (card.childCards.isNotEmpty) {
|
||||||
|
List<Widget> children = [];
|
||||||
|
card.childCards.forEach((card) {
|
||||||
|
children.add(
|
||||||
|
card.build(context)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container(height: 0.0, width: 0.0,);
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
if ((card.linkedEntityWrapper == null) && (card.entities.isNotEmpty)) {
|
||||||
|
return _buildEntitiesCard(context);
|
||||||
|
} else {
|
||||||
|
return _buildUnsupportedCard(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEntitiesCard(BuildContext context) {
|
||||||
|
List<EntityWrapper> entitiesToShow = card.getEntitiesToShow();
|
||||||
|
if (entitiesToShow.isEmpty && !card.showEmpty) {
|
||||||
|
return Container(height: 0.0, width: 0.0,);
|
||||||
|
}
|
||||||
|
List<Widget> body = [];
|
||||||
|
body.add(CardHeader(name: card.name));
|
||||||
|
entitiesToShow.forEach((EntityWrapper entity) {
|
||||||
|
body.add(
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0, 4.0, 0.0, 4.0),
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: entity,
|
||||||
|
handleTap: true,
|
||||||
|
child: entity.entity.buildDefaultWidget(context)
|
||||||
|
),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(right: Sizes.rightWidgetPadding, left: Sizes.leftWidgetPadding),
|
||||||
|
child: Column(mainAxisSize: MainAxisSize.min, children: body),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMarkdownCard(BuildContext context) {
|
||||||
|
if (card.content == null) {
|
||||||
|
return Container(height: 0.0, width: 0.0,);
|
||||||
|
}
|
||||||
|
List<Widget> body = [];
|
||||||
|
body.add(CardHeader(name: card.name));
|
||||||
|
body.add(MarkdownBody(data: card.content));
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
|
||||||
|
child: new Column(mainAxisSize: MainAxisSize.min, children: body),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAlarmPanelCard(BuildContext context) {
|
||||||
|
List<Widget> body = [];
|
||||||
|
body.add(CardHeader(
|
||||||
|
name: card.name ?? "",
|
||||||
|
subtitle: Text("${card.linkedEntityWrapper.entity.displayState}",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
EntityIcon(
|
||||||
|
size: 50.0,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 26.0,
|
||||||
|
child: IconButton(
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
|
||||||
|
"mdi:dots-vertical")),
|
||||||
|
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity: card.linkedEntityWrapper.entity))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
));
|
||||||
|
body.add(
|
||||||
|
AlarmControlPanelControlsWidget(
|
||||||
|
extended: true,
|
||||||
|
states: card.states,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return Card(
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
|
handleTap: null,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: body
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildGlanceCard(BuildContext context) {
|
||||||
|
List<EntityWrapper> entitiesToShow = card.getEntitiesToShow();
|
||||||
|
if (entitiesToShow.isEmpty && !card.showEmpty) {
|
||||||
|
return Container(height: 0.0, width: 0.0,);
|
||||||
|
}
|
||||||
|
List<Widget> rows = [];
|
||||||
|
rows.add(CardHeader(name: card.name));
|
||||||
|
|
||||||
|
int columnsCount = entitiesToShow.length >= card.columnsCount ? card.columnsCount : entitiesToShow.length;
|
||||||
|
|
||||||
|
rows.add(
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: Sizes.rowPadding, top: Sizes.rowPadding),
|
||||||
|
child: FractionallySizedBox(
|
||||||
|
widthFactor: 1,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
List<Widget> buttons = [];
|
||||||
|
double buttonWidth = constraints.maxWidth / columnsCount;
|
||||||
|
entitiesToShow.forEach((EntityWrapper entity) {
|
||||||
|
buttons.add(
|
||||||
|
SizedBox(
|
||||||
|
width: buttonWidth,
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: entity,
|
||||||
|
child: GlanceCardEntityContainer(
|
||||||
|
showName: card.showName,
|
||||||
|
showState: card.showState,
|
||||||
|
),
|
||||||
|
handleTap: true
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return Wrap(
|
||||||
|
//spacing: 5.0,
|
||||||
|
//alignment: WrapAlignment.spaceEvenly,
|
||||||
|
runSpacing: Sizes.doubleRowPadding,
|
||||||
|
children: buttons,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: rows
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMediaControlsCard(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
|
handleTap: null,
|
||||||
|
child: MediaPlayerWidget()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEntityButtonCard(BuildContext context) {
|
||||||
|
card.linkedEntityWrapper.displayName = card.name?.toUpperCase() ??
|
||||||
|
card.linkedEntityWrapper.displayName.toUpperCase();
|
||||||
|
return Card(
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
|
child: EntityButtonCardBody(),
|
||||||
|
handleTap: true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildGaugeCard(BuildContext context) {
|
||||||
|
card.linkedEntityWrapper.displayName = card.name ??
|
||||||
|
card.linkedEntityWrapper.displayName;
|
||||||
|
card.linkedEntityWrapper.unitOfMeasurement = card.unit ??
|
||||||
|
card.linkedEntityWrapper.unitOfMeasurement;
|
||||||
|
return Card(
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
|
child: GaugeCardBody(
|
||||||
|
min: card.min,
|
||||||
|
max: card.max,
|
||||||
|
severity: card.severity,
|
||||||
|
),
|
||||||
|
handleTap: true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLightCard(BuildContext context) {
|
||||||
|
card.linkedEntityWrapper.displayName = card.name ??
|
||||||
|
card.linkedEntityWrapper.displayName;
|
||||||
|
return Card(
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
|
child: LightCardBody(
|
||||||
|
min: card.min,
|
||||||
|
max: card.max,
|
||||||
|
severity: card.severity,
|
||||||
|
),
|
||||||
|
handleTap: true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildUnsupportedCard(BuildContext context) {
|
||||||
|
List<Widget> body = [];
|
||||||
|
body.add(CardHeader(name: card.name ?? ""));
|
||||||
|
List<Widget> result = [];
|
||||||
|
if (card.linkedEntityWrapper != null) {
|
||||||
|
result.addAll(<Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||||
|
child: EntityModel(
|
||||||
|
entityWrapper: card.linkedEntityWrapper,
|
||||||
|
handleTap: true,
|
||||||
|
child: card.linkedEntityWrapper.entity.buildDefaultWidget(context)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
result.addAll(<Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
|
||||||
|
child: Text("'${card.type}' card is not supported yet"),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
body.addAll(result);
|
||||||
|
return Card(
|
||||||
|
child: new Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: body
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,79 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class EntitiesCard extends StatelessWidget {
|
|
||||||
final EntitiesCardData card;
|
|
||||||
|
|
||||||
const EntitiesCard({Key key, this.card}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
List<EntityWrapper> entitiesToShow = card.getEntitiesToShow();
|
|
||||||
if (entitiesToShow.isEmpty && !card.showEmpty) {
|
|
||||||
return Container(height: 0.0, width: 0.0,);
|
|
||||||
}
|
|
||||||
List<Widget> body = [];
|
|
||||||
Widget headerSwitch;
|
|
||||||
if (card.showHeaderToggle) {
|
|
||||||
bool headerToggleVal = entitiesToShow.any((EntityWrapper en){ return en.entity.state == EntityState.on; });
|
|
||||||
List<String> entitiesToToggle = entitiesToShow.where((EntityWrapper enw) {
|
|
||||||
return <String>["switch", "light", "automation", "input_boolean"].contains(enw.entity.domain);
|
|
||||||
}).map((EntityWrapper en) {
|
|
||||||
return en.entity.entityId;
|
|
||||||
}).toList();
|
|
||||||
headerSwitch = Switch(
|
|
||||||
value: headerToggleVal,
|
|
||||||
onChanged: (val) {
|
|
||||||
if (entitiesToToggle.isNotEmpty) {
|
|
||||||
ConnectionManager().callService(
|
|
||||||
domain: "homeassistant",
|
|
||||||
service: val ? "turn_on" : "turn_off",
|
|
||||||
entityId: entitiesToToggle
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
body.add(
|
|
||||||
CardHeader(
|
|
||||||
name: card.title,
|
|
||||||
trailing: headerSwitch,
|
|
||||||
emptyPadding: Sizes.rowPadding,
|
|
||||||
leading: card.icon != null ? Icon(
|
|
||||||
MaterialDesignIcons.getIconDataFromIconName(card.icon),
|
|
||||||
size: Sizes.iconSize,
|
|
||||||
color: Theme.of(context).textTheme.headline.color
|
|
||||||
) : null,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
body.addAll(
|
|
||||||
entitiesToShow.map((EntityWrapper entity) {
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(0.0, 4.0, 0.0, 4.0),
|
|
||||||
child: EntityModel(
|
|
||||||
entityWrapper: entity,
|
|
||||||
handleTap: true,
|
|
||||||
child: entity.entity.buildDefaultWidget(context)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return CardWrapper(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
right: Sizes.rightWidgetPadding,
|
|
||||||
left: Sizes.leftWidgetPadding,
|
|
||||||
bottom: Sizes.rowPadding,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: body
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class EntityButtonCard extends StatelessWidget {
|
|
||||||
|
|
||||||
final ButtonCardData card;
|
|
||||||
|
|
||||||
EntityButtonCard({
|
|
||||||
Key key, this.card
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
EntityWrapper entityWrapper = card.entity;
|
|
||||||
if (entityWrapper.entity.statelessType == StatelessEntityType.missed) {
|
|
||||||
return EntityModel(
|
|
||||||
entityWrapper: card.entity,
|
|
||||||
child: MissedEntityWidget(),
|
|
||||||
handleTap: false,
|
|
||||||
);
|
|
||||||
} else if (entityWrapper.entity.statelessType != StatelessEntityType.ghost && entityWrapper.entity.statelessType != StatelessEntityType.none) {
|
|
||||||
return Container(width: 0.0, height: 0.0,);
|
|
||||||
}
|
|
||||||
|
|
||||||
double iconSize = math.max(card.iconHeightPx, card.iconHeightRem * Theme.of(context).textTheme.body1.fontSize);
|
|
||||||
|
|
||||||
Widget buttonIcon;
|
|
||||||
if (!card.showIcon) {
|
|
||||||
buttonIcon = Container(height: Sizes.rowPadding, width: 10);
|
|
||||||
} else if (iconSize > 0) {
|
|
||||||
buttonIcon = SizedBox(
|
|
||||||
height: iconSize,
|
|
||||||
child: FractionallySizedBox(
|
|
||||||
widthFactor: 0.5,
|
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
child: EntityIcon(
|
|
||||||
//padding: EdgeInsets.only(top: 6),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
buttonIcon = AspectRatio(
|
|
||||||
aspectRatio: 2,
|
|
||||||
child: FractionallySizedBox(
|
|
||||||
widthFactor: 0.5,
|
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.fitWidth,
|
|
||||||
child: EntityIcon(
|
|
||||||
//padding: EdgeInsets.only(top: 6),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return CardWrapper(
|
|
||||||
child: EntityModel(
|
|
||||||
entityWrapper: card.entity,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => entityWrapper.handleTap(),
|
|
||||||
onLongPress: () => entityWrapper.handleHold(),
|
|
||||||
onDoubleTap: () => entityWrapper.handleDoubleTap(),
|
|
||||||
child: Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(top: 5),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: <Widget>[
|
|
||||||
buttonIcon,
|
|
||||||
_buildName(context)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
handleTap: true
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildName(BuildContext context) {
|
|
||||||
if (card.showName) {
|
|
||||||
return EntityName(
|
|
||||||
padding: EdgeInsets.fromLTRB(Sizes.buttonPadding, 0.0, Sizes.buttonPadding, Sizes.rowPadding),
|
|
||||||
textOverflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 3,
|
|
||||||
textStyle: Theme.of(context).textTheme.subhead,
|
|
||||||
wordsWrap: true,
|
|
||||||
textAlign: TextAlign.center
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Container(width: 0, height: 0);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class ErrorCard extends StatelessWidget {
|
|
||||||
final ErrorCardData card;
|
|
||||||
|
|
||||||
const ErrorCard({Key key, this.card}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return CardWrapper(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
'There was an error rendering card: ${card.type}. Please copy card config to clipboard and report this issue. Thanks!',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
RaisedButton(
|
|
||||||
onPressed: () {
|
|
||||||
Clipboard.setData(new ClipboardData(text: card.cardConfig));
|
|
||||||
},
|
|
||||||
child: Text('Copy card config'),
|
|
||||||
),
|
|
||||||
RaisedButton(
|
|
||||||
onPressed: () {
|
|
||||||
Launcher.launchURLInBrowser("https://github.com/estevez-dev/ha_client/issues/new?assignees=&labels=&template=bug_report.md&title=");
|
|
||||||
},
|
|
||||||
child: Text('Report issue'),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,201 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class GaugeCard extends StatelessWidget {
|
|
||||||
|
|
||||||
final GaugeCardData card;
|
|
||||||
|
|
||||||
GaugeCard({Key key, this.card}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
EntityWrapper entityWrapper = card.entity;
|
|
||||||
if (entityWrapper.entity.statelessType == StatelessEntityType.missed) {
|
|
||||||
return EntityModel(
|
|
||||||
entityWrapper: card.entity,
|
|
||||||
child: MissedEntityWidget(),
|
|
||||||
handleTap: false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
entityWrapper.overrideName = card.name ??
|
|
||||||
entityWrapper.displayName;
|
|
||||||
entityWrapper.unitOfMeasurementOverride = card.unit ??
|
|
||||||
entityWrapper.unitOfMeasurement;
|
|
||||||
double fixedValue;
|
|
||||||
double value = entityWrapper.entity.doubleState;
|
|
||||||
if (value > card.max) {
|
|
||||||
fixedValue = card.max.toDouble();
|
|
||||||
} else if (value < card.min) {
|
|
||||||
fixedValue = card.min.toDouble();
|
|
||||||
} else {
|
|
||||||
fixedValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<GaugeRange> ranges;
|
|
||||||
Color currentColor;
|
|
||||||
if (card.severity != null && card.severity["green"] is int && card.severity["red"] is int && card.severity["yellow"] is int) {
|
|
||||||
List<RangeContainer> rangesList = <RangeContainer>[
|
|
||||||
RangeContainer(card.severity["green"], HAClientTheme().getGreenGaugeColor()),
|
|
||||||
RangeContainer(card.severity["red"], HAClientTheme().getRedGaugeColor()),
|
|
||||||
RangeContainer(card.severity["yellow"], HAClientTheme().getYellowGaugeColor())
|
|
||||||
];
|
|
||||||
rangesList.sort((current, next) {
|
|
||||||
if (current.startFrom > next.startFrom) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (current.startFrom < next.startFrom) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (fixedValue < rangesList[1].startFrom) {
|
|
||||||
currentColor = rangesList[0].color;
|
|
||||||
} else if (fixedValue < rangesList[2].startFrom && fixedValue >= rangesList[1].startFrom) {
|
|
||||||
currentColor = rangesList[1].color;
|
|
||||||
} else {
|
|
||||||
currentColor = rangesList[2].color;
|
|
||||||
}
|
|
||||||
|
|
||||||
ranges = [
|
|
||||||
GaugeRange(
|
|
||||||
startValue: rangesList[0].startFrom.toDouble(),
|
|
||||||
endValue: rangesList[1].startFrom.toDouble(),
|
|
||||||
color: rangesList[0].color.withOpacity(0.1),
|
|
||||||
sizeUnit: GaugeSizeUnit.factor,
|
|
||||||
endWidth: 0.3,
|
|
||||||
startWidth: 0.3
|
|
||||||
),
|
|
||||||
GaugeRange(
|
|
||||||
startValue: rangesList[1].startFrom.toDouble(),
|
|
||||||
endValue: rangesList[2].startFrom.toDouble(),
|
|
||||||
color: rangesList[1].color.withOpacity(0.1),
|
|
||||||
sizeUnit: GaugeSizeUnit.factor,
|
|
||||||
endWidth: 0.3,
|
|
||||||
startWidth: 0.3
|
|
||||||
),
|
|
||||||
GaugeRange(
|
|
||||||
startValue: rangesList[2].startFrom.toDouble(),
|
|
||||||
endValue: card.max.toDouble(),
|
|
||||||
color: rangesList[2].color.withOpacity(0.1),
|
|
||||||
sizeUnit: GaugeSizeUnit.factor,
|
|
||||||
endWidth: 0.3,
|
|
||||||
startWidth: 0.3
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
if (ranges == null) {
|
|
||||||
currentColor = Theme.of(context).primaryColorDark;
|
|
||||||
ranges = <GaugeRange>[
|
|
||||||
GaugeRange(
|
|
||||||
startValue: card.min.toDouble(),
|
|
||||||
endValue: card.max.toDouble(),
|
|
||||||
color: Theme.of(context).primaryColorDark.withOpacity(0.1),
|
|
||||||
sizeUnit: GaugeSizeUnit.factor,
|
|
||||||
endWidth: 0.3,
|
|
||||||
startWidth: 0.3,
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return CardWrapper(
|
|
||||||
padding: EdgeInsets.all(4),
|
|
||||||
child: EntityModel(
|
|
||||||
entityWrapper: entityWrapper,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => entityWrapper.handleTap(),
|
|
||||||
onLongPress: () => entityWrapper.handleHold(),
|
|
||||||
onDoubleTap: () => entityWrapper.handleDoubleTap(),
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 1.8,
|
|
||||||
child: Stack(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
children: <Widget>[
|
|
||||||
IgnorePointer(
|
|
||||||
ignoring: true,
|
|
||||||
child: SfRadialGauge(
|
|
||||||
axes: <RadialAxis>[
|
|
||||||
RadialAxis(
|
|
||||||
maximum: card.max.toDouble(),
|
|
||||||
minimum: card.min.toDouble(),
|
|
||||||
showLabels: false,
|
|
||||||
useRangeColorForAxis: true,
|
|
||||||
showTicks: false,
|
|
||||||
canScaleToFit: true,
|
|
||||||
ranges: ranges,
|
|
||||||
axisLineStyle: AxisLineStyle(
|
|
||||||
thickness: 0.3,
|
|
||||||
thicknessUnit: GaugeSizeUnit.factor,
|
|
||||||
color: Colors.transparent
|
|
||||||
),
|
|
||||||
startAngle: 180,
|
|
||||||
endAngle: 0,
|
|
||||||
pointers: <GaugePointer>[
|
|
||||||
RangePointer(
|
|
||||||
value: fixedValue,
|
|
||||||
sizeUnit: GaugeSizeUnit.factor,
|
|
||||||
width: 0.3,
|
|
||||||
color: currentColor,
|
|
||||||
enableAnimation: true,
|
|
||||||
animationType: AnimationType.bounceOut,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
Flexible(
|
|
||||||
flex: 8,
|
|
||||||
fit: FlexFit.tight,
|
|
||||||
child: Container()
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
flex: 6,
|
|
||||||
fit: FlexFit.tight,
|
|
||||||
child: FractionallySizedBox(
|
|
||||||
widthFactor: 0.4,
|
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: SimpleEntityState(
|
|
||||||
padding: EdgeInsets.all(0),
|
|
||||||
expanded: false,
|
|
||||||
maxLines: 1,
|
|
||||||
textAlign: TextAlign.center
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
flex: 3,
|
|
||||||
fit: FlexFit.tight,
|
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
child: EntityName(
|
|
||||||
padding: EdgeInsets.all(0),
|
|
||||||
textStyle: Theme.of(context).textTheme.subhead
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
handleTap: true
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class RangeContainer {
|
|
||||||
final int startFrom;
|
|
||||||
Color color;
|
|
||||||
|
|
||||||
RangeContainer(this.startFrom, this.color);
|
|
||||||
}
|
|
@ -1,126 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class GlanceCard extends StatelessWidget {
|
|
||||||
final GlanceCardData card;
|
|
||||||
|
|
||||||
const GlanceCard({Key key, this.card}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
List<EntityWrapper> entitiesToShow = card.getEntitiesToShow();
|
|
||||||
if (entitiesToShow.isEmpty && !card.showEmpty) {
|
|
||||||
return Container(height: 0.0, width: 0.0,);
|
|
||||||
}
|
|
||||||
int length = entitiesToShow.length;
|
|
||||||
int rowsCount;
|
|
||||||
int columnsCount;
|
|
||||||
if (length == 0) {
|
|
||||||
columnsCount = 0;
|
|
||||||
rowsCount = 0;
|
|
||||||
} else {
|
|
||||||
columnsCount = length >= card.columnsCount ? card.columnsCount : entitiesToShow.length;
|
|
||||||
rowsCount = (length / columnsCount).round();
|
|
||||||
}
|
|
||||||
List<TableRow> rows = [];
|
|
||||||
for (int i = 0; i < rowsCount; i++) {
|
|
||||||
int start = i*columnsCount;
|
|
||||||
int end = start + math.min(columnsCount, length - start);
|
|
||||||
List<Widget> rowChildren = [];
|
|
||||||
rowChildren.addAll(entitiesToShow.sublist(
|
|
||||||
start, end
|
|
||||||
).map(
|
|
||||||
(EntityWrapper entity){
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: Sizes.rowPadding),
|
|
||||||
child: EntityModel(
|
|
||||||
entityWrapper: entity,
|
|
||||||
child: _buildEntityContainer(context, entity),
|
|
||||||
handleTap: true
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
).toList()
|
|
||||||
);
|
|
||||||
while (rowChildren.length < columnsCount) {
|
|
||||||
rowChildren.add(
|
|
||||||
Container()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
rows.add(
|
|
||||||
TableRow(
|
|
||||||
children: rowChildren
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return CardWrapper(
|
|
||||||
child: Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
CardHeader(
|
|
||||||
name: card.title,
|
|
||||||
emptyPadding: Sizes.rowPadding,
|
|
||||||
),
|
|
||||||
Table(
|
|
||||||
children: rows
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildEntityContainer(BuildContext context, EntityWrapper entityWrapper) {
|
|
||||||
if (entityWrapper.entity.statelessType == StatelessEntityType.missed) {
|
|
||||||
return MissedEntityWidget();
|
|
||||||
} else if (entityWrapper.entity.statelessType != StatelessEntityType.none) {
|
|
||||||
return Container(width: 0.0, height: 0.0,);
|
|
||||||
}
|
|
||||||
List<Widget> result = [];
|
|
||||||
if (card.showName) {
|
|
||||||
result.add(_buildName(context));
|
|
||||||
}
|
|
||||||
result.add(
|
|
||||||
EntityIcon(
|
|
||||||
padding: EdgeInsets.all(0.0),
|
|
||||||
size: Sizes.iconSize,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (card.showState) {
|
|
||||||
result.add(_buildState());
|
|
||||||
}
|
|
||||||
|
|
||||||
return InkResponse(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: result,
|
|
||||||
),
|
|
||||||
onTap: () => entityWrapper.handleTap(),
|
|
||||||
onLongPress: () => entityWrapper.handleHold(),
|
|
||||||
onDoubleTap: () => entityWrapper.handleDoubleTap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildName(BuildContext context) {
|
|
||||||
return EntityName(
|
|
||||||
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
|
|
||||||
textOverflow: TextOverflow.ellipsis,
|
|
||||||
wordsWrap: false,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
textStyle: Theme.of(context).textTheme.body1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildState() {
|
|
||||||
return SimpleEntityState(
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
expanded: false,
|
|
||||||
maxLines: 1,
|
|
||||||
padding: EdgeInsets.only(top: Sizes.rowPadding),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class HorizontalStackCard extends StatelessWidget {
|
|
||||||
final HorizontalStackCardData card;
|
|
||||||
|
|
||||||
const HorizontalStackCard({Key key, this.card}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (card.childCards.isNotEmpty) {
|
|
||||||
List<Widget> children = [];
|
|
||||||
children = card.childCards.map((childCard) => Flexible(
|
|
||||||
fit: FlexFit.tight,
|
|
||||||
child: childCard.buildCardWidget()
|
|
||||||
)
|
|
||||||
).toList();
|
|
||||||
return IntrinsicHeight(
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: children,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Container(height: 0.0, width: 0.0,);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,161 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class LightCard extends StatefulWidget {
|
|
||||||
|
|
||||||
final LightCardData card;
|
|
||||||
|
|
||||||
LightCard({Key key, this.card}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() {
|
|
||||||
return _LightCardState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LightCardState extends State<LightCard> {
|
|
||||||
|
|
||||||
double _actualBrightness;
|
|
||||||
double _newBrightness;
|
|
||||||
bool _changedHere = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _setBrightness(double value, LightEntity entity) {
|
|
||||||
setState((){
|
|
||||||
_newBrightness = value;
|
|
||||||
_changedHere = true;
|
|
||||||
});
|
|
||||||
ConnectionManager().callService(
|
|
||||||
domain: entity.domain,
|
|
||||||
service: "turn_on",
|
|
||||||
entityId: entity.entityId,
|
|
||||||
data: {"brightness": value.round()}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
EntityWrapper entityWrapper = widget.card.entity;
|
|
||||||
LightEntity entity = entityWrapper.entity;
|
|
||||||
if (entityWrapper.entity.statelessType == StatelessEntityType.missed) {
|
|
||||||
return EntityModel(
|
|
||||||
entityWrapper: widget.card.entity,
|
|
||||||
child: MissedEntityWidget(),
|
|
||||||
handleTap: false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
entityWrapper.overrideName = widget.card.name ??
|
|
||||||
entityWrapper.displayName;
|
|
||||||
entityWrapper.overrideIcon = widget.card.icon ??
|
|
||||||
entityWrapper.icon;
|
|
||||||
|
|
||||||
if (!_changedHere) {
|
|
||||||
_actualBrightness = (entity.brightness ?? 0).toDouble();
|
|
||||||
_newBrightness = _actualBrightness;
|
|
||||||
} else {
|
|
||||||
_changedHere = false;
|
|
||||||
}
|
|
||||||
Color lightColor = entity.color?.toColor();
|
|
||||||
Color color;
|
|
||||||
if (lightColor != null && lightColor != Colors.white) {
|
|
||||||
color = lightColor;
|
|
||||||
} else {
|
|
||||||
color = Theme.of(context).accentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CardWrapper(
|
|
||||||
padding: EdgeInsets.all(4),
|
|
||||||
child: EntityModel(
|
|
||||||
entityWrapper: entityWrapper,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
ConstrainedBox(
|
|
||||||
constraints: BoxConstraints.loose(Size(200, 200)),
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 1,
|
|
||||||
child: Stack(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
SfRadialGauge(
|
|
||||||
axes: <RadialAxis>[
|
|
||||||
RadialAxis(
|
|
||||||
onAxisTapped: (val) {
|
|
||||||
_setBrightness(val, entity);
|
|
||||||
},
|
|
||||||
maximum: 255,
|
|
||||||
minimum: 0,
|
|
||||||
showLabels: false,
|
|
||||||
showTicks: false,
|
|
||||||
axisLineStyle: AxisLineStyle(
|
|
||||||
thickness: 0.05,
|
|
||||||
thicknessUnit: GaugeSizeUnit.factor,
|
|
||||||
color: HAClientTheme().getDisabledStateColor(context)
|
|
||||||
),
|
|
||||||
pointers: <GaugePointer>[
|
|
||||||
RangePointer(
|
|
||||||
value: _actualBrightness,
|
|
||||||
sizeUnit: GaugeSizeUnit.factor,
|
|
||||||
width: 0.05,
|
|
||||||
color: color,
|
|
||||||
enableAnimation: true,
|
|
||||||
animationType: AnimationType.bounceOut,
|
|
||||||
),
|
|
||||||
MarkerPointer(
|
|
||||||
value: _newBrightness,
|
|
||||||
markerType: MarkerType.circle,
|
|
||||||
markerHeight: 20,
|
|
||||||
markerWidth: 20,
|
|
||||||
enableDragging: true,
|
|
||||||
onValueChangeEnd: (val) {
|
|
||||||
_setBrightness(val, entity);
|
|
||||||
},
|
|
||||||
color: HAClientTheme().getColorByEntityState(entity.state, context)
|
|
||||||
//enableAnimation: true,
|
|
||||||
//animationType: AnimationType.bounceOut,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
FractionallySizedBox(
|
|
||||||
heightFactor: 0.4,
|
|
||||||
widthFactor: 0.4,
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 1,
|
|
||||||
child: InkResponse(
|
|
||||||
onTap: () => entityWrapper.handleTap(),
|
|
||||||
onLongPress: () => entityWrapper.handleHold(),
|
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
child: EntityIcon(
|
|
||||||
showBadge: false,
|
|
||||||
padding: EdgeInsets.all(0)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
EntityName(
|
|
||||||
padding: EdgeInsets.all(0),
|
|
||||||
wordsWrap: true,
|
|
||||||
maxLines: 3,
|
|
||||||
textOverflow: TextOverflow.ellipsis,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
handleTap: true
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class MarkdownCard extends StatelessWidget {
|
|
||||||
final MarkdownCardData card;
|
|
||||||
|
|
||||||
const MarkdownCard({Key key, this.card}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (card.content == null) {
|
|
||||||
return Container(height: 0.0, width: 0.0,);
|
|
||||||
} else if (card.content == '***') {
|
|
||||||
return Container(height: Sizes.rowPadding, width: 0.0,);
|
|
||||||
}
|
|
||||||
return CardWrapper(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: <Widget>[
|
|
||||||
CardHeader(name: card.title),
|
|
||||||
MarkdownBody(
|
|
||||||
data: card.content,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class MediaControlsCard extends StatelessWidget {
|
|
||||||
final MediaControlCardData card;
|
|
||||||
|
|
||||||
const MediaControlsCard({Key key, this.card}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (card.entity.entity.statelessType == StatelessEntityType.missed) {
|
|
||||||
return EntityModel(
|
|
||||||
entityWrapper: card.entity,
|
|
||||||
child: MissedEntityWidget(),
|
|
||||||
handleTap: false,
|
|
||||||
);
|
|
||||||
} else if (card.entity.entity.domain == null || card.entity.entity.domain != 'media_player') {
|
|
||||||
return EntityModel(
|
|
||||||
entityWrapper: card.entity,
|
|
||||||
child: ErrorEntityWidget(
|
|
||||||
text: '${card.entity.entity?.entityId} is not a media_player',
|
|
||||||
),
|
|
||||||
handleTap: false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return CardWrapper(
|
|
||||||
child: EntityModel(
|
|
||||||
entityWrapper: card.entity,
|
|
||||||
handleTap: null,
|
|
||||||
child: MediaPlayerWidget()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class UnsupportedCard extends StatelessWidget {
|
|
||||||
final CardData card;
|
|
||||||
|
|
||||||
const UnsupportedCard({Key key, this.card}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class VerticalStackCard extends StatelessWidget {
|
|
||||||
final VerticalStackCardData card;
|
|
||||||
|
|
||||||
const VerticalStackCard({Key key, this.card}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (card.childCards.isNotEmpty) {
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: card.childCards.map<Widget>(
|
|
||||||
(childCard) => childCard.buildCardWidget()
|
|
||||||
).toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Container(height: 0.0, width: 0.0,);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -4,11 +4,9 @@ class CardHeader extends StatelessWidget {
|
|||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final Widget trailing;
|
final Widget trailing;
|
||||||
final Widget leading;
|
|
||||||
final Widget subtitle;
|
final Widget subtitle;
|
||||||
final double emptyPadding;
|
|
||||||
|
|
||||||
const CardHeader({Key key, this.name, this.leading, this.emptyPadding: 0, this.trailing, this.subtitle}) : super(key: key);
|
const CardHeader({Key key, this.name, this.trailing, this.subtitle}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -16,15 +14,14 @@ class CardHeader extends StatelessWidget {
|
|||||||
if ((name != null) && (name.trim().length > 0)) {
|
if ((name != null) && (name.trim().length > 0)) {
|
||||||
result = new ListTile(
|
result = new ListTile(
|
||||||
trailing: trailing,
|
trailing: trailing,
|
||||||
leading: leading,
|
|
||||||
subtitle: subtitle,
|
subtitle: subtitle,
|
||||||
title: Text("$name",
|
title: Text("$name",
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: Theme.of(context).textTheme.headline),
|
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: Sizes.largeFontSize)),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
result = new Container(width: 0.0, height: emptyPadding);
|
result = new Container(width: 0.0, height: 0.0);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
part of '../../main.dart';
|
|
||||||
|
|
||||||
class CardWrapper extends StatelessWidget {
|
|
||||||
|
|
||||||
final Widget child;
|
|
||||||
final EdgeInsets padding;
|
|
||||||
|
|
||||||
const CardWrapper({Key key, this.child, this.padding: const EdgeInsets.all(0)}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: padding,
|
|
||||||
child: child
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
51
lib/cards/widgets/entity_button_card_body.widget.dart
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class EntityButtonCardBody extends StatelessWidget {
|
||||||
|
|
||||||
|
EntityButtonCardBody({
|
||||||
|
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: 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() {
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
153
lib/cards/widgets/gauge_card_body.dart
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class GaugeCardBody extends StatefulWidget {
|
||||||
|
|
||||||
|
final int min;
|
||||||
|
final int max;
|
||||||
|
final Map severity;
|
||||||
|
|
||||||
|
GaugeCardBody({Key key, this.min, this.max, this.severity}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_GaugeCardBodyState createState() => _GaugeCardBodyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GaugeCardBodyState extends State<GaugeCardBody> {
|
||||||
|
|
||||||
|
List<charts.Series> seriesList;
|
||||||
|
|
||||||
|
List<charts.Series<GaugeSegment, String>> _createData(double value) {
|
||||||
|
double fixedValue;
|
||||||
|
if (value > widget.max) {
|
||||||
|
fixedValue = widget.max.toDouble();
|
||||||
|
} else if (value < widget.min) {
|
||||||
|
fixedValue = widget.min.toDouble();
|
||||||
|
} else {
|
||||||
|
fixedValue = value;
|
||||||
|
}
|
||||||
|
double toShow = ((fixedValue - widget.min) / (widget.max - widget.min)) * 100;
|
||||||
|
Color mainColor;
|
||||||
|
if (widget.severity != null) {
|
||||||
|
if (widget.severity["red"] is int && fixedValue >= widget.severity["red"]) {
|
||||||
|
mainColor = Colors.red;
|
||||||
|
} else if (widget.severity["yellow"] is int && fixedValue >= widget.severity["yellow"]) {
|
||||||
|
mainColor = Colors.amber;
|
||||||
|
} else {
|
||||||
|
mainColor = Colors.green;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mainColor = Colors.green;
|
||||||
|
}
|
||||||
|
final data = [
|
||||||
|
GaugeSegment('Main', toShow, mainColor),
|
||||||
|
GaugeSegment('Rest', 100 - toShow, Colors.black45),
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
charts.Series<GaugeSegment, String>(
|
||||||
|
id: 'Segments',
|
||||||
|
domainFn: (GaugeSegment segment, _) => segment.segment,
|
||||||
|
measureFn: (GaugeSegment segment, _) => segment.value,
|
||||||
|
colorFn: (GaugeSegment segment, _) => segment.color,
|
||||||
|
// Set a label accessor to control the text of the arc label.
|
||||||
|
labelAccessorFn: (GaugeSegment segment, _) =>
|
||||||
|
segment.segment == 'Main' ? '${segment.value}' : null,
|
||||||
|
data: data,
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||||
|
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => entityWrapper.handleTap(),
|
||||||
|
onLongPress: () => entityWrapper.handleHold(),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 1.5,
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
overflow: Overflow.clip,
|
||||||
|
children: [
|
||||||
|
LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
double verticalOffset;
|
||||||
|
if(constraints.maxWidth > 150.0) {
|
||||||
|
verticalOffset = 0.2;
|
||||||
|
} else if (constraints.maxWidth > 100.0) {
|
||||||
|
verticalOffset = 0.3;
|
||||||
|
} else {
|
||||||
|
verticalOffset = 0.3;
|
||||||
|
}
|
||||||
|
return FractionallySizedBox(
|
||||||
|
heightFactor: 2,
|
||||||
|
widthFactor: 1,
|
||||||
|
alignment: FractionalOffset(0,verticalOffset),
|
||||||
|
child: charts.PieChart(
|
||||||
|
_createData(entityWrapper.entity.doubleState),
|
||||||
|
animate: false,
|
||||||
|
defaultRenderer: charts.ArcRendererConfig(
|
||||||
|
arcRatio: 0.4,
|
||||||
|
startAngle: pi,
|
||||||
|
arcLength: pi,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
double fontSize = constraints.maxHeight / 7;
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 2*fontSize),
|
||||||
|
child: SimpleEntityState(
|
||||||
|
//textAlign: TextAlign.center,
|
||||||
|
expanded: false,
|
||||||
|
maxLines: 1,
|
||||||
|
bold: true,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
fontSize: fontSize,
|
||||||
|
//padding: EdgeInsets.only(top: Sizes.rowPadding),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
double fontSize = constraints.maxHeight / 7;
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: fontSize),
|
||||||
|
child: EntityName(
|
||||||
|
fontSize: fontSize,
|
||||||
|
maxLines: 1,
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
textOverflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GaugeSegment {
|
||||||
|
final String segment;
|
||||||
|
final double value;
|
||||||
|
final charts.Color color;
|
||||||
|
|
||||||
|
GaugeSegment(this.segment, this.value, Color color)
|
||||||
|
: this.color = charts.Color(
|
||||||
|
r: color.red, g: color.green, b: color.blue, a: color.alpha);
|
||||||
|
}
|
85
lib/cards/widgets/glance_card_entity_container.dart
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class GlanceCardEntityContainer extends StatelessWidget {
|
||||||
|
|
||||||
|
final bool showName;
|
||||||
|
final bool showState;
|
||||||
|
final bool nameInTheBottom;
|
||||||
|
final double iconSize;
|
||||||
|
final double nameFontSize;
|
||||||
|
final bool wordsWrapInName;
|
||||||
|
|
||||||
|
GlanceCardEntityContainer({
|
||||||
|
Key key,
|
||||||
|
@required this.showName,
|
||||||
|
@required this.showState,
|
||||||
|
this.nameInTheBottom: false,
|
||||||
|
this.iconSize: Sizes.iconSize,
|
||||||
|
this.nameFontSize: Sizes.smallFontSize,
|
||||||
|
this.wordsWrapInName: false
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@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,);
|
||||||
|
}
|
||||||
|
List<Widget> result = [];
|
||||||
|
if (!nameInTheBottom) {
|
||||||
|
if (showName) {
|
||||||
|
result.add(_buildName());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (showState) {
|
||||||
|
result.add(_buildState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.add(
|
||||||
|
EntityIcon(
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
size: iconSize,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (!nameInTheBottom) {
|
||||||
|
if (showState) {
|
||||||
|
result.add(_buildState());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.add(_buildName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: InkResponse(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: result,
|
||||||
|
),
|
||||||
|
onTap: () => entityWrapper.handleTap(),
|
||||||
|
onLongPress: () => entityWrapper.handleHold(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildName() {
|
||||||
|
return EntityName(
|
||||||
|
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
|
||||||
|
textOverflow: TextOverflow.ellipsis,
|
||||||
|
wordsWrap: wordsWrapInName,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
fontSize: nameFontSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildState() {
|
||||||
|
return SimpleEntityState(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
expanded: false,
|
||||||
|
maxLines: 1,
|
||||||
|
padding: EdgeInsets.only(top: Sizes.rowPadding),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
90
lib/cards/widgets/light_card_body.dart
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class LightCardBody extends StatefulWidget {
|
||||||
|
|
||||||
|
final int min;
|
||||||
|
final int max;
|
||||||
|
final Map severity;
|
||||||
|
|
||||||
|
LightCardBody({Key key, this.min, this.max, this.severity}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LightCardBodyState createState() => _LightCardBodyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LightCardBodyState extends State<LightCardBody> {
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||||
|
LightEntity entity = entityWrapper.entity;
|
||||||
|
Logger.d("Light brightness: ${entity.brightness}");
|
||||||
|
|
||||||
|
return FractionallySizedBox(
|
||||||
|
widthFactor: 0.5,
|
||||||
|
child: Container(
|
||||||
|
//color: Colors.redAccent,
|
||||||
|
child: SingleCircularSlider(
|
||||||
|
255,
|
||||||
|
entity.brightness ?? 0,
|
||||||
|
baseColor: Colors.white,
|
||||||
|
handlerColor: Colors.blue[200],
|
||||||
|
selectionColor: Colors.blue[100],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => entityWrapper.handleTap(),
|
||||||
|
onLongPress: () => entityWrapper.handleHold(),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 1.5,
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
overflow: Overflow.clip,
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
double fontSize = constraints.maxHeight / 7;
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 2*fontSize),
|
||||||
|
child: SimpleEntityState(
|
||||||
|
//textAlign: TextAlign.center,
|
||||||
|
expanded: false,
|
||||||
|
maxLines: 1,
|
||||||
|
bold: true,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
fontSize: fontSize,
|
||||||
|
//padding: EdgeInsets.only(top: Sizes.rowPadding),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
double fontSize = constraints.maxHeight / 7;
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: fontSize),
|
||||||
|
child: EntityName(
|
||||||
|
fontSize: fontSize,
|
||||||
|
maxLines: 1,
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
textOverflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -36,6 +36,51 @@ class EntityState {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EntityUIAction {
|
||||||
|
static const moreInfo = 'more-info';
|
||||||
|
static const toggle = 'toggle';
|
||||||
|
static const callService = 'call-service';
|
||||||
|
static const navigate = 'navigate';
|
||||||
|
static const none = 'none';
|
||||||
|
|
||||||
|
String tapAction = EntityUIAction.moreInfo;
|
||||||
|
String tapNavigationPath;
|
||||||
|
String tapService;
|
||||||
|
Map<String, dynamic> tapServiceData;
|
||||||
|
String holdAction = EntityUIAction.none;
|
||||||
|
String holdNavigationPath;
|
||||||
|
String holdService;
|
||||||
|
Map<String, dynamic> holdServiceData;
|
||||||
|
|
||||||
|
EntityUIAction({rawEntityData}) {
|
||||||
|
if (rawEntityData != null) {
|
||||||
|
if (rawEntityData["tap_action"] != null) {
|
||||||
|
if (rawEntityData["tap_action"] is String) {
|
||||||
|
tapAction = rawEntityData["tap_action"];
|
||||||
|
} else {
|
||||||
|
tapAction =
|
||||||
|
rawEntityData["tap_action"]["action"] ?? EntityUIAction.moreInfo;
|
||||||
|
tapNavigationPath = rawEntityData["tap_action"]["navigation_path"];
|
||||||
|
tapService = rawEntityData["tap_action"]["service"];
|
||||||
|
tapServiceData = rawEntityData["tap_action"]["service_data"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rawEntityData["hold_action"] != null) {
|
||||||
|
if (rawEntityData["hold_action"] is String) {
|
||||||
|
holdAction = rawEntityData["hold_action"];
|
||||||
|
} else {
|
||||||
|
holdAction =
|
||||||
|
rawEntityData["hold_action"]["action"] ?? EntityUIAction.none;
|
||||||
|
holdNavigationPath = rawEntityData["hold_action"]["navigation_path"];
|
||||||
|
holdService = rawEntityData["hold_action"]["service"];
|
||||||
|
holdServiceData = rawEntityData["hold_action"]["service_data"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class CardType {
|
class CardType {
|
||||||
static const HORIZONTAL_STACK = "horizontal-stack";
|
static const HORIZONTAL_STACK = "horizontal-stack";
|
||||||
static const VERTICAL_STACK = "vertical-stack";
|
static const VERTICAL_STACK = "vertical-stack";
|
||||||
@ -53,17 +98,10 @@ class CardType {
|
|||||||
static const IFRAME = "iframe";
|
static const IFRAME = "iframe";
|
||||||
static const GAUGE = "gauge";
|
static const GAUGE = "gauge";
|
||||||
static const ENTITY_BUTTON = "entity-button";
|
static const ENTITY_BUTTON = "entity-button";
|
||||||
static const ENTITY = "entity";
|
|
||||||
static const BUTTON = "button";
|
|
||||||
static const CONDITIONAL = "conditional";
|
static const CONDITIONAL = "conditional";
|
||||||
static const ALARM_PANEL = "alarm-panel";
|
static const ALARM_PANEL = "alarm-panel";
|
||||||
static const MARKDOWN = "markdown";
|
static const MARKDOWN = "markdown";
|
||||||
static const LIGHT = "light";
|
static const LIGHT = "light";
|
||||||
static const ENTITY_FILTER = "entity-filter";
|
|
||||||
static const UNKNOWN = "unknown";
|
|
||||||
static const HISTORY_GRAPH = "history-graph";
|
|
||||||
static const PICTURE_GLANCE = "picture-glance";
|
|
||||||
static const BADGES = "badges";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Sizes {
|
class Sizes {
|
||||||
@ -73,10 +111,10 @@ class Sizes {
|
|||||||
static const extendedWidgetHeight = 50.0;
|
static const extendedWidgetHeight = 50.0;
|
||||||
static const iconSize = 28.0;
|
static const iconSize = 28.0;
|
||||||
static const largeIconSize = 46.0;
|
static const largeIconSize = 46.0;
|
||||||
//static const stateFontSize = 15.0;
|
static const stateFontSize = 15.0;
|
||||||
//static const nameFontSize = 15.0;
|
static const nameFontSize = 15.0;
|
||||||
//static const smallFontSize = 14.0;
|
static const smallFontSize = 14.0;
|
||||||
//static const largeFontSize = 24.0;
|
static const largeFontSize = 24.0;
|
||||||
static const inputWidth = 160.0;
|
static const inputWidth = 160.0;
|
||||||
static const rowPadding = 10.0;
|
static const rowPadding = 10.0;
|
||||||
static const doubleRowPadding = rowPadding*2;
|
static const doubleRowPadding = rowPadding*2;
|
||||||
|
@ -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),
|
||||||
)
|
)
|
||||||
|
145
lib/entities/badge.widget.dart
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
class BadgeWidget extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entityModel = EntityModel.of(context);
|
||||||
|
double iconSize = 26.0;
|
||||||
|
Widget badgeIcon;
|
||||||
|
String onBadgeTextValue;
|
||||||
|
Color iconColor = EntityColor.badgeColors[entityModel.entityWrapper.entity.domain] ??
|
||||||
|
EntityColor.badgeColors["default"];
|
||||||
|
switch (entityModel.entityWrapper.entity.domain) {
|
||||||
|
case "sun":
|
||||||
|
{
|
||||||
|
badgeIcon = entityModel.entityWrapper.entity.state == "below_horizon"
|
||||||
|
? Icon(
|
||||||
|
MaterialDesignIcons.getIconDataFromIconCode(0xf0dc),
|
||||||
|
size: iconSize,
|
||||||
|
)
|
||||||
|
: Icon(
|
||||||
|
MaterialDesignIcons.getIconDataFromIconCode(0xf5a8),
|
||||||
|
size: iconSize,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "camera":
|
||||||
|
case "media_player":
|
||||||
|
case "binary_sensor":
|
||||||
|
{
|
||||||
|
badgeIcon = EntityIcon(
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
size: iconSize,
|
||||||
|
color: Colors.black
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "device_tracker":
|
||||||
|
case "person":
|
||||||
|
{
|
||||||
|
badgeIcon = EntityIcon(
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
size: iconSize,
|
||||||
|
color: Colors.black
|
||||||
|
);
|
||||||
|
onBadgeTextValue = entityModel.entityWrapper.entity.displayState;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
double stateFontSize;
|
||||||
|
if (entityModel.entityWrapper.entity.displayState.length <= 3) {
|
||||||
|
stateFontSize = 18.0;
|
||||||
|
} else if (entityModel.entityWrapper.entity.displayState.length <= 4) {
|
||||||
|
stateFontSize = 15.0;
|
||||||
|
} else if (entityModel.entityWrapper.entity.displayState.length <= 6) {
|
||||||
|
stateFontSize = 10.0;
|
||||||
|
} else if (entityModel.entityWrapper.entity.displayState.length <= 10) {
|
||||||
|
stateFontSize = 8.0;
|
||||||
|
}
|
||||||
|
onBadgeTextValue = entityModel.entityWrapper.unitOfMeasurement;
|
||||||
|
badgeIcon = Center(
|
||||||
|
child: Text(
|
||||||
|
"${entityModel.entityWrapper.entity.displayState}",
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
softWrap: false,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: stateFontSize),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Widget onBadgeText;
|
||||||
|
if (onBadgeTextValue == null || onBadgeTextValue.length == 0) {
|
||||||
|
onBadgeText = Container(width: 0.0, height: 0.0);
|
||||||
|
} else {
|
||||||
|
onBadgeText = Container(
|
||||||
|
padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0),
|
||||||
|
child: Text("$onBadgeTextValue",
|
||||||
|
style: TextStyle(fontSize: 12.0, color: Colors.white),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
softWrap: false,
|
||||||
|
overflow: TextOverflow.fade),
|
||||||
|
decoration: new BoxDecoration(
|
||||||
|
// Circle shape
|
||||||
|
//shape: BoxShape.circle,
|
||||||
|
color: iconColor,
|
||||||
|
borderRadius: BorderRadius.circular(9.0),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return GestureDetector(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0),
|
||||||
|
width: 50.0,
|
||||||
|
height: 50.0,
|
||||||
|
decoration: new BoxDecoration(
|
||||||
|
// Circle shape
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Colors.white,
|
||||||
|
// The border you want
|
||||||
|
border: new Border.all(
|
||||||
|
width: 2.0,
|
||||||
|
color: iconColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
overflow: Overflow.visible,
|
||||||
|
children: <Widget>[
|
||||||
|
Positioned(
|
||||||
|
width: 46.0,
|
||||||
|
height: 46.0,
|
||||||
|
top: 0.0,
|
||||||
|
left: 0.0,
|
||||||
|
child: badgeIcon,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
//width: 50.0,
|
||||||
|
bottom: -9.0,
|
||||||
|
left: -10.0,
|
||||||
|
right: -10.0,
|
||||||
|
child: Center(
|
||||||
|
child: onBadgeText,
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 60.0,
|
||||||
|
child: Text(
|
||||||
|
"${entityModel.entityWrapper.displayName}",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 12.0),
|
||||||
|
softWrap: true,
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () =>
|
||||||
|
eventBus.fire(new ShowEntityPageEvent(entity: entityModel.entityWrapper.entity)));
|
||||||
|
}
|
||||||
|
}
|
@ -3,16 +3,12 @@ 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, String webHost) : super(rawData, webHost);
|
||||||
|
|
||||||
bool get supportOnOff => ((supportedFeatures &
|
bool get supportOnOff => ((supportedFeatures &
|
||||||
CameraEntity.SUPPORT_ON_OFF) ==
|
CameraEntity.SUPPORT_ON_OFF) ==
|
||||||
CameraEntity.SUPPORT_ON_OFF);
|
CameraEntity.SUPPORT_ON_OFF);
|
||||||
bool get supportStream => ((supportedFeatures &
|
|
||||||
CameraEntity.SUPPORT_STREAM) ==
|
|
||||||
CameraEntity.SUPPORT_STREAM);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
Widget _buildAdditionalControlsForPage(BuildContext context) {
|
||||||
|
@ -2,9 +2,7 @@ part of '../../../main.dart';
|
|||||||
|
|
||||||
class CameraStreamView extends StatefulWidget {
|
class CameraStreamView extends StatefulWidget {
|
||||||
|
|
||||||
final bool withControls;
|
CameraStreamView({Key key}) : super(key: key);
|
||||||
|
|
||||||
CameraStreamView({Key key, this.withControls: true}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_CameraStreamViewState createState() => _CameraStreamViewState();
|
_CameraStreamViewState createState() => _CameraStreamViewState();
|
||||||
@ -12,175 +10,45 @@ class CameraStreamView extends StatefulWidget {
|
|||||||
|
|
||||||
class _CameraStreamViewState extends State<CameraStreamView> {
|
class _CameraStreamViewState extends State<CameraStreamView> {
|
||||||
|
|
||||||
CameraEntity _entity;
|
|
||||||
String _streamUrl = "";
|
|
||||||
bool _isLoaded = false;
|
|
||||||
double _aspectRatio = 1.33;
|
|
||||||
String _webViewHtml;
|
|
||||||
String _jsMessageChannelName = 'unknown';
|
|
||||||
Completer _loading;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _loadResources() {
|
CameraEntity _entity;
|
||||||
if (_loading != null && !_loading.isCompleted) {
|
bool started = false;
|
||||||
Logger.d("[Camera Player] Resources loading is not finished yet");
|
String streamUrl = "";
|
||||||
return _loading.future;
|
|
||||||
}
|
|
||||||
Logger.d("[Camera Player] Loading resources");
|
|
||||||
_loading = Completer();
|
|
||||||
_entity = EntityModel
|
|
||||||
.of(context)
|
|
||||||
.entityWrapper
|
|
||||||
.entity;
|
|
||||||
if (_entity.supportStream && HomeAssistant().isComponentEnabled('stream')) {
|
|
||||||
HomeAssistant().getCameraStream(_entity.entityId)
|
|
||||||
.then((data) {
|
|
||||||
_jsMessageChannelName = 'HA_${_entity.entityId.replaceAll('.', '_')}';
|
|
||||||
rootBundle.loadString('assets/html/cameraLiveView.html').then((file) {
|
|
||||||
_webViewHtml = Uri.dataFromString(
|
|
||||||
file.replaceFirst('{{stream_url}}', '${AppSettings().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 {
|
launchStream() {
|
||||||
_streamUrl = '${AppSettings().httpWebHost}/api/camera_proxy_stream/${_entity
|
Launcher.launchURLInCustomTab(
|
||||||
.entityId}?token=${_entity.attributes['access_token']}';
|
context: context,
|
||||||
_jsMessageChannelName = 'HA_${_entity.entityId.replaceAll('.', '_')}';
|
url: streamUrl
|
||||||
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 ? () {
|
|
||||||
Navigator.of(context).pushReplacement(
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (conext) => FullScreenPage(
|
|
||||||
child: EntityModel(
|
|
||||||
child: CameraStreamView(
|
|
||||||
withControls: false
|
|
||||||
),
|
|
||||||
handleTap: false,
|
|
||||||
entityWrapper: EntityWrapper(
|
|
||||||
entity: _entity
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
fullscreenDialog: true
|
|
||||||
)
|
|
||||||
).then((_){
|
|
||||||
eventBus.fire(ShowEntityPageEvent(entityId: _entity.entityId));
|
|
||||||
});
|
|
||||||
} : null,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (!_isLoaded && (_loading == null || _loading.isCompleted)) {
|
if (!started) {
|
||||||
_loadResources().then((_) => setState((){ _isLoaded = true; }));
|
_entity = EntityModel
|
||||||
|
.of(context)
|
||||||
|
.entityWrapper
|
||||||
|
.entity;
|
||||||
|
started = true;
|
||||||
}
|
}
|
||||||
if (widget.withControls) {
|
streamUrl = '${ConnectionManager().httpWebHost}/api/camera_proxy_stream/${_entity
|
||||||
return Card(
|
.entityId}?token=${_entity.attributes['access_token']}';
|
||||||
child: Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: <Widget>[
|
||||||
children: <Widget>[
|
Container(
|
||||||
_buildScreen(),
|
padding: const EdgeInsets.all(20.0),
|
||||||
_buildControls()
|
child: IconButton(
|
||||||
],
|
icon: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:monitor-screenshot"), color: Colors.amber),
|
||||||
),
|
iconSize: 50.0,
|
||||||
);
|
onPressed: () => launchStream(),
|
||||||
} else {
|
)
|
||||||
return _buildScreen();
|
)
|
||||||
}
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -10,8 +10,9 @@ 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;
|
||||||
@ -26,11 +27,9 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
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;
|
|
||||||
}
|
|
||||||
_tmpHVACMode = entity.state;
|
_tmpHVACMode = entity.state;
|
||||||
_tmpFanMode = entity.fanMode;
|
_tmpFanMode = entity.fanMode;
|
||||||
_tmpSwingMode = entity.swingMode;
|
_tmpSwingMode = entity.swingMode;
|
||||||
@ -39,6 +38,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
_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,12 +111,8 @@ 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"}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,12 +120,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpHVACMode = value;
|
_tmpHVACMode = value;
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
ConnectionManager().callService(
|
eventBus.fire(new ServiceCallEvent(entity.domain, "set_hvac_mode", entity.entityId,{"hvac_mode": "$_tmpHVACMode"}));
|
||||||
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,7 +138,8 @@ 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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +147,8 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpPresetMode = value;
|
_tmpPresetMode = 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_preset_mode", entity.entityId,{"preset_mode": "$_tmpPresetMode"}));
|
||||||
|
_resetStateTimer(entity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +165,18 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,9 +185,10 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
final entityModel = EntityModel.of(context);
|
final entityModel = EntityModel.of(context);
|
||||||
final ClimateEntity entity = entityModel.entityWrapper.entity;
|
final ClimateEntity entity = entityModel.entityWrapper.entity;
|
||||||
if (_changedHere) {
|
if (_changedHere) {
|
||||||
//_showPending = (_tmpTemperature != entity.temperature || _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,20 +197,20 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
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),
|
_buildPresetModeControl(entity),
|
||||||
_buildAuxHeatControl(entity, context)
|
_buildAuxHeatControl(entity)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPresetModeControl(ClimateEntity entity, BuildContext context) {
|
Widget _buildPresetModeControl(ClimateEntity entity) {
|
||||||
if (entity.supportPresetMode) {
|
if (entity.supportPresetMode) {
|
||||||
return ModeSelectorWidget(
|
return ModeSelectorWidget(
|
||||||
options: entity.presetModes,
|
options: entity.presetModes,
|
||||||
@ -241,7 +235,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
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",
|
||||||
@ -253,7 +247,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildOperationControl(ClimateEntity entity, BuildContext context) {
|
Widget _buildOperationControl(ClimateEntity entity) {
|
||||||
if (entity.hvacModes != null) {
|
if (entity.hvacModes != null) {
|
||||||
return ModeSelectorWidget(
|
return ModeSelectorWidget(
|
||||||
onChange: (mode) => _setHVACMode(entity, mode),
|
onChange: (mode) => _setHVACMode(entity, mode),
|
||||||
@ -266,7 +260,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.fanModes,
|
||||||
@ -279,7 +273,7 @@ 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),
|
||||||
@ -292,15 +286,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),
|
||||||
)
|
)
|
||||||
@ -311,13 +307,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.supportTargetTemperatureRange) && (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),
|
||||||
),
|
),
|
||||||
@ -330,7 +326,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
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),
|
||||||
)
|
)
|
||||||
@ -340,7 +336,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,
|
||||||
)
|
)
|
||||||
@ -351,20 +349,16 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildHumidityControls(ClimateEntity entity, BuildContext context) {
|
Widget _buildHumidityControls(ClimateEntity entity) {
|
||||||
|
List<Widget> result = [];
|
||||||
if (entity.supportTargetHumidity) {
|
if (entity.supportTargetHumidity) {
|
||||||
return Column(
|
result.addAll(<Widget>[
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Text(
|
||||||
children: <Widget>[
|
"$_tmpTargetHumidity%",
|
||||||
Padding(
|
style: TextStyle(fontSize: Sizes.largeFontSize),
|
||||||
padding: EdgeInsets.only(top: Sizes.rowPadding),
|
),
|
||||||
child: Text("Target humidity", style: Theme.of(context).textTheme.body1),
|
Expanded(
|
||||||
),
|
child: Slider(
|
||||||
UniversalSlider(
|
|
||||||
leading: Text(
|
|
||||||
"$_tmpTargetHumidity%",
|
|
||||||
style: Theme.of(context).textTheme.display1,
|
|
||||||
),
|
|
||||||
value: _tmpTargetHumidity,
|
value: _tmpTargetHumidity,
|
||||||
max: entity.maxHumidity,
|
max: entity.maxHumidity,
|
||||||
min: entity.minHumidity,
|
min: entity.minHumidity,
|
||||||
@ -376,6 +370,24 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}),
|
}),
|
||||||
onChangeEnd: (double v) => _setTargetHumidity(entity, v),
|
onChangeEnd: (double v) => _setTargetHumidity(entity, v),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (result.isNotEmpty) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(
|
||||||
|
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||||
|
child: Text("Target humidity", style: TextStyle(
|
||||||
|
fontSize: Sizes.stateFontSize
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: result,
|
||||||
|
),
|
||||||
Container(
|
Container(
|
||||||
height: Sizes.rowPadding,
|
height: Sizes.rowPadding,
|
||||||
)
|
)
|
||||||
@ -392,6 +404,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_resetTimer?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,16 +33,23 @@ class ClimateStateWidget extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text("$displayState",
|
Text("$displayState",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: Theme.of(context).textTheme.body2),
|
style: new TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: Sizes.stateFontSize,
|
||||||
|
)),
|
||||||
Text(" $targetTemp",
|
Text(" $targetTemp",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: Theme.of(context).textTheme.body1)
|
style: new TextStyle(
|
||||||
|
fontSize: Sizes.stateFontSize,
|
||||||
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
entity.currentTemperature != null ?
|
entity.currentTemperature != null ?
|
||||||
Text("Currently: ${entity.currentTemperature}",
|
Text("Currently: ${entity.currentTemperature}",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: Theme.of(context).textTheme.subtitle
|
style: new TextStyle(
|
||||||
|
fontSize: Sizes.stateFontSize,
|
||||||
|
color: Colors.black45)
|
||||||
) :
|
) :
|
||||||
Container(height: 0.0,)
|
Container(height: 0.0,)
|
||||||
],
|
],
|
||||||
|
@ -3,17 +3,21 @@ 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;
|
||||||
|
|
||||||
ModeSelectorWidget({
|
ModeSelectorWidget({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.caption,
|
@required this.caption,
|
||||||
this.options: const [],
|
@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),
|
||||||
|
@ -4,6 +4,7 @@ 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(
|
||||||
|
@ -2,7 +2,8 @@ part of '../../../main.dart';
|
|||||||
|
|
||||||
class TemperatureControlWidget extends StatelessWidget {
|
class TemperatureControlWidget extends StatelessWidget {
|
||||||
final double value;
|
final double value;
|
||||||
final bool active;
|
final double fontSize;
|
||||||
|
final Color fontColor;
|
||||||
final onInc;
|
final onInc;
|
||||||
final onDec;
|
final onDec;
|
||||||
|
|
||||||
@ -11,9 +12,8 @@ class TemperatureControlWidget extends StatelessWidget {
|
|||||||
@required this.value,
|
@required this.value,
|
||||||
@required this.onInc,
|
@required this.onInc,
|
||||||
@required this.onDec,
|
@required this.onDec,
|
||||||
//this.fontSize,
|
this.fontSize,
|
||||||
this.active: false
|
this.fontColor})
|
||||||
})
|
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -23,7 +23,10 @@ class TemperatureControlWidget extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
"$value",
|
"$value",
|
||||||
style: active ? Theme.of(context).textTheme.display2 : Theme.of(context).textTheme.display1,
|
style: TextStyle(
|
||||||
|
fontSize: fontSize ?? 24.0,
|
||||||
|
color: fontColor ?? Colors.black
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
@ -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()}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,10 +62,13 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: Sizes.rowPadding),
|
padding: EdgeInsets.fromLTRB(
|
||||||
child: Text("Position"),
|
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||||
|
child: Text("Position", style: TextStyle(
|
||||||
|
fontSize: Sizes.stateFontSize
|
||||||
|
)),
|
||||||
),
|
),
|
||||||
UniversalSlider(
|
Slider(
|
||||||
value: _tmpPosition,
|
value: _tmpPosition,
|
||||||
min: 0.0,
|
min: 0.0,
|
||||||
max: 100.0,
|
max: 100.0,
|
||||||
@ -77,7 +80,8 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onChangeEnd: (double value) => _setNewPosition(entity, value),
|
onChangeEnd: (double value) => _setNewPosition(entity, value),
|
||||||
)
|
),
|
||||||
|
Container(height: Sizes.rowPadding,)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -94,7 +98,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
|||||||
}
|
}
|
||||||
if (entity.supportSetTiltPosition) {
|
if (entity.supportSetTiltPosition) {
|
||||||
controls.addAll(<Widget>[
|
controls.addAll(<Widget>[
|
||||||
UniversalSlider(
|
Slider(
|
||||||
value: _tmpTiltPosition,
|
value: _tmpTiltPosition,
|
||||||
min: 0.0,
|
min: 0.0,
|
||||||
max: 100.0,
|
max: 100.0,
|
||||||
@ -113,8 +117,10 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
|||||||
if (controls.isNotEmpty) {
|
if (controls.isNotEmpty) {
|
||||||
controls.insert(0, Padding(
|
controls.insert(0, Padding(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
0.0, Sizes.rowPadding, 0.0, 0),
|
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,
|
||||||
@ -129,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
|
||||||
|
@ -2,27 +2,18 @@ part of '../../../main.dart';
|
|||||||
|
|
||||||
class CoverStateWidget extends StatelessWidget {
|
class CoverStateWidget extends StatelessWidget {
|
||||||
void _open(CoverEntity entity) {
|
void _open(CoverEntity entity) {
|
||||||
ConnectionManager().callService(
|
eventBus.fire(new ServiceCallEvent(
|
||||||
domain: entity.domain,
|
entity.domain, "open_cover", entity.entityId, null));
|
||||||
service: "open_cover",
|
|
||||||
entityId: entity.entityId
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _close(CoverEntity entity) {
|
void _close(CoverEntity entity) {
|
||||||
ConnectionManager().callService(
|
eventBus.fire(new ServiceCallEvent(
|
||||||
domain: entity.domain,
|
entity.domain, "close_cover", entity.entityId, null));
|
||||||
service: "close_cover",
|
|
||||||
entityId: entity.entityId
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _stop(CoverEntity entity) {
|
void _stop(CoverEntity entity) {
|
||||||
ConnectionManager().callService(
|
eventBus.fire(new ServiceCallEvent(
|
||||||
domain: entity.domain,
|
entity.domain, "stop_cover", entity.entityId, null));
|
||||||
service: "stop_cover",
|
|
||||||
entityId: entity.entityId
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,8 +9,10 @@ class DateTimeStateWidget extends StatelessWidget {
|
|||||||
padding: EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0),
|
padding: EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
child: Text("${entity.formattedState}",
|
child: Text("${entity.formattedState}",
|
||||||
textAlign: TextAlign.right
|
textAlign: TextAlign.right,
|
||||||
),
|
style: new TextStyle(
|
||||||
|
fontSize: Sizes.stateFontSize,
|
||||||
|
)),
|
||||||
onTap: () => _handleStateTap(context, entity),
|
onTap: () => _handleStateTap(context, entity),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -11,23 +11,25 @@ class DefaultEntityContainer extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final EntityModel entityModel = EntityModel.of(context);
|
final EntityModel entityModel = EntityModel.of(context);
|
||||||
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.missed) {
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.MISSED) {
|
||||||
return MissedEntityWidget();
|
return MissedEntityWidget();
|
||||||
}
|
}
|
||||||
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.divider) {
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.DIVIDER) {
|
||||||
return Divider();
|
return Divider(
|
||||||
|
color: Colors.black45,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.section) {
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.SECTION) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Divider(),
|
Divider(
|
||||||
|
color: Colors.black45,
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
"${entityModel.entityWrapper.entity.displayName}",
|
"${entityModel.entityWrapper.entity.displayName}",
|
||||||
style: HAClientTheme().getLinkTextStyle(context).copyWith(
|
style: TextStyle(color: Colors.blue),
|
||||||
decoration: TextDecoration.none
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -36,6 +38,7 @@ class DefaultEntityContainer extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
EntityIcon(),
|
EntityIcon(),
|
||||||
|
|
||||||
Flexible(
|
Flexible(
|
||||||
fit: FlexFit.tight,
|
fit: FlexFit.tight,
|
||||||
flex: 3,
|
flex: 3,
|
||||||
@ -58,11 +61,6 @@ class DefaultEntityContainer extends StatelessWidget {
|
|||||||
entityModel.entityWrapper.handleTap();
|
entityModel.entityWrapper.handleTap();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDoubleTap: () {
|
|
||||||
if (entityModel.handleTap) {
|
|
||||||
entityModel.entityWrapper.handleDoubleTap();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: result,
|
child: result,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
part of '../main.dart';
|
part of '../main.dart';
|
||||||
|
|
||||||
enum StatelessEntityType {none, missed, ghost, divider, section, callService, webLink}
|
class StatelessEntityType {
|
||||||
|
static const NONE = 0;
|
||||||
|
static const MISSED = 1;
|
||||||
|
static const DIVIDER = 2;
|
||||||
|
static const SECTION = 3;
|
||||||
|
static const CALL_SERVICE = 4;
|
||||||
|
static const WEBLINK = 5;
|
||||||
|
}
|
||||||
|
|
||||||
class Entity {
|
class Entity {
|
||||||
|
|
||||||
@ -69,8 +76,8 @@ class Entity {
|
|||||||
String entityPicture;
|
String entityPicture;
|
||||||
String state;
|
String state;
|
||||||
String displayState;
|
String displayState;
|
||||||
DateTime lastUpdatedTimestamp;
|
DateTime _lastUpdated;
|
||||||
StatelessEntityType statelessType = StatelessEntityType.none;
|
int statelessType = 0;
|
||||||
|
|
||||||
List<Entity> childEntities = [];
|
List<Entity> childEntities = [];
|
||||||
String deviceClass;
|
String deviceClass;
|
||||||
@ -78,21 +85,8 @@ class Entity {
|
|||||||
chartType: EntityHistoryWidgetType.simple
|
chartType: EntityHistoryWidgetType.simple
|
||||||
);
|
);
|
||||||
|
|
||||||
String get displayName {
|
String get displayName =>
|
||||||
if (attributes.containsKey('friendly_name')) {
|
attributes["friendly_name"] ?? (attributes["name"] ?? entityId.split(".")[1].replaceAll("_", " "));
|
||||||
return attributes['friendly_name'];
|
|
||||||
}
|
|
||||||
if (attributes.containsKey('name')) {
|
|
||||||
return attributes['name'];
|
|
||||||
}
|
|
||||||
if (entityId == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
if (entityId.contains(".")) {
|
|
||||||
return entityId.split(".")[1].replaceAll("_", " ");
|
|
||||||
}
|
|
||||||
return entityId;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get isView =>
|
bool get isView =>
|
||||||
(domain == "group") &&
|
(domain == "group") &&
|
||||||
@ -126,47 +120,42 @@ class Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Entity.missed(String entityId) {
|
Entity.missed(String entityId) {
|
||||||
statelessType = StatelessEntityType.missed;
|
statelessType = StatelessEntityType.MISSED;
|
||||||
attributes = {"hidden": false};
|
attributes = {"hidden": false};
|
||||||
this.entityId = entityId;
|
this.entityId = entityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity.divider() {
|
Entity.divider() {
|
||||||
statelessType = StatelessEntityType.divider;
|
statelessType = StatelessEntityType.DIVIDER;
|
||||||
attributes = {"hidden": false};
|
attributes = {"hidden": false};
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity.section(String label) {
|
Entity.section(String label) {
|
||||||
statelessType = StatelessEntityType.section;
|
statelessType = StatelessEntityType.SECTION;
|
||||||
attributes = {"hidden": false, "friendly_name": "$label"};
|
attributes = {"hidden": false, "friendly_name": "$label"};
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity.ghost(String name, String icon) {
|
|
||||||
statelessType = StatelessEntityType.ghost;
|
|
||||||
attributes = {"icon": icon, "hidden": false, "friendly_name": name};
|
|
||||||
}
|
|
||||||
|
|
||||||
Entity.callService({String icon, String name, String service, String actionName}) {
|
Entity.callService({String icon, String name, String service, String actionName}) {
|
||||||
statelessType = StatelessEntityType.callService;
|
statelessType = StatelessEntityType.CALL_SERVICE;
|
||||||
entityId = service;
|
entityId = service;
|
||||||
displayState = actionName?.toUpperCase() ?? "RUN";
|
displayState = actionName?.toUpperCase() ?? "RUN";
|
||||||
attributes = {"hidden": false, "friendly_name": "$name", "icon": "$icon"};
|
attributes = {"hidden": false, "friendly_name": "$name", "icon": "$icon"};
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity.weblink({String url, String name, String icon}) {
|
Entity.weblink({String url, String name, String icon}) {
|
||||||
statelessType = StatelessEntityType.webLink;
|
statelessType = StatelessEntityType.WEBLINK;
|
||||||
entityId = "custom.custom";
|
entityId = "custom.custom"; //TODO wtf??
|
||||||
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, String webHost) {
|
||||||
attributes = rawData["attributes"] ?? {};
|
attributes = rawData["attributes"] ?? {};
|
||||||
domain = rawData["entity_id"] != null ? rawData["entity_id"].split(".")[0] : null;
|
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.toLowerCase() == 'unknown' ? '-' : state);
|
||||||
lastUpdatedTimestamp = DateTime.tryParse(rawData["last_updated"]);
|
_lastUpdated = DateTime.tryParse(rawData["last_updated"]);
|
||||||
entityPicture = _getEntityPictureUrl(webHost);
|
entityPicture = _getEntityPictureUrl(webHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,19 +211,27 @@ class Entity {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget buildBadgeWidget(BuildContext context) {
|
||||||
|
return EntityModel(
|
||||||
|
entityWrapper: EntityWrapper(entity: this),
|
||||||
|
child: BadgeWidget(),
|
||||||
|
handleTap: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getLastUpdatedFormatted() {
|
String _getLastUpdatedFormatted() {
|
||||||
if (lastUpdatedTimestamp == null) {
|
if (_lastUpdated == null) {
|
||||||
return "-";
|
return "-";
|
||||||
} else {
|
} else {
|
||||||
DateTime now = DateTime.now();
|
DateTime now = DateTime.now();
|
||||||
Duration d = now.difference(lastUpdatedTimestamp);
|
Duration d = now.difference(_lastUpdated);
|
||||||
String text;
|
String text;
|
||||||
int v;
|
int v;
|
||||||
if (d.inDays == 0) {
|
if (d.inDays == 0) {
|
||||||
|
77
lib/entities/entity_colors.class.dart
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
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,
|
||||||
|
EntityState.paused: Colors.amber,
|
||||||
|
"above_horizon": Colors.amber,
|
||||||
|
EntityState.home: Colors.amber,
|
||||||
|
EntityState.open: Colors.amber,
|
||||||
|
EntityState.cleaning: Colors.amber,
|
||||||
|
EntityState.returning: 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,18 +3,12 @@ part of '../main.dart';
|
|||||||
class EntityIcon extends StatelessWidget {
|
class EntityIcon extends StatelessWidget {
|
||||||
|
|
||||||
final EdgeInsetsGeometry padding;
|
final EdgeInsetsGeometry padding;
|
||||||
final EdgeInsetsGeometry iconPadding;
|
|
||||||
final EdgeInsetsGeometry imagePadding;
|
|
||||||
final double size;
|
final double size;
|
||||||
final Color color;
|
final Color color;
|
||||||
final bool showBadge;
|
|
||||||
|
|
||||||
const EntityIcon({Key key, this.color, this.showBadge: true, this.size: Sizes.iconSize, this.padding: const EdgeInsets.all(0.0), this.iconPadding, this.imagePadding}) : super(key: key);
|
const EntityIcon({Key key, this.color, this.size: Sizes.iconSize, this.padding: const EdgeInsets.all(0.0)}) : super(key: key);
|
||||||
|
|
||||||
int getDefaultIconByEntityId(String entityId, String deviceClass, String state) {
|
int getDefaultIconByEntityId(String entityId, String deviceClass, String state) {
|
||||||
if (entityId == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
String domain = entityId.split(".")[0];
|
String domain = entityId.split(".")[0];
|
||||||
String iconNameByDomain = MaterialDesignIcons.defaultIconsByDomains["$domain.$state"] ?? MaterialDesignIcons.defaultIconsByDomains["$domain"];
|
String iconNameByDomain = MaterialDesignIcons.defaultIconsByDomains["$domain.$state"] ?? MaterialDesignIcons.defaultIconsByDomains["$domain"];
|
||||||
String iconNameByDeviceClass;
|
String iconNameByDeviceClass;
|
||||||
@ -29,110 +23,52 @@ class EntityIcon extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Widget buildIcon(EntityWrapper data, Color color) {
|
||||||
Widget build(BuildContext context) {
|
if (data == null) {
|
||||||
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
return null;
|
||||||
Color iconColor;
|
|
||||||
if (color != null) {
|
|
||||||
iconColor = color;
|
|
||||||
} else if (entityWrapper.stateColor) {
|
|
||||||
iconColor = HAClientTheme().getColorByEntityState(entityWrapper.entity.state, context);
|
|
||||||
} else {
|
|
||||||
iconColor = HAClientTheme().getOffStateColor(context);
|
|
||||||
}
|
}
|
||||||
Widget iconWidget;
|
if (data.entityPicture != null) {
|
||||||
bool isPicture = false;
|
return Container(
|
||||||
if (entityWrapper == null) {
|
height: size+12,
|
||||||
iconWidget = Container(
|
width: size+12,
|
||||||
width: size,
|
decoration: BoxDecoration(
|
||||||
height: size,
|
shape: BoxShape.circle,
|
||||||
|
image: DecorationImage(
|
||||||
|
fit:BoxFit.cover,
|
||||||
|
image: CachedNetworkImageProvider(
|
||||||
|
"${data.entityPicture}"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
if (entityWrapper.entityPicture != null) {
|
|
||||||
iconWidget = ClipOval(
|
|
||||||
child: CachedNetworkImage(
|
|
||||||
imageUrl: '${entityWrapper.entityPicture}',
|
|
||||||
width: size+12,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
height: size+12,
|
|
||||||
errorWidget: (context, str, dyn) {
|
|
||||||
return Padding(
|
|
||||||
padding: iconPadding ?? padding,
|
|
||||||
child: _buildIcon(entityWrapper, iconColor)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
isPicture = true;
|
|
||||||
} else {
|
|
||||||
iconWidget = _buildIcon(entityWrapper, iconColor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
EdgeInsetsGeometry computedPadding;
|
String iconName = data.icon;
|
||||||
if (isPicture && imagePadding != null) {
|
|
||||||
computedPadding = imagePadding;
|
|
||||||
} else if (!isPicture && iconPadding != null) {
|
|
||||||
computedPadding = iconPadding;
|
|
||||||
} else {
|
|
||||||
computedPadding = padding;
|
|
||||||
}
|
|
||||||
return Padding(
|
|
||||||
padding: computedPadding,
|
|
||||||
child: iconWidget,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildIcon(EntityWrapper entityWrapper, Color iconColor) {
|
|
||||||
Widget iconWidget;
|
|
||||||
String iconName = entityWrapper.icon;
|
|
||||||
int iconCode = 0;
|
int iconCode = 0;
|
||||||
if (iconName.length > 0) {
|
if (iconName.length > 0) {
|
||||||
iconCode = MaterialDesignIcons.getIconCodeByIconName(iconName);
|
iconCode = MaterialDesignIcons.getIconCodeByIconName(iconName);
|
||||||
} else {
|
} else {
|
||||||
iconCode = getDefaultIconByEntityId(entityWrapper.entity.entityId,
|
iconCode = getDefaultIconByEntityId(data.entity.entityId,
|
||||||
entityWrapper.entity.deviceClass, entityWrapper.entity.state); //
|
data.entity.deviceClass, data.entity.state); //
|
||||||
}
|
}
|
||||||
if (showBadge && entityWrapper.entity is LightEntity &&
|
return Padding(
|
||||||
(entityWrapper.entity as LightEntity).supportColor &&
|
padding: EdgeInsets.fromLTRB(6.0, 6.0, 6.0, 6.0),
|
||||||
(entityWrapper.entity as LightEntity).color != null &&
|
child: Icon(
|
||||||
(entityWrapper.entity as LightEntity).color.toColor() != Colors.white
|
IconData(iconCode, fontFamily: 'Material Design Icons'),
|
||||||
) {
|
size: size,
|
||||||
Color lightColor = (entityWrapper.entity as LightEntity).color.toColor();
|
color: color,
|
||||||
iconWidget = Stack(
|
)
|
||||||
children: <Widget>[
|
);
|
||||||
Icon(
|
}
|
||||||
IconData(iconCode, fontFamily: 'Material Design Icons'),
|
|
||||||
size: size,
|
@override
|
||||||
color: iconColor,
|
Widget build(BuildContext context) {
|
||||||
),
|
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||||
Positioned(
|
return Padding(
|
||||||
bottom: 0,
|
padding: padding,
|
||||||
right: 0,
|
child: buildIcon(
|
||||||
child: Container(
|
entityWrapper,
|
||||||
width: size / 3,
|
color ?? EntityColor.stateColor(entityWrapper.entity.state)
|
||||||
height: size / 3,
|
),
|
||||||
decoration: BoxDecoration(
|
);
|
||||||
color: lightColor,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
boxShadow: <BoxShadow>[
|
|
||||||
BoxShadow(
|
|
||||||
spreadRadius: 0,
|
|
||||||
blurRadius: 0,
|
|
||||||
offset: Offset(0.3, 0.3)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
iconWidget = Icon(
|
|
||||||
IconData(iconCode, fontFamily: 'Material Design Icons'),
|
|
||||||
size: size,
|
|
||||||
color: iconColor,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return iconWidget;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,11 +12,11 @@ class EntityModel extends InheritedWidget {
|
|||||||
final bool handleTap;
|
final bool handleTap;
|
||||||
|
|
||||||
static EntityModel of(BuildContext context) {
|
static EntityModel of(BuildContext context) {
|
||||||
return context.dependOnInheritedWidgetOfExactType<EntityModel>();
|
return context.inheritFromWidgetOfExactType(EntityModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(EntityModel oldWidget) {
|
bool updateShouldNotify(InheritedWidget oldWidget) {
|
||||||
return entityWrapper.entity.lastUpdatedTimestamp != oldWidget.entityWrapper.entity.lastUpdatedTimestamp;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -2,9 +2,10 @@ part of '../main.dart';
|
|||||||
|
|
||||||
class EntityPageLayout extends StatelessWidget {
|
class EntityPageLayout extends StatelessWidget {
|
||||||
|
|
||||||
|
final bool showClose;
|
||||||
final Entity entity;
|
final Entity entity;
|
||||||
|
|
||||||
EntityPageLayout({Key key, this.entity}) : super(key: key);
|
EntityPageLayout({Key key, this.showClose: false, this.entity}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -13,6 +14,38 @@ class EntityPageLayout extends StatelessWidget {
|
|||||||
child: ListView(
|
child: ListView(
|
||||||
padding: EdgeInsets.all(0),
|
padding: EdgeInsets.all(0),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
showClose ?
|
||||||
|
Container(
|
||||||
|
color: Colors.blue[300],
|
||||||
|
height: 36,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(left: 8),
|
||||||
|
child: Text(
|
||||||
|
entity.displayName,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 22
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
padding: EdgeInsets.all(0),
|
||||||
|
icon: Icon(Icons.close),
|
||||||
|
color: Colors.white,
|
||||||
|
iconSize: 30.0,
|
||||||
|
onPressed: () {
|
||||||
|
eventBus.fire(ShowEntityPageEvent());
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
) :
|
||||||
|
Container(height: 0, width: 0,),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: Sizes.rowPadding, left: Sizes.leftWidgetPadding),
|
padding: EdgeInsets.only(top: Sizes.rowPadding, left: Sizes.leftWidgetPadding),
|
||||||
child: DefaultEntityContainer(state: entity._buildStatePartForPage(context)),
|
child: DefaultEntityContainer(state: entity._buildStatePartForPage(context)),
|
||||||
|
@ -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
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,48 +2,47 @@ part of '../main.dart';
|
|||||||
|
|
||||||
class EntityWrapper {
|
class EntityWrapper {
|
||||||
|
|
||||||
String overrideName;
|
String displayName;
|
||||||
String overrideIcon;
|
String icon;
|
||||||
final bool stateColor;
|
String unitOfMeasurement;
|
||||||
|
String entityPicture;
|
||||||
EntityUIAction uiAction;
|
EntityUIAction uiAction;
|
||||||
Entity entity;
|
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({
|
EntityWrapper({
|
||||||
this.entity,
|
this.entity,
|
||||||
this.overrideIcon,
|
String icon,
|
||||||
this.overrideName,
|
String displayName,
|
||||||
this.stateColor: true,
|
this.uiAction
|
||||||
this.uiAction,
|
|
||||||
this.stateFilter
|
|
||||||
}) {
|
}) {
|
||||||
if (entity.statelessType == StatelessEntityType.ghost || entity.statelessType == StatelessEntityType.none || entity.statelessType == StatelessEntityType.callService || entity.statelessType == StatelessEntityType.webLink) {
|
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) {
|
if (uiAction == null) {
|
||||||
uiAction = EntityUIAction();
|
uiAction = EntityUIAction();
|
||||||
}
|
}
|
||||||
|
unitOfMeasurement = entity.unitOfMeasurement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleTap() {
|
void handleTap() {
|
||||||
switch (uiAction.tapAction) {
|
switch (uiAction.tapAction) {
|
||||||
case EntityUIAction.toggle: {
|
case EntityUIAction.toggle: {
|
||||||
ConnectionManager().callService(domain: "homeassistant", service: "toggle", entityId: entity.entityId);
|
eventBus.fire(
|
||||||
|
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EntityUIAction.callService: {
|
case EntityUIAction.callService: {
|
||||||
if (uiAction.tapService != null) {
|
if (uiAction.tapService != null) {
|
||||||
ConnectionManager().callService(
|
eventBus.fire(
|
||||||
domain: uiAction.tapService.split(".")[0],
|
ServiceCallEvent(uiAction.tapService.split(".")[0],
|
||||||
service: uiAction.tapService.split(".")[1],
|
uiAction.tapService.split(".")[1], null,
|
||||||
data: uiAction.tapServiceData
|
uiAction.tapServiceData));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -54,16 +53,16 @@ class EntityWrapper {
|
|||||||
|
|
||||||
case EntityUIAction.moreInfo: {
|
case EntityUIAction.moreInfo: {
|
||||||
eventBus.fire(
|
eventBus.fire(
|
||||||
new ShowEntityPageEvent(entityId: entity.entityId));
|
new ShowEntityPageEvent(entity: entity));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EntityUIAction.navigate: {
|
case EntityUIAction.navigate: {
|
||||||
if (uiAction.tapService != null && uiAction.tapService.startsWith("/")) {
|
if (uiAction.tapService.startsWith("/")) {
|
||||||
//TODO handle local urls
|
//TODO handle local urls
|
||||||
Launcher.launchURLInBrowser('${AppSettings().httpWebHost}${uiAction.tapService}');
|
Logger.w("Local urls is not supported yet");
|
||||||
} else {
|
} else {
|
||||||
Launcher.launchURLInBrowser(uiAction.tapService);
|
Launcher.launchURL(uiAction.tapService);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -77,73 +76,33 @@ class EntityWrapper {
|
|||||||
void handleHold() {
|
void handleHold() {
|
||||||
switch (uiAction.holdAction) {
|
switch (uiAction.holdAction) {
|
||||||
case EntityUIAction.toggle: {
|
case EntityUIAction.toggle: {
|
||||||
ConnectionManager().callService(domain: "homeassistant", service: "toggle", entityId: entity.entityId);
|
eventBus.fire(
|
||||||
|
ServiceCallEvent("homeassistant", "toggle", entity.entityId, null));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EntityUIAction.callService: {
|
case EntityUIAction.callService: {
|
||||||
if (uiAction.holdService != null) {
|
if (uiAction.holdService != null) {
|
||||||
ConnectionManager().callService(
|
eventBus.fire(
|
||||||
domain: uiAction.holdService.split(".")[0],
|
ServiceCallEvent(uiAction.holdService.split(".")[0],
|
||||||
service: uiAction.holdService.split(".")[1],
|
uiAction.holdService.split(".")[1], null,
|
||||||
data: uiAction.holdServiceData
|
uiAction.holdServiceData));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EntityUIAction.moreInfo: {
|
case EntityUIAction.moreInfo: {
|
||||||
eventBus.fire(
|
eventBus.fire(
|
||||||
new ShowEntityPageEvent(entityId: entity.entityId));
|
new ShowEntityPageEvent(entity: entity));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EntityUIAction.navigate: {
|
case EntityUIAction.navigate: {
|
||||||
if (uiAction.holdService != null && uiAction.holdService.startsWith("/")) {
|
if (uiAction.holdService.startsWith("/")) {
|
||||||
//TODO handle local urls
|
//TODO handle local urls
|
||||||
Launcher.launchURLInBrowser('${AppSettings().httpWebHost}${uiAction.holdService}');
|
Logger.w("Local urls is not supported yet");
|
||||||
} else {
|
} else {
|
||||||
Launcher.launchURLInBrowser(uiAction.holdService);
|
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(entityId: entity.entityId));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EntityUIAction.navigate: {
|
|
||||||
if (uiAction.doubleTapService != null && uiAction.doubleTapService.startsWith("/")) {
|
|
||||||
//TODO handle local urls
|
|
||||||
Launcher.launchURLInBrowser('${AppSettings().httpWebHost}${uiAction.doubleTapService}');
|
|
||||||
} else {
|
|
||||||
Launcher.launchURLInBrowser(uiAction.doubleTapService);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -155,63 +114,3 @@ class EntityWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class EntityUIAction {
|
|
||||||
static const moreInfo = 'more-info';
|
|
||||||
static const toggle = 'toggle';
|
|
||||||
static const callService = 'call-service';
|
|
||||||
static const navigate = 'navigate';
|
|
||||||
static const none = 'none';
|
|
||||||
|
|
||||||
String tapAction = EntityUIAction.moreInfo;
|
|
||||||
String tapNavigationPath;
|
|
||||||
String tapService;
|
|
||||||
Map<String, dynamic> tapServiceData;
|
|
||||||
String holdAction = EntityUIAction.moreInfo;
|
|
||||||
String holdNavigationPath;
|
|
||||||
String holdService;
|
|
||||||
Map<String, dynamic> holdServiceData;
|
|
||||||
String doubleTapAction = EntityUIAction.none;
|
|
||||||
String doubleTapNavigationPath;
|
|
||||||
String doubleTapService;
|
|
||||||
Map<String, dynamic> doubleTapServiceData;
|
|
||||||
|
|
||||||
EntityUIAction({rawEntityData}) {
|
|
||||||
if (rawEntityData != null) {
|
|
||||||
if (rawEntityData["tap_action"] != null) {
|
|
||||||
if (rawEntityData["tap_action"] is String) {
|
|
||||||
tapAction = rawEntityData["tap_action"];
|
|
||||||
} else {
|
|
||||||
tapAction =
|
|
||||||
rawEntityData["tap_action"]["action"] ?? EntityUIAction.moreInfo;
|
|
||||||
tapNavigationPath = rawEntityData["tap_action"]["navigation_path"];
|
|
||||||
tapService = rawEntityData["tap_action"]["service"];
|
|
||||||
tapServiceData = rawEntityData["tap_action"]["service_data"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rawEntityData["hold_action"] != null) {
|
|
||||||
if (rawEntityData["hold_action"] is String) {
|
|
||||||
holdAction = rawEntityData["hold_action"];
|
|
||||||
} else {
|
|
||||||
holdAction =
|
|
||||||
rawEntityData["hold_action"]["action"] ?? EntityUIAction.none;
|
|
||||||
holdNavigationPath = rawEntityData["hold_action"]["navigation_path"];
|
|
||||||
holdService = rawEntityData["hold_action"]["service"];
|
|
||||||
holdServiceData = rawEntityData["hold_action"]["service_data"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class ErrorEntityWidget extends StatelessWidget {
|
|
||||||
|
|
||||||
final String text;
|
|
||||||
|
|
||||||
ErrorEntityWidget({
|
|
||||||
Key key, this.text
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final EntityModel entityModel = EntityModel.of(context);
|
|
||||||
String errorText = text ?? "Entity error: ${entityModel.entityWrapper.entity?.entityId}";
|
|
||||||
return Container(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.all(5.0),
|
|
||||||
child: Text(errorText),
|
|
||||||
),
|
|
||||||
color: Theme.of(context).errorColor,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -28,12 +28,9 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tmpBrightness = value.round();
|
_tmpBrightness = value.round();
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
ConnectionManager().callService(
|
eventBus.fire(new ServiceCallEvent(
|
||||||
domain: entity.domain,
|
entity.domain, "turn_on", entity.entityId,
|
||||||
service: "turn_on",
|
{"brightness": _tmpBrightness}));
|
||||||
entityId: entity.entityId,
|
|
||||||
data: {"brightness": _tmpBrightness}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,12 +38,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 +49,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 +59,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 +71,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"}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -134,10 +120,8 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
_tmpBrightness = value.round();
|
_tmpBrightness = value.round();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
min: 1,
|
min: 1.0,
|
||||||
max: 255,
|
max: 255.0,
|
||||||
divisions: 254,
|
|
||||||
label: '${val?.toInt() ?? ''}',
|
|
||||||
onChangeEnd: (value) => _setBrightness(entity, value),
|
onChangeEnd: (value) => _setBrightness(entity, value),
|
||||||
value: val,
|
value: val,
|
||||||
leading: Icon(Icons.brightness_5),
|
leading: Icon(Icons.brightness_5),
|
||||||
@ -157,12 +141,10 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
_tmpWhiteValue = value.round();
|
_tmpWhiteValue = value.round();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
min: 0,
|
min: 0.0,
|
||||||
max: 255,
|
max: 255.0,
|
||||||
divisions: 255,
|
|
||||||
label: '$_tmpWhiteValue',
|
|
||||||
onChangeEnd: (value) => _setWhiteValue(entity, value),
|
onChangeEnd: (value) => _setWhiteValue(entity, value),
|
||||||
value: _tmpWhiteValue?.toDouble() ?? 0.0,
|
value: _tmpWhiteValue == null ? 0.0 : _tmpWhiteValue.toDouble(),
|
||||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:file-word-box")),
|
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:file-word-box")),
|
||||||
title: "White",
|
title: "White",
|
||||||
);
|
);
|
||||||
@ -187,20 +169,18 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
}
|
}
|
||||||
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: val,
|
||||||
onChangeEnd: (value) => _setColorTemp(entity, value),
|
onChangeEnd: (value) => _setColorTemp(entity, value),
|
||||||
max: entity.maxMireds,
|
max: entity.maxMireds,
|
||||||
min: entity.minMireds,
|
min: entity.minMireds,
|
||||||
divisions: (entity.maxMireds - entity.minMireds).toInt(),
|
|
||||||
label: '$_tmpColorTemp',
|
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
_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);
|
||||||
@ -230,7 +210,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
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);
|
||||||
@ -247,6 +227,8 @@ 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)) {
|
||||||
|
Logger.d("[LIGHT] entity effects: ${entity.effectList}");
|
||||||
|
Logger.d("[LIGHT] current effect: $_tmpEffect");
|
||||||
List<String> list = List.from(entity.effectList);
|
List<String> list = List.from(entity.effectList);
|
||||||
if (_tmpEffect!= null && !list.contains(_tmpEffect)) {
|
if (_tmpEffect!= null && !list.contains(_tmpEffect)) {
|
||||||
list.insert(0, _tmpEffect);
|
list.insert(0, _tmpEffect);
|
||||||
|
@ -7,11 +7,11 @@ class LockStateWidget extends StatelessWidget {
|
|||||||
const LockStateWidget({Key key, this.assumedState: false}) : super(key: key);
|
const LockStateWidget({Key key, this.assumedState: false}) : super(key: key);
|
||||||
|
|
||||||
void _lock(Entity entity) {
|
void _lock(Entity entity) {
|
||||||
ConnectionManager().callService(domain: "lock", service: "lock", entityId: entity.entityId);
|
eventBus.fire(new ServiceCallEvent("lock", "lock", entity.entityId, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _unlock(Entity entity) {
|
void _unlock(Entity entity) {
|
||||||
ConnectionManager().callService(domain: "lock", service: "unlock", entityId: entity.entityId);
|
eventBus.fire(new ServiceCallEvent("lock", "unlock", entity.entityId, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -28,7 +28,8 @@ class LockStateWidget extends StatelessWidget {
|
|||||||
onPressed: () => _unlock(entity),
|
onPressed: () => _unlock(entity),
|
||||||
child: Text("UNLOCK",
|
child: Text("UNLOCK",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: HAClientTheme().getActionTextStyle(context)
|
style:
|
||||||
|
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -38,7 +39,8 @@ class LockStateWidget extends StatelessWidget {
|
|||||||
onPressed: () => _lock(entity),
|
onPressed: () => _lock(entity),
|
||||||
child: Text("LOCK",
|
child: Text("LOCK",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: HAClientTheme().getActionTextStyle(context),
|
style:
|
||||||
|
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -54,7 +56,8 @@ class LockStateWidget extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
entity.isLocked ? "UNLOCK" : "LOCK",
|
entity.isLocked ? "UNLOCK" : "LOCK",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: HAClientTheme().getActionTextStyle(context),
|
style:
|
||||||
|
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -84,22 +84,25 @@ class MediaPlayerEntity extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool canCalculateActualPosition() {
|
bool canCalculateActualPosition() {
|
||||||
return positionLastUpdated != null && durationSeconds != null && positionSeconds != null && durationSeconds > 0;
|
return positionLastUpdated != null && durationSeconds != null && positionSeconds != null && durationSeconds >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
double getActualPosition() {
|
double getActualPosition() {
|
||||||
double result = 0;
|
double result = 0;
|
||||||
Duration durationD;
|
if (canCalculateActualPosition()) {
|
||||||
Duration positionD;
|
Duration durationD;
|
||||||
durationD = Duration(seconds: durationSeconds);
|
Duration positionD;
|
||||||
positionD = Duration(
|
durationD = Duration(seconds: durationSeconds);
|
||||||
|
positionD = Duration(
|
||||||
seconds: positionSeconds);
|
seconds: positionSeconds);
|
||||||
result = positionD.inSeconds.toDouble();
|
result = positionD.inSeconds.toDouble();
|
||||||
int differenceInSeconds = DateTime
|
int differenceInSeconds = DateTime
|
||||||
.now()
|
.now()
|
||||||
.difference(positionLastUpdated)
|
.difference(positionLastUpdated)
|
||||||
.inSeconds;
|
.inSeconds;
|
||||||
result = ((result + differenceInSeconds) <= durationD.inSeconds) ? (result + differenceInSeconds) : durationD.inSeconds.toDouble();
|
result = ((result + differenceInSeconds) <= durationD.inSeconds) ? (result + differenceInSeconds) : durationD.inSeconds.toDouble();
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,18 +22,18 @@ class _MediaPlayerProgressBarState extends State<MediaPlayerProgressBar> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final EntityModel entityModel = EntityModel.of(context);
|
final EntityModel entityModel = EntityModel.of(context);
|
||||||
final MediaPlayerEntity entity = entityModel.entityWrapper.entity;
|
final MediaPlayerEntity entity = entityModel.entityWrapper.entity;
|
||||||
double progress = 0;
|
double progress;
|
||||||
int currentPosition;
|
int currentPosition;
|
||||||
if (entity.canCalculateActualPosition()) {
|
if (entity.canCalculateActualPosition()) {
|
||||||
currentPosition = entity.getActualPosition().toInt();
|
currentPosition = entity.getActualPosition().toInt();
|
||||||
if (currentPosition > 0) {
|
progress = (currentPosition <= entity.durationSeconds) ? currentPosition / entity.durationSeconds : 100;
|
||||||
progress = (currentPosition <= entity.durationSeconds) ? currentPosition / entity.durationSeconds : 100;
|
} else {
|
||||||
}
|
progress = 0;
|
||||||
}
|
}
|
||||||
return LinearProgressIndicator(
|
return LinearProgressIndicator(
|
||||||
value: progress,
|
value: progress,
|
||||||
backgroundColor: Colors.black45,
|
backgroundColor: Colors.black45,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(HAClientTheme().getOnStateColor(context)),
|
valueColor: AlwaysStoppedAnimation<Color>(EntityColor.stateColor(EntityState.on)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,12 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
|
|||||||
double _currentPosition = 0;
|
double _currentPosition = 0;
|
||||||
int _savedPosition = 0;
|
int _savedPosition = 0;
|
||||||
|
|
||||||
|
final TextStyle _seekTextStyle = TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
color: Colors.blue,
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
initState() {
|
initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -47,14 +53,15 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
|
|||||||
buttons.add(
|
buttons.add(
|
||||||
RaisedButton(
|
RaisedButton(
|
||||||
child: Text("Jump to ${Duration(seconds: _savedPosition).toString().split('.')[0]}"),
|
child: Text("Jump to ${Duration(seconds: _savedPosition).toString().split('.')[0]}"),
|
||||||
color: Theme.of(context).accentColor,
|
color: Colors.orange,
|
||||||
|
focusColor: Colors.white,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ConnectionManager().callService(
|
eventBus.fire(ServiceCallEvent(
|
||||||
domain: "media_player",
|
"media_player",
|
||||||
service: "media_seek",
|
"media_seek",
|
||||||
entityId: entity.entityId,
|
"${entity.entityId}",
|
||||||
data: {"seek_position": _savedPosition}
|
{"seek_position": _savedPosition}
|
||||||
);
|
));
|
||||||
setState(() {
|
setState(() {
|
||||||
_savedPosition = 0;
|
_savedPosition = 0;
|
||||||
});
|
});
|
||||||
@ -72,20 +79,16 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text("00:00"),
|
Text("00:00"),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text("${Duration(seconds: _currentPosition.toInt()).toString().split(".")[0]}",textAlign: TextAlign.center, style: _seekTextStyle),
|
||||||
"${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]}")
|
Text("${Duration(seconds: entity.durationSeconds).toString().split(".")[0]}")
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
UniversalSlider(
|
Container(height: 10,),
|
||||||
|
Slider(
|
||||||
min: 0,
|
min: 0,
|
||||||
activeColor: Theme.of(context).accentColor,
|
activeColor: Colors.amber,
|
||||||
|
inactiveColor: Colors.black26,
|
||||||
max: entity.durationSeconds.toDouble(),
|
max: entity.durationSeconds.toDouble(),
|
||||||
value: _currentPosition,
|
value: _currentPosition,
|
||||||
onChangeStart: (val) {
|
onChangeStart: (val) {
|
||||||
@ -100,12 +103,12 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
|
|||||||
_seekStarted = false;
|
_seekStarted = false;
|
||||||
Timer(Duration(milliseconds: 500), () {
|
Timer(Duration(milliseconds: 500), () {
|
||||||
if (!_seekStarted) {
|
if (!_seekStarted) {
|
||||||
ConnectionManager().callService(
|
eventBus.fire(ServiceCallEvent(
|
||||||
domain: "media_player",
|
"media_player",
|
||||||
service: "media_seek",
|
"media_seek",
|
||||||
entityId: entity.entityId,
|
"${entity.entityId}",
|
||||||
data: {"seek_position": val}
|
{"seek_position": val}
|
||||||
);
|
));
|
||||||
setState(() {
|
setState(() {
|
||||||
_changedHere = true;
|
_changedHere = true;
|
||||||
_currentPosition = val;
|
_currentPosition = val;
|
||||||
|
@ -12,14 +12,14 @@ 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(
|
||||||
@ -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,7 +71,7 @@ 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 (entity.entityPicture != null && state != EntityState.off && state != EntityState.unavailable && state != EntityState.idle) {
|
||||||
return Container(
|
return Container(
|
||||||
@ -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(entityId: entity.entityId))
|
onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity: entity))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else if (entity.supportStop && entity.state != EntityState.off && entity.state != EntityState.unavailable) {
|
} else if (entity.supportStop && entity.state != EntityState.off && entity.state != EntityState.unavailable) {
|
||||||
@ -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"}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,13 +326,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)
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -457,15 +430,15 @@ class _MediaPlayerControlsState extends State<MediaPlayerControls> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _duplicateTo(entity) {
|
void _duplicateTo(entity) {
|
||||||
if (entity.canCalculateActualPosition()) {
|
HomeAssistant().savedPlayerPosition = entity.getActualPosition().toInt();
|
||||||
HomeAssistant().savedPlayerPosition = entity.getActualPosition().toInt();
|
if (MediaQuery.of(context).size.width < Sizes.tabletMinWidth) {
|
||||||
|
Navigator.of(context).popAndPushNamed("/play-media", arguments: {"url": entity.attributes["media_content_id"], "type": entity.attributes["media_content_type"]});
|
||||||
} else {
|
} else {
|
||||||
HomeAssistant().savedPlayerPosition = 0;
|
Navigator.of(context).pushNamed("/play-media", arguments: {
|
||||||
}
|
|
||||||
Navigator.of(context).pushNamed("/play-media", arguments: {
|
|
||||||
"url": entity.attributes["media_content_id"],
|
"url": entity.attributes["media_content_id"],
|
||||||
"type": entity.attributes["media_content_type"]
|
"type": entity.attributes["media_content_type"]
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _switchTo(entity) {
|
void _switchTo(entity) {
|
||||||
|
@ -11,12 +11,8 @@ class SelectStateWidget extends StatefulWidget {
|
|||||||
class _SelectStateWidgetState extends State<SelectStateWidget> {
|
class _SelectStateWidgetState extends State<SelectStateWidget> {
|
||||||
|
|
||||||
void setNewState(domain, entityId, newValue) {
|
void setNewState(domain, entityId, newValue) {
|
||||||
ConnectionManager().callService(
|
eventBus.fire(new ServiceCallEvent(domain, "select_option", entityId,
|
||||||
domain: domain,
|
{"option": "$newValue"}));
|
||||||
service: "select_option",
|
|
||||||
entityId: entityId,
|
|
||||||
data: {"option": "$newValue"}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -7,10 +7,10 @@ class SimpleEntityState extends StatelessWidget {
|
|||||||
final EdgeInsetsGeometry padding;
|
final EdgeInsetsGeometry padding;
|
||||||
final int maxLines;
|
final int maxLines;
|
||||||
final String customValue;
|
final String customValue;
|
||||||
final TextStyle textStyle;
|
final double fontSize;
|
||||||
//final bool bold;
|
final bool bold;
|
||||||
|
|
||||||
const SimpleEntityState({Key key,/*this.bold: false,*/ this.maxLines: 10, this.expanded: true, this.textAlign: TextAlign.right, this.textStyle, this.padding: const EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0), this.customValue}) : super(key: key);
|
const SimpleEntityState({Key key,this.bold: false, this.maxLines: 10, this.fontSize: Sizes.stateFontSize, this.expanded: true, this.textAlign: TextAlign.right, this.padding: const EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0), this.customValue}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -22,19 +22,16 @@ class SimpleEntityState extends StatelessWidget {
|
|||||||
} else {
|
} else {
|
||||||
state = customValue;
|
state = customValue;
|
||||||
}
|
}
|
||||||
TextStyle tStyle;
|
TextStyle textStyle = TextStyle(
|
||||||
if (textStyle != null) {
|
fontSize: this.fontSize,
|
||||||
tStyle = textStyle;
|
fontWeight: FontWeight.normal
|
||||||
} else if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.callService) {
|
);
|
||||||
tStyle = Theme.of(context).textTheme.subhead.copyWith(
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.CALL_SERVICE) {
|
||||||
color: HAClientTheme().getLinkTextStyle(context).color
|
textStyle = textStyle.apply(color: Colors.blue);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
tStyle = Theme.of(context).textTheme.body1;
|
|
||||||
}
|
}
|
||||||
/*if (this.bold) {
|
if (this.bold) {
|
||||||
textStyle = textStyle.apply(fontWeightDelta: 100);
|
textStyle = textStyle.apply(fontWeightDelta: 100);
|
||||||
}*/
|
}
|
||||||
while (state.contains(" ")){
|
while (state.contains(" ")){
|
||||||
state = state.replaceAll(" ", " ");
|
state = state.replaceAll(" ", " ");
|
||||||
}
|
}
|
||||||
@ -46,7 +43,7 @@ class SimpleEntityState extends StatelessWidget {
|
|||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
style: tStyle
|
style: textStyle
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
|
@ -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
|
||||||
@ -40,7 +36,7 @@ class _SliderControlsWidgetState extends State<SliderControlsWidget> {
|
|||||||
} else {
|
} else {
|
||||||
_changedHere = false;
|
_changedHere = false;
|
||||||
}
|
}
|
||||||
Widget slider = UniversalSlider(
|
Widget slider = Slider(
|
||||||
min: entity.minValue * _multiplier,
|
min: entity.minValue * _multiplier,
|
||||||
max: entity.maxValue * _multiplier,
|
max: entity.maxValue * _multiplier,
|
||||||
value: (_newValue <= entity.maxValue) &&
|
value: (_newValue <= entity.maxValue) &&
|
||||||
@ -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
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -38,11 +38,8 @@ class _SwitchStateWidgetState extends State<SwitchStateWidget> {
|
|||||||
} else {
|
} else {
|
||||||
domain = entity.domain;
|
domain = entity.domain;
|
||||||
}
|
}
|
||||||
ConnectionManager().callService(
|
eventBus.fire(new ServiceCallEvent(
|
||||||
domain: domain,
|
domain, (newValue as bool) ? "turn_on" : "turn_off", entity.entityId, null));
|
||||||
service: (newValue as bool) ? "turn_on" : "turn_off",
|
|
||||||
entityId: entity.entityId
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -26,12 +26,8 @@ class _TextInputStateWidgetState extends State<TextInputStateWidget> {
|
|||||||
|
|
||||||
void setNewState(newValue, domain, entityId) {
|
void setNewState(newValue, domain, entityId) {
|
||||||
if (validate(newValue, _minLength, _maxLength)) {
|
if (validate(newValue, _minLength, _maxLength)) {
|
||||||
ConnectionManager().callService(
|
eventBus.fire(new ServiceCallEvent(domain, "set_value", entityId,
|
||||||
domain: domain,
|
{"value": "$newValue"}));
|
||||||
service: "set_value",
|
|
||||||
entityId: entityId,
|
|
||||||
data: {"value": "$newValue"}
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
_tmpValue = _entityState;
|
_tmpValue = _entityState;
|
||||||
|