diff --git a/Cargo.lock b/Cargo.lock index 0c1575d..d1792a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,6 +346,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "bincode" version = "2.0.1" @@ -684,6 +690,35 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cookie" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +dependencies = [ + "cookie", + "document-features", + "idna", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "copypasta" version = "0.10.2" @@ -849,12 +884,14 @@ dependencies = [ "dotenvy", "hass-rs", "paste", + "serde", "serde_json", "slint", "slint-build", "thiserror 2.0.20", "tokio", "trekstor", + "ureq", "url", ] @@ -870,6 +907,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + [[package]] name = "derive_more" version = "2.1.1" @@ -950,6 +993,15 @@ dependencies = [ "libloading", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -1445,6 +1497,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -2513,6 +2576,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -2788,6 +2857,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-derive" version = "0.4.2" @@ -3459,6 +3534,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -3841,6 +3922,20 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rowan" version = "0.16.1" @@ -3922,6 +4017,41 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -4352,6 +4482,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "svgtypes" version = "0.16.1" @@ -4521,6 +4657,36 @@ dependencies = [ "zune-jpeg", ] +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tiny-skia" version = "0.12.0" @@ -4855,12 +5021,49 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "unty" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" +[[package]] +name = "ureq" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d" +dependencies = [ + "base64 0.23.1", + "cookie_store", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613" +dependencies = [ + "base64 0.23.1", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -4879,7 +5082,7 @@ version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d46cf96c5f498d36b7a9693bc6a7075c0bb9303189d61b2249b0dc3d309c07de" dependencies = [ - "base64", + "base64 0.22.1", "data-url", "flate2", "fontdb", @@ -4901,6 +5104,12 @@ dependencies = [ "xmlwriter", ] +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -5074,6 +5283,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weezl" version = "0.1.12" @@ -5694,6 +5912,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + [[package]] name = "zerotrie" version = "0.2.4" diff --git a/dashboard/Cargo.toml b/dashboard/Cargo.toml index 85e7635..7326ae7 100644 --- a/dashboard/Cargo.toml +++ b/dashboard/Cargo.toml @@ -14,6 +14,8 @@ url = "2.5.8" chrono = "0.4.45" thiserror = "2.0.20" paste = "1.0.15" +ureq = { version = "3.4.0", default-features = false, features = ["json", "rustls"] } +serde = { version = "1.0.229", features = ["derive"] } [features] dev = ["slint/backend-winit-x11"] diff --git a/dashboard/src/error.rs b/dashboard/src/error.rs index ecde20b..df15fa7 100644 --- a/dashboard/src/error.rs +++ b/dashboard/src/error.rs @@ -1,4 +1,3 @@ -#[allow(clippy::all)] #[derive(Debug, thiserror::Error)] pub enum AppError { #[error("HASS_URL is not set")] diff --git a/dashboard/src/ha_ext.rs b/dashboard/src/ha_ext.rs index 29ae811..b48b343 100644 --- a/dashboard/src/ha_ext.rs +++ b/dashboard/src/ha_ext.rs @@ -44,7 +44,7 @@ impl HassEntityExt for HassEntity { impl From<&HassEntity> for LightData { fn from(value: &HassEntity) -> Self { Self { - id: value.entity_id.clone().into(), + id: value.entity_id.to_shared_string(), name: value .friendly_name() .replace(" Lights", "") @@ -61,7 +61,7 @@ impl From<&HassEntity> for ThermometerData { let v: f32 = value.state.parse().unwrap_or_default(); Self { - id: value.entity_id.clone().into(), + id: value.entity_id.to_shared_string(), name: value.friendly_name().into(), value: format!("{:.1}", v).into(), unit: value.unit_of_measurement().into(), @@ -71,21 +71,19 @@ impl From<&HassEntity> for ThermometerData { impl From<&HassEntity> for DateTimeData { fn from(value: &HassEntity) -> Self { + let mut data = Self { + id: value.entity_id.to_shared_string(), + ..Default::default() + }; + if let Ok(datetime) = chrono::NaiveDateTime::parse_from_str(&value.state, "%Y-%m-%dT%H:%M:%S") { - return Self { - id: value.entity_id.clone().into(), - date: datetime.format("%a %m %b").to_shared_string(), - time: datetime.format("%H:%M").to_shared_string(), - }; + data.date = datetime.format("%a %m %b").to_shared_string(); + data.time = datetime.format("%H:%M").to_shared_string(); } - Self { - id: value.entity_id.clone().into(), - date: "0-0-0000".into(), - time: "00:00".into(), - } + data } } @@ -139,64 +137,6 @@ impl From<&HassEntity> for WeatherData { let label = pretty_label(&value.state).to_shared_string(); - let attribution = value - .attributes - .get("attribution") - .and_then(|x| x.as_str()) - .unwrap_or_default() - .to_shared_string(); - - let cloud_coverage = value - .attributes - .get("cloud_coverage") - .and_then(|x| x.as_i64()) - .unwrap_or_default() as i32; - - let dew_point = value - .attributes - .get("dew_point") - .and_then(|x| x.as_f64()) - .unwrap_or_default() as f32; - - let friendly_name = value - .attributes - .get("friendly_name") - .and_then(|x| x.as_str()) - .unwrap_or_default() - .to_shared_string(); - - let humidity = value - .attributes - .get("humidity") - .and_then(|x| x.as_i64()) - .unwrap_or_default() as i32; - - let precipitation_unit = value - .attributes - .get("precipitation_unit") - .and_then(|x| x.as_str()) - .unwrap_or_default() - .to_shared_string(); - - let pressure = value - .attributes - .get("pressure") - .and_then(|x| x.as_f64()) - .unwrap_or_default() as f32; - - let pressure_unit = value - .attributes - .get("pressure_unit") - .and_then(|x| x.as_str()) - .unwrap_or_default() - .to_shared_string(); - - let supported_features = value - .attributes - .get("supported_features") - .and_then(|x| x.as_i64()) - .unwrap_or_default() as i32; - let temperature = value .attributes .get("temperature") @@ -210,57 +150,24 @@ impl From<&HassEntity> for WeatherData { .unwrap_or_default() .to_shared_string(); - let uv_index = value - .attributes - .get("uv_index") - .and_then(|x| x.as_f64()) - .unwrap_or_default() as f32; - - let visibility_unit = value - .attributes - .get("visibility_unit") - .and_then(|x| x.as_str()) - .unwrap_or_default() - .to_shared_string(); - - let wind_bearing = value - .attributes - .get("wind_bearing") - .and_then(|x| x.as_f64()) - .unwrap_or_default() as f32; - - let wind_speed = value - .attributes - .get("wind_speed") - .and_then(|x| x.as_f64()) - .unwrap_or_default() as f32; - - let wind_speed_unit = value - .attributes - .get("wind_speed_unit") - .and_then(|x| x.as_str()) - .unwrap_or_default() - .to_shared_string(); - Self { icon_index, label, - attribution, - cloud_coverage, - dew_point, - friendly_name, - humidity, - precipitation_unit, - pressure, - pressure_unit, - supported_features, temperature, temperature_unit, - uv_index, - visibility_unit, - wind_bearing, - wind_speed, - wind_speed_unit, + } + } +} + +// weird spot for this but okay +impl From<&crate::home_assistant::CalendarEvent> for CalendarEventData { + fn from(value: &crate::home_assistant::CalendarEvent) -> Self { + Self { + calendar: value.calendar.to_shared_string(), + date: value.date.to_shared_string(), + id: value.entity_id.to_shared_string(), + summary: value.summary.to_shared_string(), + time: value.time.to_shared_string(), } } } diff --git a/dashboard/src/home_assistant.rs b/dashboard/src/home_assistant.rs index 0fa6270..d9770b2 100644 --- a/dashboard/src/home_assistant.rs +++ b/dashboard/src/home_assistant.rs @@ -1,8 +1,13 @@ +use chrono::{DateTime, Local, NaiveDate}; use dotenvy::var; use hass_rs::{HassClient, HassEntity}; +use serde::Deserialize; use serde_json::json; -use std::collections::HashMap; -use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; +use std::{collections::HashMap, time::Duration}; +use tokio::{ + sync::mpsc::{UnboundedReceiver, UnboundedSender}, + time::sleep, +}; use crate::{ error::AppError, @@ -114,6 +119,18 @@ pub async fn hass( initial_state.sort_by(|a, b| a.entity_id.cmp(&b.entity_id)); s2c_tx.send(EventMessage::InitialState(initial_state)).ok(); + tokio::spawn({ + let s2c_tx = s2c_tx.clone(); + async move { + loop { + let ha_client = HaClient::from_environment().unwrap(); + let events = ha_client.fetch_calendar_events().unwrap(); + s2c_tx.send(EventMessage::CalendarUpdated(events)).ok(); + sleep(Duration::from_secs(30)).await; + } + } + }); + let mut event_receiver = client .subscribe_event("state_changed") .await @@ -162,3 +179,143 @@ fn toggle_action(current_state: &str, on_state: &str) -> &'static str { "turn_on" } } + +const REQUEST_TIMEOUT: Duration = Duration::from_secs(12); + +#[derive(Clone)] +pub struct HaClient { + pub agent: ureq::Agent, + pub base_url: String, + pub token: String, +} + +#[derive(Deserialize)] +pub struct HaCalendar { + pub entity_id: String, + pub name: String, +} + +#[derive(Deserialize)] +pub struct HaCalendarEvent { + pub uid: String, + pub summary: String, + pub start: HaCalendarTime, +} + +#[derive(Deserialize)] +pub struct HaCalendarTime { + #[serde(rename = "dateTime")] + pub date_time: Option, + pub date: Option, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CalendarEvent { + pub entity_id: String, + pub summary: String, + pub date: String, + pub time: String, + pub calendar: String, + pub sort_epoch: i64, +} + +impl HaClient { + pub fn from_environment() -> Result { + let base_url = std::env::var("HASS_URL") + .map_err(|_| "HASS_URL is not configured".to_string())? + .trim_end_matches('/') + .to_string(); + let token = + std::env::var("HASS_TOKEN").map_err(|_| "HASS_TOKEN is not configured".to_string())?; + let agent = ureq::Agent::new_with_config( + ureq::config::Config::builder() + .timeout_send_request(Some(REQUEST_TIMEOUT)) + .timeout_recv_response(Some(REQUEST_TIMEOUT)) + .timeout_send_body(Some(REQUEST_TIMEOUT)) + .build(), + ); + Ok(Self { + agent, + base_url, + token, + }) + } + + fn get_calendars(&self) -> Result, String> { + self.agent + .get(&format!("{}/api/calendars", self.base_url)) + .header("Authorization", &format!("Bearer {}", self.token)) + .header("Accept", "application/json") + .call() + .map_err(|error| format!("calendars: {error}"))? + .body_mut() + .read_json() + .map_err(|error| format!("calendars: invalid response: {error}")) + } + + fn get_calendar_events( + &self, + entity_id: &str, + start: &str, + end: &str, + ) -> Result, String> { + self.agent + .get(&format!("{}/api/calendars/{entity_id}", self.base_url)) + .query("start", start) + .query("end", end) + .header("Authorization", &format!("Bearer {}", self.token)) + .header("Accept", "application/json") + .call() + .map_err(|error| format!("{entity_id}: {error}"))? + .body_mut() + .read_json() + .map_err(|error| format!("{entity_id}: invalid response: {error}")) + } + + pub fn fetch_calendar_events(&self) -> Result, String> { + let now = Local::now(); + let start = now.to_rfc3339(); + let end = (now + chrono::Duration::days(30)).to_rfc3339(); + let mut upcoming = Vec::new(); + + for calendar in self.get_calendars()? { + let Ok(events) = self.get_calendar_events(&calendar.entity_id, &start, &end) else { + continue; + }; + for event in events { + let Some((sort_epoch, date, time)) = format_calendar_time(&event.start) else { + continue; + }; + upcoming.push(CalendarEvent { + entity_id: event.uid, + summary: event.summary, + date, + time, + calendar: calendar.name.clone(), + sort_epoch, + }); + } + } + upcoming.sort_by_key(|event| event.sort_epoch); + upcoming.truncate(7); + Ok(upcoming) + } +} + +pub fn format_calendar_time(value: &HaCalendarTime) -> Option<(i64, String, String)> { + if let Some(date_time) = value.date_time.as_deref() { + let parsed = DateTime::parse_from_rfc3339(date_time).ok()?; + return Some(( + parsed.timestamp(), + parsed.format("%a %d %b").to_string().to_uppercase(), + parsed.format("%H:%M").to_string().to_uppercase(), + )); + } + let date = NaiveDate::parse_from_str(value.date.as_deref()?, "%Y-%m-%d").ok()?; + let sort_epoch = date.and_hms_opt(0, 0, 0)?.and_utc().timestamp(); + Some(( + sort_epoch, + date.format("%a %d %b").to_string().to_uppercase(), + "ALL DAY".into(), + )) +} diff --git a/dashboard/src/messages.rs b/dashboard/src/messages.rs index 306ccbc..a1eb2da 100644 --- a/dashboard/src/messages.rs +++ b/dashboard/src/messages.rs @@ -1,5 +1,7 @@ use hass_rs::HassEntity; +use crate::home_assistant::CalendarEvent; + #[derive(Clone)] pub enum CommandMessage { ToggleLight { entity_id: String }, @@ -14,4 +16,5 @@ pub enum EventMessage { InitialState(Vec), EntityUpdated(HassEntity), EntityRemoved(HassEntity), + CalendarUpdated(Vec), } diff --git a/dashboard/src/ui.rs b/dashboard/src/ui.rs index 1850e80..3ecddc1 100644 --- a/dashboard/src/ui.rs +++ b/dashboard/src/ui.rs @@ -2,6 +2,7 @@ slint::include_modules!(); use crate::{ ha_ext::*, + home_assistant::CalendarEvent, messages::{CommandMessage, EventMessage}, }; @@ -96,6 +97,18 @@ pub fn apply_thermostats(app: &AppWindow, entities: &[HassEntity]) { }); } +pub fn apply_calendars(app: &Weak, entities: Vec) { + if let Err(err) = app.upgrade_in_event_loop(move |app| { + let mut entities = entities.clone(); + entities.sort_by_key(|a| a.sort_epoch); + + let entities = entities.iter().map(Into::into).collect::>(); + app.set_calendar_events(ModelRc::new(VecModel::::from(entities))); + }) { + eprintln!("Failed to upgrade in event loop.\n\t{:?}", err); + } +} + pub fn apply_state(app: &Weak, entities: Vec) { if let Err(err) = app.upgrade_in_event_loop(move |app| { apply_theme(&app, &entities); @@ -161,6 +174,9 @@ pub async fn update_listener( EventMessage::EntityRemoved(hass_entity) => { apply_state(&app_weak, vec![hass_entity]); } + EventMessage::CalendarUpdated(events) => { + apply_calendars(&app_weak, events); + } } } } diff --git a/dashboard/ui/calendar.slint b/dashboard/ui/calendar.slint index 31d883b..0771dcd 100644 --- a/dashboard/ui/calendar.slint +++ b/dashboard/ui/calendar.slint @@ -1,7 +1,41 @@ -import { FilledButton, SnackBar } from "material/material.slint"; +import { Palette } from "std-widgets.slint"; +import { + FilledButton, + SnackBar, + ListTile, + MaterialText, + MaterialPalette, + MaterialTypography, +} from "material/material.slint"; +import { Icons } from "material/ui/icons/icons.slint"; + +export struct CalendarEventData { + id: string, + date: string, + time: string, + summary: string, + calendar: string, +} + export component Calendar inherits Rectangle { + in property <[CalendarEventData]> calendar-events; + VerticalLayout { - // .. + for event[index] in calendar-events: ListTile { + avatar_icon: Icons.calendar_today; + avatar_foreground: Palette.foreground; + text: event.date; + supporting-text: event.summary; + height: 72px; + + MaterialText { + text: event.time; + color: MaterialPalette.on_surface; + overflow: elide; + style: MaterialTypography.title_medium; + } + } + Rectangle { } } } diff --git a/dashboard/ui/climate.slint b/dashboard/ui/climate.slint index 8006330..e2517ee 100644 --- a/dashboard/ui/climate.slint +++ b/dashboard/ui/climate.slint @@ -1,4 +1,5 @@ import { Palette, HorizontalBox, VerticalBox } from "std-widgets.slint"; +import { Icons } from "material/ui/icons/icons.slint"; import { ListTile, Vertical, @@ -7,26 +8,12 @@ import { MaterialPalette, FilledButton, } from "material/material.slint"; -import { Icons } from "material/ui/icons/icons.slint"; + export struct WeatherData { icon-index: int, label: string, temperature: float, - dew_point: float, temperature_unit: string, - humidity: int, - cloud_coverage: int, - uv_index: float, - pressure: float, - pressure_unit: string, - wind_bearing: float, - wind_speed: float, - wind_speed_unit: string, - visibility_unit: string, - precipitation_unit: string, - attribution: string, - friendly_name: string, - supported_features: int, } export struct ThermostatData { @@ -114,7 +101,6 @@ export component Climate inherits Rectangle { HorizontalLayout { Text { text: thermostat.target; - // color: MaterialPalette.primary; font-size: 40px; vertical-alignment: center; horizontal-alignment: right; @@ -126,7 +112,6 @@ export component Climate inherits Rectangle { padding-top: 2px; Text { text: thermostat.unit; - // color: MaterialPalette.primary; font-size: 14px; horizontal-alignment: left; vertical-alignment: top; diff --git a/dashboard/ui/main.slint b/dashboard/ui/main.slint index b81ed21..879867c 100644 --- a/dashboard/ui/main.slint +++ b/dashboard/ui/main.slint @@ -9,6 +9,8 @@ import { } from "std-widgets.slint"; import { Climate, WeatherData, ThermostatData } from "./climate.slint"; +import { CalendarEventData } from "./calendar.slint"; + import { Calendar } from "calendar.slint"; import { Energy } from "energy.slint"; import { @@ -132,6 +134,8 @@ export component AppWindow inherits Window { callback set_temperature(string, float); callback toggle_thermostat(string); + in property <[CalendarEventData]> calendar-events; + in property <[ThermometerData]> thermometers; in property <[LightData]> lights; @@ -177,7 +181,11 @@ export component AppWindow inherits Window { set_temperature(entity_id, value) => { root.set_temperature(entity_id, value); } toggle_thermostat(entity_id) => { toggle_thermostat(entity_id) } } - if tab-bar.current-index == 1: Calendar { } + + if tab-bar.current-index == 1: Calendar { + calendar-events: calendar-events; + } + if tab-bar.current-index == 2: Energy { } Rectangle {