1786380840

This commit is contained in:
2026-08-10 18:54:00 +02:00
parent 212479861b
commit b7fce915ca
5 changed files with 263 additions and 260 deletions

View File

@@ -297,7 +297,7 @@ impl HaClient {
}
}
upcoming.sort_by_key(|event| event.sort_epoch);
upcoming.truncate(7);
upcoming.truncate(6);
Ok(upcoming)
}
}

View File

@@ -9,44 +9,8 @@ import {
} 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;
VerticalLayout {
alignment: center;
horizontal_stretch: 1;
MaterialText {
text: event.time;
horizontal-alignment: right;
color: MaterialPalette.on_surface;
overflow: elide;
style: MaterialTypography.title_medium;
}
MaterialText {
text: "";
horizontal-alignment: right;
overflow: elide;
style: MaterialTypography.body_medium;
}
}
}
Rectangle { }
}
Rectangle { }
}

View File

@@ -1,198 +0,0 @@
import { Palette, HorizontalBox, VerticalBox } from "std-widgets.slint";
import { Icons } from "material/ui/icons/icons.slint";
import {
ListTile,
Vertical,
ElevatedButton,
SegmentedButton,
MaterialPalette,
FilledButton,
MaterialText,
MaterialTypography,
MaterialStyleMetrics,
} from "material/material.slint";
export struct WeatherData {
icon-index: int,
label: string,
temperature: float,
temperature_unit: string,
}
export struct ThermostatData {
id: string,
name: string,
current: float,
temperature: float,
unit: string,
target: int,
remainder: int,
state: string,
available: bool,
}
export component Climate inherits Rectangle {
in property <WeatherData> weather;
in property <[ThermostatData]> thermostats;
in property <bool> is-night;
in property <string> next-event-date;
in property <string> next-event-time;
in property <string> next-event-summary;
property <int> icon-index: weather.icon-index == 1 && is-night ? 2 : weather.icon-index;
callback set_temperature(string, float);
callback toggle_thermostat(string);
property <[image]> icons: [
@image-url("icons/sun.svg"),
@image-url("icons/cloud-sun.svg"),
@image-url("icons/cloud-moon.svg"),
@image-url("icons/cloud.svg"),
@image-url("icons/cloud-rain.svg"),
@image-url("icons/cloud-rain-wind.svg"),
@image-url("icons/cloud-lightning.svg"),
@image-url("icons/snowflake.svg"),
@image-url("icons/cloud-fog.svg"),
@image-url("icons/moon.svg"),
];
VerticalLayout {
spacing: MaterialStyleMetrics.spacing_16;
HorizontalLayout {
Vertical {
ListTile {
avatar_icon: Icons.event_upcoming;
avatar_foreground: Palette.foreground;
text: next-event-date;
supporting-text: next-event-summary;
height: 72px;
enabled: false;
VerticalLayout {
alignment: center;
horizontal_stretch: 1;
MaterialText {
text: next-event-time;
color: MaterialPalette.on_surface;
overflow: elide;
horizontal-alignment: right;
style: MaterialTypography.title_medium;
}
MaterialText {
text: "";
overflow: elide;
horizontal-alignment: right;
style: MaterialTypography.body_medium;
}
}
}
}
Vertical {
Text {
text: "\{weather.temperature}\{weather.temperature-unit}";
font-size: 36px;
font-weight: 700;
vertical-alignment: center;
horizontal-alignment: right;
}
Text {
text: weather.label;
font-size: 24px;
font-weight: 500;
vertical-alignment: center;
horizontal-alignment: right;
}
}
Image {
colorize: Palette.foreground;
width: 120px;
height: 105px;
source: icons[icon-index];
image-fit: contain;
}
}
for thermostat[index] in thermostats: ListTile {
avatar_icon: Icons.thermostat;
avatar_foreground: Palette.foreground;
text: thermostat.name;
supporting-text: thermostat.available ? thermostat.state : "UNAVAILABLE";
height: 72px;
enabled: false;
ElevatedButton {
icon: Icons.remove;
clicked => { root.set_temperature(thermostat.id, thermostat.temperature - 0.5) }
}
Rectangle {
width: 80px;
VerticalLayout {
HorizontalLayout {
Text {
text: thermostat.target;
font-size: 40px;
vertical-alignment: center;
horizontal-alignment: right;
font-weight: 700;
height: 40px;
}
VerticalLayout {
padding-top: 2px;
Text {
text: thermostat.unit;
font-size: 14px;
horizontal-alignment: left;
vertical-alignment: top;
font-weight: 500;
height: 18px;
}
Text {
text: ".\{thermostat.remainder}";
// color: MaterialPalette.primary;
font-size: 14px;
horizontal-alignment: left;
vertical-alignment: bottom;
font-weight: 500;
height: 18px;
}
}
}
Text {
text: "\{thermostat.current} \{thermostat.unit}";
color: MaterialPalette.secondary;
font-size: 12px;
vertical-alignment: top;
horizontal-alignment: center;
font-weight: 500;
}
}
}
ElevatedButton {
icon: Icons.add;
clicked => { root.set_temperature(thermostat.id, thermostat.temperature + 0.5) }
}
if thermostat.state == "OFF": ElevatedButton {
icon: Icons.mode_heat_off;
clicked => { root.toggle_thermostat(thermostat.id) }
}
if thermostat.state == "HEAT": FilledButton {
icon: Icons.mode_heat;
clicked => { root.toggle_thermostat(thermostat.id) }
}
}
Rectangle { }
}
}

View File

@@ -8,8 +8,14 @@ import {
StandardTableView,
} from "std-widgets.slint";
import { Climate, WeatherData, ThermostatData } from "./climate.slint";
import { CalendarEventData } from "./calendar.slint";
import {
CalendarEventData,
MainPage,
WeatherData,
ThermostatData,
} from "./main_page.slint";
// import { } from "./calendar.slint";
import { Calendar } from "calendar.slint";
import { Energy } from "energy.slint";
@@ -181,10 +187,8 @@ export component AppWindow inherits Window {
Rectangle {
width: (root.width / 4) * 3;
VerticalLayout {
if tab-bar.current-index == 0: Climate {
next-event-date: next-event-date;
next-event-time: next-event-time;
next-event-summary: next-event-summary;
MainPage {
calendar-events: calendar-events;
thermostats: thermostats;
is-night: is-night;
@@ -193,24 +197,16 @@ export component AppWindow inherits Window {
toggle_thermostat(entity_id) => { toggle_thermostat(entity_id) }
}
if tab-bar.current-index == 1: Calendar {
calendar-events: calendar-events;
}
if tab-bar.current-index == 2: Energy {
render-plot(iteration) => render-plot(iteration);
}
Rectangle {
tab-bar := TabBar {
width: parent.width;
items: [
{ icon: Icons.wb_sunny, text: "Climate" },
{ icon: Icons.calendar_today, text: "Calendar" },
{ icon: Icons.energy_savings_leaf, text: "Energy" }
];
}
}
// Rectangle {
// tab-bar := TabBar {
// width: parent.width;
// items: [
// { icon: Icons.wb_sunny, text: "Climate" },
// { icon: Icons.calendar_today, text: "Calendar" },
// { icon: Icons.energy_savings_leaf, text: "Energy" }
// ];
// }
// }
}
}

View File

@@ -0,0 +1,241 @@
import { Palette, HorizontalBox, VerticalBox } from "std-widgets.slint";
import { Icons } from "material/ui/icons/icons.slint";
import {
ListTile,
Vertical,
ElevatedButton,
SegmentedButton,
MaterialPalette,
FilledButton,
MaterialText,
MaterialTypography,
MaterialStyleMetrics,
} from "material/material.slint";
export struct WeatherData {
icon-index: int,
label: string,
temperature: float,
temperature_unit: string,
}
export struct ThermostatData {
id: string,
name: string,
current: float,
temperature: float,
unit: string,
target: int,
remainder: int,
state: string,
available: bool,
}
export struct CalendarEventData {
id: string,
date: string,
time: string,
summary: string,
calendar: string,
}
export component MainPage inherits Rectangle {
in property <WeatherData> weather;
in property <[ThermostatData]> thermostats;
in property <bool> is-night;
in property <[CalendarEventData]> calendar-events;
// in property <string> next-event-date;
// in property <string> next-event-time;
// in property <string> next-event-summary;
property <int> icon-index: weather.icon-index == 1 && is-night ? 2 : weather.icon-index;
callback set_temperature(string, float);
callback toggle_thermostat(string);
property <[image]> icons: [
@image-url("icons/sun.svg"),
@image-url("icons/cloud-sun.svg"),
@image-url("icons/cloud-moon.svg"),
@image-url("icons/cloud.svg"),
@image-url("icons/cloud-rain.svg"),
@image-url("icons/cloud-rain-wind.svg"),
@image-url("icons/cloud-lightning.svg"),
@image-url("icons/snowflake.svg"),
@image-url("icons/cloud-fog.svg"),
@image-url("icons/moon.svg"),
];
VerticalLayout {
spacing: MaterialStyleMetrics.spacing_16;
HorizontalLayout {
// Vertical {
// ListTile {
// avatar_icon: Icons.event_upcoming;
// avatar_foreground: Palette.foreground;
// text: next-event-date;
// supporting-text: next-event-summary;
// height: 72px;
// enabled: false;
// VerticalLayout {
// alignment: center;
// horizontal_stretch: 1;
// MaterialText {
// text: next-event-time;
// color: MaterialPalette.on_surface;
// overflow: elide;
// horizontal-alignment: right;
// style: MaterialTypography.title_medium;
// }
// MaterialText {
// text: "";
// overflow: elide;
// horizontal-alignment: right;
// style: MaterialTypography.body_medium;
// }
// }
// }
// }
Vertical {
for thermostat[index] in thermostats: ListTile {
avatar_icon: Icons.thermostat;
avatar_foreground: Palette.foreground;
// text: thermostat.name;
// supporting-text: thermostat.available ? thermostat.state : "UNAVAILABLE";
height: 72px;
enabled: false;
ElevatedButton {
icon: Icons.remove;
enabled: thermostat.available;
clicked => { root.set_temperature(thermostat.id, thermostat.temperature - 0.5) }
}
Rectangle {
width: 80px;
VerticalLayout {
HorizontalLayout {
Text {
text: thermostat.target;
font-size: 40px;
vertical-alignment: center;
horizontal-alignment: right;
font-weight: 700;
height: 40px;
}
VerticalLayout {
padding-top: 2px;
Text {
text: thermostat.unit;
font-size: 14px;
horizontal-alignment: left;
vertical-alignment: top;
font-weight: 500;
height: 18px;
}
Text {
text: ".\{thermostat.remainder}";
// color: MaterialPalette.primary;
font-size: 14px;
horizontal-alignment: left;
vertical-alignment: bottom;
font-weight: 500;
height: 18px;
}
}
}
Text {
text: "\{thermostat.current} \{thermostat.unit}";
color: MaterialPalette.secondary;
font-size: 12px;
vertical-alignment: top;
horizontal-alignment: center;
font-weight: 500;
}
}
}
ElevatedButton {
icon: Icons.add;
enabled: thermostat.available;
clicked => { root.set_temperature(thermostat.id, thermostat.temperature + 0.5) }
}
if thermostat.state == "OFF": ElevatedButton {
icon: Icons.mode_heat_off;
enabled: thermostat.available;
clicked => { root.toggle_thermostat(thermostat.id) }
}
if thermostat.state == "HEAT": FilledButton {
icon: Icons.mode_heat;
clicked => { root.toggle_thermostat(thermostat.id) }
}
}
}
Vertical {
Text {
text: "\{weather.temperature}\{weather.temperature-unit}";
font-size: 36px;
font-weight: 700;
vertical-alignment: center;
horizontal-alignment: right;
}
Text {
text: weather.label;
font-size: 24px;
font-weight: 500;
vertical-alignment: center;
horizontal-alignment: right;
}
}
Image {
colorize: Palette.foreground;
width: 120px;
height: 105px;
source: icons[icon-index];
image-fit: contain;
}
}
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;
VerticalLayout {
alignment: center;
horizontal_stretch: 1;
MaterialText {
text: event.time;
horizontal-alignment: right;
color: MaterialPalette.on_surface;
overflow: elide;
style: MaterialTypography.title_medium;
}
MaterialText {
text: "";
horizontal-alignment: right;
overflow: elide;
style: MaterialTypography.body_medium;
}
}
}
Rectangle { }
}
}
}