1786278419
This commit is contained in:
27
Cargo.lock
generated
27
Cargo.lock
generated
@@ -57,7 +57,7 @@ dependencies = [
|
||||
"ndk-context",
|
||||
"ndk-sys",
|
||||
"num_enum",
|
||||
"thiserror 2.0.19",
|
||||
"thiserror 2.0.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -312,7 +312,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"pastey",
|
||||
"rayon",
|
||||
"thiserror 2.0.19",
|
||||
"thiserror 2.0.20",
|
||||
"v_frame",
|
||||
"y4m",
|
||||
]
|
||||
@@ -851,6 +851,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"slint",
|
||||
"slint-build",
|
||||
"thiserror 2.0.20",
|
||||
"tokio",
|
||||
"trekstor",
|
||||
"url",
|
||||
@@ -1636,7 +1637,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.19",
|
||||
"thiserror 2.0.20",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
]
|
||||
@@ -2304,7 +2305,7 @@ dependencies = [
|
||||
"jni-sys 0.4.1",
|
||||
"log",
|
||||
"simd_cesu8",
|
||||
"thiserror 2.0.19",
|
||||
"thiserror 2.0.20",
|
||||
"walkdir",
|
||||
"windows-link",
|
||||
]
|
||||
@@ -2552,7 +2553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7755f08423275157ad1680aaecc9ccb7e0cc633da3240fea2d1522935cc15c72"
|
||||
dependencies = [
|
||||
"lyon_path",
|
||||
"thiserror 2.0.19",
|
||||
"thiserror 2.0.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2662,7 +2663,7 @@ dependencies = [
|
||||
"objc2-foundation 0.3.2",
|
||||
"once_cell",
|
||||
"png",
|
||||
"thiserror 2.0.19",
|
||||
"thiserror 2.0.20",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
@@ -3662,7 +3663,7 @@ dependencies = [
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"simd_helpers",
|
||||
"thiserror 2.0.19",
|
||||
"thiserror 2.0.20",
|
||||
"v_frame",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -4476,11 +4477,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.19"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
|
||||
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.19",
|
||||
"thiserror-impl 2.0.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4496,9 +4497,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.19"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
|
||||
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4739,7 +4740,7 @@ dependencies = [
|
||||
"log",
|
||||
"rand",
|
||||
"sha1",
|
||||
"thiserror 2.0.19",
|
||||
"thiserror 2.0.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -12,6 +12,7 @@ tokio = { version = "1.53.1", features = ["full"] }
|
||||
dotenvy = "0.15.7"
|
||||
url = "2.5.8"
|
||||
chrono = "0.4.45"
|
||||
thiserror = "2.0.20"
|
||||
|
||||
[features]
|
||||
dev = ["slint/backend-winit-x11"]
|
||||
|
||||
@@ -1,84 +1,105 @@
|
||||
mod ha_ext;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use dotenvy::var;
|
||||
use hass_rs::{HassClient, HassEntity};
|
||||
use serde_json::{Value, json};
|
||||
use serde_json::json;
|
||||
use slint::language::ColorScheme;
|
||||
use slint::{ModelRc, VecModel, Weak};
|
||||
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
||||
slint::include_modules!();
|
||||
|
||||
fn apply_theme(app: &AppWindow, state: &State) {
|
||||
if let Some(illuminance) = state
|
||||
.get("sensor.diyless_thermostat_3_ambient_light_level")
|
||||
.as_ref()
|
||||
&& let Ok(illuminance) = illuminance.state.parse::<i32>()
|
||||
{
|
||||
let color_scheme = if illuminance < 250 {
|
||||
ColorScheme::Dark
|
||||
} else {
|
||||
ColorScheme::Light
|
||||
};
|
||||
|
||||
app.set_color_scheme(color_scheme);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_clock(app: &AppWindow, state: &State) {
|
||||
if let Some(date_time_iso) = state.get("sensor.date_time_iso").as_ref() {
|
||||
app.set_date_time(date_time_iso.into());
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_lights(app: &AppWindow, state: &State) {
|
||||
let lights = state
|
||||
.domain("light")
|
||||
.iter()
|
||||
.filter(|light| {
|
||||
!(light.entity_id.ends_with("screen") || light.entity_id.chars().any(char::is_numeric))
|
||||
})
|
||||
.map(Into::into)
|
||||
.collect::<Vec<LightData>>();
|
||||
app.set_lights(ModelRc::new(VecModel::from(lights)));
|
||||
}
|
||||
|
||||
fn apply_thermometers(app: &AppWindow, state: &State) {
|
||||
let thermometers = state
|
||||
.domain("sensor")
|
||||
.iter()
|
||||
.filter(|sensor| sensor.entity_id.ends_with("thermometer_temperature"))
|
||||
.map(Into::into)
|
||||
.collect::<Vec<ThermometerData>>();
|
||||
app.set_thermometers(ModelRc::new(VecModel::from(thermometers)));
|
||||
}
|
||||
|
||||
fn apply_weather(app: &AppWindow, state: &State) {
|
||||
if let Some(weather) = state.get("weather.forecast_home").as_ref() {
|
||||
app.set_weather(weather.into());
|
||||
}
|
||||
app.set_is_night(
|
||||
state
|
||||
.get("sun.sun")
|
||||
.is_some_and(|sun| sun.state == "below_horizon"),
|
||||
);
|
||||
}
|
||||
|
||||
fn apply_thermostats(app: &AppWindow, state: &State) {
|
||||
let thermostats = state
|
||||
.domain("climate")
|
||||
.iter()
|
||||
.filter(|sensor| !sensor.entity_id.contains("diyless"))
|
||||
.map(Into::into)
|
||||
.collect::<Vec<ThermostatData>>();
|
||||
|
||||
app.set_thermostats(ModelRc::new(VecModel::from(thermostats)));
|
||||
}
|
||||
|
||||
fn apply_state(app: &Weak<AppWindow>, state: State) {
|
||||
app.upgrade_in_event_loop(move |app| {
|
||||
// Light/Dark mode
|
||||
//
|
||||
if let Some(illuminance) = state
|
||||
.get("sensor.diyless_thermostat_3_ambient_light_level")
|
||||
.as_ref()
|
||||
&& let Ok(illuminance) = illuminance.state.parse::<i32>()
|
||||
{
|
||||
let scheme = if illuminance < 300 {
|
||||
ColorScheme::Dark
|
||||
} else {
|
||||
ColorScheme::Light
|
||||
};
|
||||
if let Err(err) = app.upgrade_in_event_loop(move |app| {
|
||||
apply_theme(&app, &state);
|
||||
apply_clock(&app, &state);
|
||||
apply_lights(&app, &state);
|
||||
apply_thermometers(&app, &state);
|
||||
apply_weather(&app, &state);
|
||||
apply_thermostats(&app, &state);
|
||||
}) {
|
||||
eprintln!("Failed to upgrade in event loop.\n\t{:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
app.set_color_scheme(scheme);
|
||||
}
|
||||
|
||||
// Lights
|
||||
let lights = state
|
||||
.domain("light")
|
||||
.iter()
|
||||
.filter(|light| {
|
||||
!(light.entity_id.ends_with("screen")
|
||||
|| light.entity_id.chars().any(char::is_numeric))
|
||||
})
|
||||
.map(Into::into)
|
||||
.collect::<Vec<LightData>>();
|
||||
app.set_lights(ModelRc::new(VecModel::from(lights)));
|
||||
|
||||
// Thermometers
|
||||
let thermometers = state
|
||||
.domain("sensor")
|
||||
.iter()
|
||||
.filter(|sensor| sensor.entity_id.ends_with("thermometer_temperature"))
|
||||
.map(Into::into)
|
||||
.collect::<Vec<ThermometerData>>();
|
||||
app.set_thermometers(ModelRc::new(VecModel::from(thermometers)));
|
||||
|
||||
// Clock
|
||||
if let Some(date_time_iso) = state.get("sensor.date_time_iso").as_ref() {
|
||||
app.set_date_time(date_time_iso.into());
|
||||
}
|
||||
|
||||
// Weather
|
||||
if let Some(weather) = state.get("weather.forecast_home").as_ref() {
|
||||
app.set_weather(weather.into());
|
||||
}
|
||||
app.set_is_night(
|
||||
state
|
||||
.get("sun.sun")
|
||||
.is_some_and(|sun| sun.state == "below_horizon"),
|
||||
);
|
||||
|
||||
// Thermostats
|
||||
let thermostats = state
|
||||
.domain("climate")
|
||||
.iter()
|
||||
.filter(|sensor| !sensor.entity_id.contains("diyless"))
|
||||
.map(Into::into)
|
||||
.collect::<Vec<ThermostatData>>();
|
||||
|
||||
app.set_thermostats(ModelRc::new(VecModel::from(thermostats)));
|
||||
})
|
||||
.ok();
|
||||
fn toggle_action(current_state: &str, on_state: &str) -> &'static str {
|
||||
if current_state == on_state {
|
||||
"turn_off"
|
||||
} else {
|
||||
"turn_on"
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
async fn main() -> Result<(), AppError> {
|
||||
dotenvy::dotenv().ok();
|
||||
|
||||
#[cfg(not(feature = "dev"))]
|
||||
@@ -98,37 +119,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let app_weak = app.as_weak();
|
||||
let state = state.clone();
|
||||
async move {
|
||||
loop {
|
||||
if let Some(S2cMessage::EntityUpdated) = s2c_rx.recv().await {
|
||||
apply_state(&app_weak, state.clone());
|
||||
};
|
||||
while let Some(S2cMessage::EntityUpdated) = s2c_rx.recv().await {
|
||||
apply_state(&app_weak, state.clone());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Button Bindings
|
||||
app.on_toggle_light({
|
||||
let state = state.clone();
|
||||
let c2s_tx = c2s_tx.clone();
|
||||
move |entity_id| {
|
||||
let id = entity_id.to_string();
|
||||
|
||||
let payload = Some(json!({
|
||||
"entity_id": id
|
||||
}));
|
||||
|
||||
if let Some(entity) = state.get(&id) {
|
||||
let action = if entity.state == "on" {
|
||||
"turn_off"
|
||||
} else {
|
||||
"turn_on"
|
||||
};
|
||||
c2s_tx
|
||||
.send(C2sMessage::CallService(
|
||||
"light".into(),
|
||||
action.into(),
|
||||
payload.clone(),
|
||||
))
|
||||
.ok();
|
||||
if let Err(err) = c2s_tx.send(C2sMessage::ToggleLight { entity_id: id }) {
|
||||
eprintln!("Failed to send message to server.\n\t{:?}", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -136,56 +140,60 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
app.on_set_temperature({
|
||||
let c2s_tx = c2s_tx.clone();
|
||||
move |entity_id, value| {
|
||||
let payload = Some(json!({
|
||||
"entity_id": entity_id.to_string(),
|
||||
"temperature": value
|
||||
}));
|
||||
|
||||
c2s_tx
|
||||
.send(C2sMessage::CallService(
|
||||
"climate".into(),
|
||||
"set_temperature".into(),
|
||||
payload.clone(),
|
||||
))
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
|
||||
app.on_toggle_thermostat({
|
||||
let state = state.clone();
|
||||
let c2s_tx = c2s_tx.clone();
|
||||
move |entity_id| {
|
||||
let id = entity_id.to_string();
|
||||
|
||||
let payload = Some(json!({
|
||||
"entity_id": id
|
||||
}));
|
||||
|
||||
if let Some(entity) = state.get(&id) {
|
||||
let action = if entity.state == "heat" {
|
||||
"turn_off"
|
||||
} else {
|
||||
"turn_on"
|
||||
};
|
||||
c2s_tx
|
||||
.send(C2sMessage::CallService(
|
||||
"climate".into(),
|
||||
action.into(),
|
||||
payload.clone(),
|
||||
))
|
||||
.ok();
|
||||
if let Err(err) = c2s_tx.send(C2sMessage::SetTemperature {
|
||||
entity_id: entity_id.to_string(),
|
||||
temperature: value,
|
||||
}) {
|
||||
eprintln!("Failed to send message to server.\n\t{:?}", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.run()?;
|
||||
app.on_toggle_thermostat({
|
||||
let c2s_tx = c2s_tx.clone();
|
||||
move |entity_id| {
|
||||
let id = entity_id.to_string();
|
||||
|
||||
Ok(())
|
||||
if let Err(err) = c2s_tx.send(C2sMessage::ToggleThermostat { entity_id: id }) {
|
||||
eprintln!("Failed to send message to server.\n\t{:?}", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.run().map_err(Into::into)
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum AppError {
|
||||
#[error("HASS_URL is not set")]
|
||||
MissingUrl,
|
||||
|
||||
#[error("HASS_TOKEN is not set")]
|
||||
MissingToken,
|
||||
|
||||
#[error("invalid HASS_URL")]
|
||||
InvalidUrl,
|
||||
|
||||
#[error("Unsupported HASS_URL scheme: {0}")]
|
||||
InvalidScheme(String),
|
||||
|
||||
#[error(transparent)]
|
||||
SlintPlatform(#[from] slint::PlatformError),
|
||||
|
||||
#[error(transparent)]
|
||||
HomeAssistant(#[from] Box<hass_rs::HassError>),
|
||||
|
||||
#[error("Entity {0} not found")]
|
||||
NoEntity(String),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum C2sMessage {
|
||||
CallService(String, String, Option<Value>),
|
||||
ToggleLight { entity_id: String },
|
||||
|
||||
ToggleThermostat { entity_id: String },
|
||||
|
||||
SetTemperature { entity_id: String, temperature: f32 },
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -194,36 +202,36 @@ enum S2cMessage {
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
struct State(Arc<RwLock<Vec<HassEntity>>>);
|
||||
struct State(Arc<RwLock<HashMap<String, HassEntity>>>);
|
||||
|
||||
impl State {
|
||||
pub fn update(&self, entity: &HassEntity) {
|
||||
let mut entities = self.0.write().unwrap();
|
||||
|
||||
if let Some(existing) = entities
|
||||
.iter_mut()
|
||||
.find(|e| e.entity_id == entity.entity_id)
|
||||
{
|
||||
*existing = entity.clone();
|
||||
} else {
|
||||
entities.push(entity.clone());
|
||||
}
|
||||
self.0
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(entity.entity_id.clone(), entity.clone());
|
||||
}
|
||||
|
||||
pub fn domain(&self, domain: &str) -> Vec<HassEntity> {
|
||||
let entities = self.0.read().unwrap();
|
||||
|
||||
entities
|
||||
.iter()
|
||||
.filter(|e| e.entity_id.starts_with(domain))
|
||||
let mut entities = entities
|
||||
.values()
|
||||
.filter(|e| {
|
||||
e.entity_id
|
||||
.split_once('.')
|
||||
.is_some_and(|(d, _)| d == domain)
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
entities.sort_by(|a, b| a.entity_id.cmp(&b.entity_id));
|
||||
|
||||
entities
|
||||
}
|
||||
|
||||
pub fn get(&self, entity_id: &str) -> Option<HassEntity> {
|
||||
let entities = self.0.read().unwrap();
|
||||
|
||||
entities.iter().find(|e| e.entity_id == entity_id).cloned()
|
||||
self.0.read().unwrap().get(entity_id).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,50 +239,111 @@ async fn hass(
|
||||
s2c_tx: UnboundedSender<S2cMessage>,
|
||||
mut c2s_rx: UnboundedReceiver<C2sMessage>,
|
||||
state: State,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
) -> Result<(), AppError> {
|
||||
let Ok(url) = var("HASS_URL") else {
|
||||
return Err("NO HASS_URL SET".into());
|
||||
return Err(AppError::MissingUrl);
|
||||
};
|
||||
|
||||
let Ok(hass_token) = var("HASS_TOKEN") else {
|
||||
return Err("NO HASS_TOKEN SET".into());
|
||||
return Err(AppError::MissingToken);
|
||||
};
|
||||
|
||||
let Ok(url) = url::Url::parse(&url) else {
|
||||
return Err("INVALID HASS_URL".into());
|
||||
return Err(AppError::InvalidUrl);
|
||||
};
|
||||
|
||||
let url = format!("ws://{}/api/websocket", url.authority());
|
||||
let scheme = match url.scheme() {
|
||||
"http" => "ws",
|
||||
"https" => "wss",
|
||||
scheme => return Err(AppError::InvalidScheme(scheme.to_string())),
|
||||
};
|
||||
|
||||
let mut client = HassClient::new(&url).await?;
|
||||
client.auth_with_longlivedtoken(&hass_token).await?;
|
||||
let url = format!("{}://{}/api/websocket", scheme, url.authority());
|
||||
|
||||
let states = client.get_states().await?;
|
||||
let mut client = HassClient::new(&url).await.map_err(Box::new)?;
|
||||
client
|
||||
.auth_with_longlivedtoken(&hass_token)
|
||||
.await
|
||||
.map_err(Box::new)?;
|
||||
|
||||
let states = client.get_states().await.map_err(Box::new)?;
|
||||
|
||||
states.iter().for_each(|entity| {
|
||||
state.update(entity);
|
||||
});
|
||||
|
||||
let mut event_receiver = client.subscribe_event("state_changed").await?;
|
||||
tokio::spawn(async move {
|
||||
while let Some(message) = event_receiver.recv().await {
|
||||
if let Some(entity) = &message.event.data.new_state {
|
||||
state.update(entity);
|
||||
s2c_tx.send(S2cMessage::EntityUpdated).ok();
|
||||
let mut event_receiver = client
|
||||
.subscribe_event("state_changed")
|
||||
.await
|
||||
.map_err(Box::new)?;
|
||||
|
||||
tokio::spawn({
|
||||
let state = state.clone();
|
||||
async move {
|
||||
while let Some(message) = event_receiver.recv().await {
|
||||
if let Some(entity) = &message.event.data.new_state {
|
||||
state.update(entity);
|
||||
if let Err(err) = s2c_tx.send(S2cMessage::EntityUpdated) {
|
||||
eprintln!("Failed to send message to client.\n\t{:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
if let Some(C2sMessage::CallService(domain, service, service_data)) =
|
||||
c2s_rx.recv().await
|
||||
{
|
||||
client
|
||||
.call_service(domain, service, service_data)
|
||||
.await
|
||||
.unwrap();
|
||||
};
|
||||
tokio::spawn({
|
||||
let state = state.clone();
|
||||
async move {
|
||||
while let Some(message) = c2s_rx.recv().await {
|
||||
if let Err(err) = match message {
|
||||
C2sMessage::ToggleLight { entity_id } => {
|
||||
if let Some(entity) = state.get(&entity_id) {
|
||||
let payload = json!({
|
||||
"entity_id": entity_id
|
||||
});
|
||||
let service = toggle_action(&entity.state, "on");
|
||||
client
|
||||
.call_service("light".into(), service.into(), Some(payload))
|
||||
.await
|
||||
.map_err(Box::new)
|
||||
.map_err(Into::into)
|
||||
} else {
|
||||
Err(AppError::NoEntity(entity_id))
|
||||
}
|
||||
}
|
||||
C2sMessage::ToggleThermostat { entity_id } => {
|
||||
if let Some(entity) = state.get(&entity_id) {
|
||||
let payload = json!({
|
||||
"entity_id": entity_id
|
||||
});
|
||||
let service = toggle_action(&entity.state, "heat");
|
||||
client
|
||||
.call_service("climate".into(), service.into(), Some(payload))
|
||||
.await
|
||||
.map_err(Box::new)
|
||||
.map_err(Into::into)
|
||||
} else {
|
||||
Err(AppError::NoEntity(entity_id))
|
||||
}
|
||||
}
|
||||
C2sMessage::SetTemperature {
|
||||
entity_id,
|
||||
temperature,
|
||||
} => {
|
||||
let payload = json!({
|
||||
"entity_id": entity_id,
|
||||
"temperature": temperature
|
||||
});
|
||||
client
|
||||
.call_service("climate".into(), "set_temperature".into(), Some(payload))
|
||||
.await
|
||||
.map_err(Box::new)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
} {
|
||||
eprintln!("Failed to execute Service Call.\n\t{:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -96,9 +96,11 @@ export component Climate inherits Rectangle {
|
||||
}
|
||||
|
||||
for thermostat[index] in thermostats: ListTile {
|
||||
avatar_icon: Icons.thermostat;
|
||||
text: thermostat.name;
|
||||
supporting-text: thermostat.available ? thermostat.state : "UNAVAILABLE";
|
||||
height: 72px;
|
||||
enabled: false;
|
||||
|
||||
ElevatedButton {
|
||||
icon: Icons.remove;
|
||||
@@ -111,7 +113,7 @@ export component Climate inherits Rectangle {
|
||||
HorizontalLayout {
|
||||
Text {
|
||||
text: thermostat.target;
|
||||
color: MaterialPalette.primary;
|
||||
// color: MaterialPalette.primary;
|
||||
font-size: 40px;
|
||||
vertical-alignment: center;
|
||||
horizontal-alignment: right;
|
||||
@@ -123,7 +125,7 @@ export component Climate inherits Rectangle {
|
||||
padding-top: 2px;
|
||||
Text {
|
||||
text: thermostat.unit;
|
||||
color: MaterialPalette.primary;
|
||||
// color: MaterialPalette.primary;
|
||||
font-size: 14px;
|
||||
horizontal-alignment: left;
|
||||
vertical-alignment: top;
|
||||
@@ -133,7 +135,7 @@ export component Climate inherits Rectangle {
|
||||
|
||||
Text {
|
||||
text: ".\{thermostat.remainder}";
|
||||
color: MaterialPalette.primary;
|
||||
// color: MaterialPalette.primary;
|
||||
font-size: 14px;
|
||||
horizontal-alignment: left;
|
||||
vertical-alignment: bottom;
|
||||
|
||||
@@ -61,7 +61,7 @@ export component ListTile {
|
||||
|
||||
if root.avatar_text != "": MaterialText {
|
||||
text: root.avatar_text;
|
||||
vertical_alignment: center;
|
||||
vertical_alignment: bottom;
|
||||
horizontal_alignment: center;
|
||||
color: root.avatar_foreground;
|
||||
style: MaterialTypography.title_medium;
|
||||
@@ -100,7 +100,7 @@ export component ListTile {
|
||||
states [
|
||||
disabled when !root.enabled: {
|
||||
state_layer.display_background: false;
|
||||
title_layout.opacity: 38%;
|
||||
// title_layout.opacity: 38%;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user