Read current weather information from home assistant
Diff
Cargo.lock | 29 +++++++++++++++++++++++++++++
shalom/Cargo.toml | 1 +
assets/icons/README.md | 1 +
assets/icons/clear-day.svg | 1 +
assets/icons/clear-night.svg | 1 +
assets/icons/cloud.svg | 1 +
assets/icons/extreme-rain.svg | 1 +
assets/icons/fog.svg | 1 +
assets/icons/hail.svg | 1 +
assets/icons/partly-cloudy-day.svg | 1 +
assets/icons/partly-cloudy-night.svg | 1 +
assets/icons/rain.svg | 1 +
assets/icons/snow.svg | 1 +
assets/icons/thunderstorms-rain.svg | 1 +
assets/icons/thunderstorms.svg | 1 +
assets/icons/wind.svg | 1 +
shalom/src/hass_client.rs | 301 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
shalom/src/oracle.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
shalom/src/theme.rs | 50 ++++++++++++++++++++++++++++++++++++++++----------
shalom/src/pages/omni.rs | 22 ++++++++++++----------
shalom/src/widgets/mod.rs | 1 +
shalom/src/widgets/cards/mod.rs | 1 +
shalom/src/widgets/cards/weather.rs | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
23 files changed, 544 insertions(+), 105 deletions(-)
@@ -2326,6 +2326,12 @@
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustversion"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]]
name = "rustybuzz"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2452,6 +2458,7 @@
"once_cell",
"serde",
"serde_json",
"strum",
"time",
"tokio",
"tokio-tungstenite",
@@ -2615,6 +2622,28 @@
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
dependencies = [
"float-cmp",
]
[[package]]
name = "strum"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.38",
]
[[package]]
@@ -14,6 +14,7 @@
keyframe = "1.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["raw_value"] }
strum = { version = "0.25", features = ["derive"] }
tokio = { version = "1.33", features = ["net", "sync", "rt", "macros", "time", "fs"] }
tokio-tungstenite = "0.20"
toml = "0.8"
@@ -1,3 +1,4 @@
# Resources
- https://heroicons.com/
- https://github.com/basmilius/weather-icons
@@ -1,0 +1,1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><defs><linearGradient id="a" x1="150" x2="234" y1="119.2" y2="264.8" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fbbf24"/><stop offset=".5" stop-color="#fbbf24"/><stop offset="1" stop-color="#f59e0b"/></linearGradient><symbol id="b" viewBox="0 0 384 384"><circle cx="192" cy="192" r="84" fill="url(#a)" stroke="#f8af18" stroke-miterlimit="10" stroke-width="6"/><path fill="none" stroke="#fbbf24" stroke-linecap="round" stroke-miterlimit="10" stroke-width="24" d="M192 61.7V12m0 360v-49.7m92.2-222.5 35-35M64.8 319.2l35.1-35.1m0-184.4-35-35m254.5 254.5-35.1-35.1M61.7 192H12m360 0h-49.7"><animateTransform additive="sum" attributeName="transform" dur="6s" repeatCount="indefinite" type="rotate" values="0 192 192; 45 192 192"/></path></symbol></defs><use xlink:href="#b" width="384" height="384" transform="translate(64 64)"/></svg>
@@ -1,0 +1,1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><defs><linearGradient id="a" x1="54.3" x2="187.2" y1="29" y2="259.1" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#86c3db"/><stop offset=".5" stop-color="#86c3db"/><stop offset="1" stop-color="#5eafcf"/></linearGradient><symbol id="b" overflow="visible" viewBox="0 0 270 270"><path fill="url(#a)" stroke="#72b9d5" stroke-linecap="round" stroke-linejoin="round" stroke-width="6" d="M252.3 168.6A133.4 133.4 0 01118 36.2 130.5 130.5 0 01122.5 3 133 133 0 003 134.6C3 207.7 63 267 137.2 267c62.5 0 114.8-42.2 129.8-99.2a135.6 135.6 0 01-14.8.8Z"><animateTransform additive="sum" attributeName="transform" dur="6s" repeatCount="indefinite" type="rotate" values="-15 135 135; 9 135 135; -15 135 135"/></path></symbol></defs><use xlink:href="#b" width="270" height="270" transform="translate(121 121)"/></svg>
@@ -1,0 +1,1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><defs><linearGradient id="a" x1="99.5" x2="232.6" y1="30.7" y2="261.4" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f3f7fe"/><stop offset=".5" stop-color="#f3f7fe"/><stop offset="1" stop-color="#deeafb"/></linearGradient><symbol id="b" viewBox="0 0 350 222"><path fill="url(#a)" stroke="#e6effc" stroke-miterlimit="10" stroke-width="6" d="m291 107-2.5.1A83.9 83.9 0 00135.6 43 56 56 0 0051 91a56.6 56.6 0 00.8 9A60 60 0 0063 219l4-.2v.2h224a56 56 0 000-112Z"/></symbol></defs><use xlink:href="#b" width="350" height="222" transform="translate(81 145)"><animateTransform additive="sum" attributeName="transform" dur="6s" repeatCount="indefinite" type="translate" values="-18 0; 18 0; -18 0"/></use></svg>
@@ -1,0 +1,1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><defs><linearGradient id="a" x1="52.7" x2="133.4" y1="9.6" y2="149.3" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#9ca3af"/><stop offset=".5" stop-color="#9ca3af"/><stop offset="1" stop-color="#6b7280"/></linearGradient><linearGradient id="b" x1="99.5" x2="232.6" y1="30.7" y2="261.4" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#6b7280"/><stop offset=".5" stop-color="#6b7280"/><stop offset="1" stop-color="#4b5563"/></linearGradient><linearGradient id="c" x1="1381.3" x2="1399.5" y1="-1144.7" y2="-1097.4" gradientTransform="rotate(-9 8002.567 8233.063)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0b65ed"/><stop offset=".5" stop-color="#0a5ad4"/><stop offset="1" stop-color="#0950bc"/></linearGradient><linearGradient xlink:href="#c" id="d" x1="1436.7" x2="1454.9" y1="-1137" y2="-1089.7" gradientTransform="rotate(-9 8009.537 8233.037)"/><linearGradient xlink:href="#c" id="e" x1="1492.1" x2="1510.3" y1="-1129.3" y2="-1082.1" gradientTransform="rotate(-9 8016.566 8233.078)"/><symbol id="g" viewBox="0 0 200.3 126.1"><path fill="url(#a)" stroke="#848b98" stroke-miterlimit="10" d="M.5 93.2a32.4 32.4 0 0032.4 32.4h129.8v-.1l2.3.1a34.8 34.8 0 006.5-68.9 32.4 32.4 0 00-48.5-33 48.6 48.6 0 00-88.6 37.1h-1.5A32.4 32.4 0 00.5 93.1Z"/></symbol><symbol id="h" viewBox="0 0 350 222"><path fill="url(#b)" stroke="#5b6472" stroke-miterlimit="10" stroke-width="6" d="m291 107-2.5.1A83.9 83.9 0 00135.6 43 56 56 0 0051 91a56.6 56.6 0 00.8 9A60 60 0 0063 219l4-.2v.2h224a56 56 0 000-112Z"/></symbol><symbol id="f" overflow="visible" viewBox="0 0 398 222"><use xlink:href="#g" width="200.3" height="126.1" transform="translate(198 27)"><animateTransform additive="sum" attributeName="transform" dur="6s" repeatCount="indefinite" type="translate" values="-9 0; 9 0; -9 0"/></use><use xlink:href="#h" width="350" height="222"><animateTransform additive="sum" attributeName="transform" dur="6s" repeatCount="indefinite" type="translate" values="-18 0; 18 0; -18 0"/></use></symbol><symbol id="i" overflow="visible" viewBox="0 0 129 57"><path fill="url(#c)" stroke="#0a5ad4" stroke-miterlimit="10" d="M8.5 56.5a8 8 0 01-8-8v-40a8 8 0 0116 0v40a8 8 0 01-8 8Z" opacity="0"><animateTransform id="x1" additive="sum" attributeName="transform" begin="0s; x1.end+.33s" dur=".67s" type="translate" values="0 -60; 0 60"/><animate id="y1" attributeName="opacity" begin="0s; y1.end+.33s" dur=".67s" keyTimes="0; .25; 1" values="0; 1; 0"/></path><path fill="url(#d)" stroke="#0a5ad4" stroke-miterlimit="10" d="M64.5 56.5a8 8 0 01-8-8v-40a8 8 0 0116 0v40a8 8 0 01-8 8Z" opacity="0"><animateTransform id="x2" additive="sum" attributeName="transform" begin=".33s; x2.end+.33s" dur=".67s" type="translate" values="0 -60; 0 60"/><animate id="y2" attributeName="opacity" begin=".33s; y2.end+.33s" dur=".67s" keyTimes="0; .25; 1" values="0; 1; 0"/></path><path fill="url(#e)" stroke="#0a5ad4" stroke-miterlimit="10" d="M120.5 56.5a8 8 0 01-8-8v-40a8 8 0 0116 0v40a8 8 0 01-8 8Z" opacity="0"><animateTransform id="x3" additive="sum" attributeName="transform" begin="-.33s; x3.end+.33s" dur=".67s" type="translate" values="0 -60; 0 60"/><animate id="y3" attributeName="opacity" begin="-.33s; y3.end+.33s" dur=".67s" keyTimes="0; .25; 1" values="0; 1; 0"/></path></symbol></defs><use xlink:href="#f" width="398" height="222" transform="translate(68.84 145)"/><use xlink:href="#i" width="129" height="57" transform="translate(191.5 343.5)"/></svg>
@@ -1,0 +1,1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><defs><linearGradient id="a" x1="99.5" x2="232.6" y1="30.7" y2="261.4" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f3f7fe"/><stop offset=".5" stop-color="#f3f7fe"/><stop offset="1" stop-color="#deeafb"/></linearGradient><linearGradient id="b" x1="96" x2="168" y1="-2.4" y2="122.3" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#d4d7dd"/><stop offset=".5" stop-color="#d4d7dd"/><stop offset="1" stop-color="#bec1c6"/></linearGradient><linearGradient xlink:href="#b" id="c" x2="168" y1="-50.4" y2="74.3"/><symbol id="d" viewBox="0 0 350 222"><path fill="url(#a)" stroke="#e6effc" stroke-miterlimit="10" stroke-width="6" d="m291 107-2.5.1A83.9 83.9 0 00135.6 43 56 56 0 0051 91a56.6 56.6 0 00.8 9A60 60 0 0063 219l4-.2v.2h224a56 56 0 000-112Z"/></symbol><symbol id="e" overflow="visible" viewBox="0 0 264 72"><path fill="none" stroke="url(#b)" stroke-linecap="round" stroke-miterlimit="10" stroke-width="24" d="M12 60h240"><animateTransform additive="sum" attributeName="transform" dur="6s" repeatCount="indefinite" type="translate" values="-24 0; 24 0; -24 0"/></path><path fill="none" stroke="url(#c)" stroke-linecap="round" stroke-miterlimit="10" stroke-width="24" d="M12 12h240"><animateTransform additive="sum" attributeName="transform" dur="6s" repeatCount="indefinite" type="translate" values="24 0; -24 0; 24 0"/></path></symbol></defs><use xlink:href="#d" width="350" height="222" transform="translate(81 145)"/><use xlink:href="#e" width="264" height="72" transform="translate(124 402)"/></svg>
@@ -1,0 +1,1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><defs><linearGradient id="a" x1="99.5" x2="232.6" y1="30.7" y2="261.4" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f3f7fe"/><stop offset=".5" stop-color="#f3f7fe"/><stop offset="1" stop-color="#deeafb"/></linearGradient><linearGradient id="b" x1="6.5" x2="18.5" y1="2.1" y2="22.9" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#86c3db"/><stop offset=".5" stop-color="#86c3db"/><stop offset="1" stop-color="#5eafcf"/></linearGradient><linearGradient xlink:href="#b" id="c" x1="62.5" x2="74.5" y1="2.1" y2="22.9"/><linearGradient xlink:href="#b" id="d" x1="118.5" x2="130.5" y1="2.1" y2="22.9"/><symbol id="e" viewBox="0 0 350 222"><path fill="url(#a)" stroke="#e6effc" stroke-miterlimit="10" stroke-width="6" d="m291 107-2.5.1A83.9 83.9 0 00135.6 43 56 56 0 0051 91a56.6 56.6 0 00.8 9A60 60 0 0063 219l4-.2v.2h224a56 56 0 000-112Z"/></symbol><symbol id="f" overflow="visible" viewBox="0 0 137 25"><path fill="url(#b)" stroke="#86c3db" stroke-miterlimit="10" d="M12.5.5a12 12 0 1012 12 12 12 0 00-12-12Z" opacity="0"><animateTransform id="x1" additive="sum" attributeName="transform" begin="0s; x1.end+.42s" dur=".58s" keyTimes="0; .71; 1" type="translate" values="0 -46; 0 86; -18 74"/><animate id="y1" attributeName="opacity" begin="0s; y1.end+.42s" dur=".58s" keyTimes="0; .14; .71; 1" values="0; 1; 1; 0"/></path><path fill="url(#c)" stroke="#86c3db" stroke-miterlimit="10" d="M68.5.5a12 12 0 1012 12 12 12 0 00-12-12Z" opacity="0"><animateTransform id="x2" additive="sum" attributeName="transform" begin=".67s; x2.end+.42s" dur=".58s" keyTimes="0; .71; 1" type="translate" values="0 -46; 0 86; 0 74"/><animate id="y2" attributeName="opacity" begin=".67s; y2.end+.42s" dur=".58s" keyTimes="0; .14; .71; 1" values="0; 1; 1; 0"/></path><path fill="url(#d)" stroke="#86c3db" stroke-miterlimit="10" d="M124.5.5a12 12 0 1012 12 12 12 0 00-12-12Z" opacity="0"><animateTransform id="x3" additive="sum" attributeName="transform" begin=".33s; x3.end+.42s" dur=".58s" keyTimes="0; .71; 1" type="translate" values="0 -46; 0 86; 18 74"/><animate id="y3" attributeName="opacity" begin=".33s; y3.end+.42s" dur=".58s" keyTimes="0; .14; .71; 1" values="0; 1; 1; 0"/></path></symbol></defs><use xlink:href="#e" width="350" height="222" transform="translate(81 145)"/><use xlink:href="#f" width="137" height="25" transform="translate(187.5 349.5)"/></svg>
@@ -1,0 +1,1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><defs><symbol id="d" viewBox="0 0 196 196"><circle cx="98" cy="98" r="40" fill="url(#b)" stroke="#f8af18" stroke-miterlimit="10" stroke-width="4"/><path fill="none" stroke="#fbbf24" stroke-linecap="round" stroke-miterlimit="10" stroke-width="12" d="M98 31.4V6m0 184v-25.4M145.1 51l18-17.9M33 163l18-17.9M51 51 33 33m130.1 130.1-18-18M6 98h25.4M190 98h-25.4"><animateTransform additive="sum" attributeName="transform" dur="6s" repeatCount="indefinite" type="rotate" values="0 98 98; 45 98 98"/></path></symbol><symbol id="e" viewBox="0 0 350 222"><path fill="url(#a)" stroke="#e6effc" stroke-miterlimit="10" stroke-width="6" d="m291 107-2.5.1A83.9 83.9 0 00135.6 43 56 56 0 0051 91a56.6 56.6 0 00.8 9A60 60 0 0063 219l4-.2v.2h224a56 56 0 000-112Z"/></symbol><symbol id="c" viewBox="0 0 363 258"><use xlink:href="#d" width="196" height="196"/><use xlink:href="#e" width="350" height="222" transform="translate(13 36)"/></symbol><linearGradient id="a" x1="99.5" x2="232.6" y1="30.7" y2="261.4" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f3f7fe"/><stop offset=".5" stop-color="#f3f7fe"/><stop offset="1" stop-color="#deeafb"/></linearGradient><linearGradient id="b" x1="78" x2="118" y1="63.4" y2="132.7" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fbbf24"/><stop offset=".5" stop-color="#fbbf24"/><stop offset="1" stop-color="#f59e0b"/></linearGradient></defs><use xlink:href="#c" width="363" height="258" transform="translate(68 109)"/></svg>
@@ -1,0 +1,1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><defs><symbol id="d" viewBox="0 0 172 172"><path fill="url(#b)" stroke="#72b9d5" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M160.6 107.4a84.8 84.8 0 01-85.4-84.3A83.3 83.3 0 0178 2 84.7 84.7 0 002 85.7 84.8 84.8 0 0087.4 170a85.2 85.2 0 0082.6-63.1 88 88 0 01-9.4.5Z"><animateTransform additive="sum" attributeName="transform" dur="6s" repeatCount="indefinite" type="rotate" values="-15 86 86; 9 86 86; -15 86 86"/></path></symbol><symbol id="e" viewBox="0 0 350 222"><path fill="url(#a)" stroke="#e6effc" stroke-miterlimit="10" stroke-width="6" d="m291 107-2.5.1A83.9 83.9 0 00135.6 43 56 56 0 0051 91a56.6 56.6 0 00.8 9A60 60 0 0063 219l4-.2v.2h224a56 56 0 000-112Z"/></symbol><symbol id="c" viewBox="0 0 351 246"><use xlink:href="#d" width="172" height="172"/><use xlink:href="#e" width="350" height="222" transform="translate(1 24)"/></symbol><linearGradient id="a" x1="99.5" x2="232.6" y1="30.7" y2="261.4" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f3f7fe"/><stop offset=".5" stop-color="#f3f7fe"/><stop offset="1" stop-color="#deeafb"/></linearGradient><linearGradient id="b" x1="34.7" x2="119.2" y1="18.6" y2="165" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#86c3db"/><stop offset=".5" stop-color="#86c3db"/><stop offset="1" stop-color="#5eafcf"/></linearGradient></defs><use xlink:href="#c" width="351" height="246" transform="translate(80 121)"/></svg>
@@ -1,0 +1,1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><defs><linearGradient id="a" x1="99.5" x2="232.6" y1="30.7" y2="261.4" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f3f7fe"/><stop offset=".5" stop-color="#f3f7fe"/><stop offset="1" stop-color="#deeafb"/></linearGradient><linearGradient id="b" x1="1381.3" x2="1399.5" y1="-1144.7" y2="-1097.4" gradientTransform="rotate(-9 8002.567 8233.063)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0b65ed"/><stop offset=".5" stop-color="#0a5ad4"/><stop offset="1" stop-color="#0950bc"/></linearGradient><linearGradient xlink:href="#b" id="c" x1="1436.7" x2="1454.9" y1="-1137" y2="-1089.7" gradientTransform="rotate(-9 8009.537 8233.037)"/><linearGradient xlink:href="#b" id="d" x1="1492.1" x2="1510.3" y1="-1129.3" y2="-1082.1" gradientTransform="rotate(-9 8016.566 8233.078)"/><symbol id="e" viewBox="0 0 350 222"><path fill="url(#a)" stroke="#e6effc" stroke-miterlimit="10" stroke-width="6" d="m291 107-2.5.1A83.9 83.9 0 00135.6 43 56 56 0 0051 91a56.6 56.6 0 00.8 9A60 60 0 0063 219l4-.2v.2h224a56 56 0 000-112Z"/></symbol><symbol id="f" overflow="visible" viewBox="0 0 129 57"><path fill="url(#b)" stroke="#0a5ad4" stroke-miterlimit="10" d="M8.5 56.5a8 8 0 01-8-8v-40a8 8 0 0116 0v40a8 8 0 01-8 8Z" opacity="0"><animateTransform id="x1" additive="sum" attributeName="transform" begin="0s; x1.end+.33s" dur=".67s" type="translate" values="0 -60; 0 60"/><animate id="y1" attributeName="opacity" begin="0s; y1.end+.33s" dur=".67s" keyTimes="0; .25; 1" values="0; 1; 0"/></path><path fill="url(#c)" stroke="#0a5ad4" stroke-miterlimit="10" d="M64.5 56.5a8 8 0 01-8-8v-40a8 8 0 0116 0v40a8 8 0 01-8 8Z" opacity="0"><animateTransform id="x2" additive="sum" attributeName="transform" begin=".33s; x2.end+.33s" dur=".67s" type="translate" values="0 -60; 0 60"/><animate id="y2" attributeName="opacity" begin=".33s; y2.end+.33s" dur=".67s" keyTimes="0; .25; 1" values="0; 1; 0"/></path><path fill="url(#d)" stroke="#0a5ad4" stroke-miterlimit="10" d="M120.5 56.5a8 8 0 01-8-8v-40a8 8 0 0116 0v40a8 8 0 01-8 8Z" opacity="0"><animateTransform id="x3" additive="sum" attributeName="transform" begin="-.33s; x3.end+.33s" dur=".67s" type="translate" values="0 -60; 0 60"/><animate id="y3" attributeName="opacity" begin="-.33s; y3.end+.33s" dur=".67s" keyTimes="0; .25; 1" values="0; 1; 0"/></path></symbol></defs><use xlink:href="#e" width="350" height="222" transform="translate(81 145)"/><use xlink:href="#f" width="129" height="57" transform="translate(191.5 343.5)"/></svg>
@@ -1,0 +1,1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><defs><linearGradient id="a" x1="99.5" x2="232.6" y1="30.7" y2="261.4" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f3f7fe"/><stop offset=".5" stop-color="#f3f7fe"/><stop offset="1" stop-color="#deeafb"/></linearGradient><linearGradient id="b" x1="11.4" x2="32.8" y1="5.9" y2="43.1" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#86c3db"/><stop offset=".5" stop-color="#86c3db"/><stop offset="1" stop-color="#5eafcf"/></linearGradient><linearGradient xlink:href="#b" id="c" x1="67.4" x2="88.8" y1="5.9" y2="43.1"/><linearGradient xlink:href="#b" id="d" x1="123.4" x2="144.8" y1="5.9" y2="43.1"/><symbol id="e" viewBox="0 0 350 222"><path fill="url(#a)" stroke="#e6effc" stroke-miterlimit="10" stroke-width="6" d="m291 107-2.5.1A83.9 83.9 0 00135.6 43 56 56 0 0051 91a56.6 56.6 0 00.8 9A60 60 0 0063 219l4-.2v.2h224a56 56 0 000-112Z"/></symbol><symbol id="f" overflow="visible" viewBox="0 0 156.2 49"><g><path fill="url(#b)" stroke="#86c3db" stroke-miterlimit="10" d="m41.7 31-5.8-3.3a13.7 13.7 0 000-6.5l5.8-3.2a4 4 0 001.5-5.5 4 4 0 00-5.6-1.5l-5.8 3.3a13.6 13.6 0 00-2.6-2 13.8 13.8 0 00-3-1.3V4.5a4 4 0 00-8.1 0v6.6a14.3 14.3 0 00-5.7 3.2L6.6 11A4 4 0 001 12.5 4 4 0 002.5 18l5.8 3.3a13.7 13.7 0 000 6.5L2.5 31A4 4 0 001 36.5a4 4 0 003.5 2 4 4 0 002-.5l5.8-3.3a13.6 13.6 0 002.6 2 13.8 13.8 0 003 1.2v6.6a4 4 0 008.2 0v-6.6a14.2 14.2 0 005.6-3.2l6 3.3a4 4 0 002 .5 4 4 0 003.4-2 4 4 0 00-1.4-5.5ZM19 29.7a6 6 0 01-2.3-8.2 6.1 6.1 0 015.3-3 6.2 6.2 0 013 .8 6 6 0 012.2 8.2 6.1 6.1 0 01-8.2 2.2Z" opacity="0"><animateTransform additive="sum" attributeName="transform" dur="6s" repeatCount="indefinite" type="rotate" values="0 24 24; 360 24 24"/><animate id="t1" attributeName="opacity" begin="0s; t1.end+1s" dur="2s" keyTimes="0; .17; .83; 1" values="0; 1; 1; 0"/></path><animateTransform id="s1" additive="sum" attributeName="transform" begin="0s; s1.end+1s" dur="2s" type="translate" values="0 -36; 0 92;"/></g><g><path fill="url(#c)" stroke="#86c3db" stroke-miterlimit="10" d="m97.7 31-5.8-3.3a13.7 13.7 0 000-6.5l5.8-3.2a4 4 0 001.5-5.5 4 4 0 00-5.6-1.5l-5.8 3.3a13.6 13.6 0 00-2.6-2 13.8 13.8 0 00-3-1.3V4.5a4 4 0 00-8.1 0v6.6a14.3 14.3 0 00-5.7 3.2L62.6 11a4 4 0 00-5.6 1.5 4 4 0 001.5 5.5l5.8 3.3a13.7 13.7 0 000 6.5L58.5 31a4 4 0 00-1.5 5.5 4 4 0 003.5 2 4 4 0 002-.5l5.8-3.3a13.6 13.6 0 002.7 2 13.8 13.8 0 003 1.2v6.6a4 4 0 008 0v-6.6a14.2 14.2 0 005.7-3.2l6 3.3a4 4 0 002 .5 4 4 0 003.4-2 4 4 0 00-1.4-5.5ZM75 29.7a6 6 0 01-2.3-8.2 6.1 6.1 0 015.3-3 6.2 6.2 0 013 .8 6 6 0 012.2 8.2 6.1 6.1 0 01-8.2 2.2Z" opacity="0"><animateTransform additive="sum" attributeName="transform" dur="6s" repeatCount="indefinite" type="rotate" values="0 80 24; 360 80 24"/><animate id="t2" attributeName="opacity" begin="-.83s; t2.end+1s" dur="2s" keyTimes="0; .17; .83; 1" values="0; 1; 1; 0"/></path><animateTransform id="s2" additive="sum" attributeName="transform" begin="-.83s; s2.end+1s" dur="2s" type="translate" values="0 -36; 0 92;"/></g><g><path fill="url(#d)" stroke="#86c3db" stroke-miterlimit="10" d="m153.7 31-5.8-3.3a13.7 13.7 0 000-6.5l5.8-3.2a4 4 0 001.5-5.5 4 4 0 00-5.6-1.5l-5.8 3.3a13.6 13.6 0 00-2.6-2 13.8 13.8 0 00-3-1.3V4.5a4 4 0 00-8.1 0v6.6a14.3 14.3 0 00-5.7 3.2l-5.8-3.3a4 4 0 00-5.6 1.5 4 4 0 001.5 5.5l5.8 3.3a13.7 13.7 0 000 6.5l-5.8 3.2a4 4 0 00-1.5 5.5 4 4 0 003.5 2 4 4 0 002-.5l5.8-3.3a13.6 13.6 0 002.7 2 13.8 13.8 0 003 1.2v6.6a4 4 0 008 0v-6.6a14.2 14.2 0 005.7-3.2l5.8 3.3a4 4 0 002 .5 4 4 0 003.5-2 4 4 0 00-1.3-5.5ZM131 29.7a6 6 0 01-2.3-8.2 6.1 6.1 0 015.3-3 6.2 6.2 0 013 .8 6 6 0 012.2 8.2 6.1 6.1 0 01-8.2 2.2Z" opacity="0"><animateTransform additive="sum" attributeName="transform" dur="6s" repeatCount="indefinite" type="rotate" values="0 136 24; 360 136 24"/><animate id="t3" attributeName="opacity" begin=".83s; t3.end+1s" dur="2s" keyTimes="0; .17; .83; 1" values="0; 1; 1; 0"/></path><animateTransform id="s3" additive="sum" attributeName="transform" begin=".83s; s3.end+1s" dur="2s" type="translate" values="0 -36; 0 92;"/></g></symbol></defs><use xlink:href="#e" width="350" height="222" transform="translate(81 145)"/><use xlink:href="#f" width="156.2" height="49" transform="translate(177.9 337.5)"/></svg>
@@ -1,0 +1,1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><defs><linearGradient id="a" x1="99.5" x2="232.6" y1="30.7" y2="261.4" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f3f7fe"/><stop offset=".5" stop-color="#f3f7fe"/><stop offset="1" stop-color="#deeafb"/></linearGradient><linearGradient id="b" x1="8.7" x2="80.9" y1="17.1" y2="142.1" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f7b23b"/><stop offset=".5" stop-color="#f7b23b"/><stop offset="1" stop-color="#f59e0b"/></linearGradient><linearGradient id="c" x1="1381.3" x2="1399.5" y1="-1144.7" y2="-1097.4" gradientTransform="rotate(-9 8002.567 8233.063)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0b65ed"/><stop offset=".5" stop-color="#0a5ad4"/><stop offset="1" stop-color="#0950bc"/></linearGradient><linearGradient xlink:href="#c" id="d" x1="1436.7" x2="1454.9" y1="-1137" y2="-1089.7" gradientTransform="rotate(-9 8009.537 8233.037)"/><linearGradient xlink:href="#c" id="e" x1="1492.1" x2="1510.3" y1="-1129.3" y2="-1082.1" gradientTransform="rotate(-9 8016.566 8233.078)"/><symbol id="f" viewBox="0 0 350 222"><path fill="url(#a)" stroke="#e6effc" stroke-miterlimit="10" stroke-width="6" d="m291 107-2.5.1A83.9 83.9 0 00135.6 43 56 56 0 0051 91a56.6 56.6 0 00.8 9A60 60 0 0063 219l4-.2v.2h224a56 56 0 000-112Z"/></symbol><symbol id="g" overflow="visible" viewBox="0 0 129 57"><path fill="url(#c)" stroke="#0a5ad4" stroke-miterlimit="10" d="M8.5 56.5a8 8 0 01-8-8v-40a8 8 0 0116 0v40a8 8 0 01-8 8Z" opacity="0"><animateTransform id="x1" additive="sum" attributeName="transform" begin="0s; x1.end+.33s" dur=".67s" type="translate" values="0 -60; 0 60"/><animate id="y1" attributeName="opacity" begin="0s; y1.end+.33s" dur=".67s" keyTimes="0; .25; 1" values="0; 1; 0"/></path><path fill="url(#d)" stroke="#0a5ad4" stroke-miterlimit="10" d="M64.5 56.5a8 8 0 01-8-8v-40a8 8 0 0116 0v40a8 8 0 01-8 8Z" opacity="0"><animateTransform id="x2" additive="sum" attributeName="transform" begin=".33s; x2.end+.33s" dur=".67s" type="translate" values="0 -60; 0 60"/><animate id="y2" attributeName="opacity" begin=".33s; y2.end+.33s" dur=".67s" keyTimes="0; .25; 1" values="0; 1; 0"/></path><path fill="url(#e)" stroke="#0a5ad4" stroke-miterlimit="10" d="M120.5 56.5a8 8 0 01-8-8v-40a8 8 0 0116 0v40a8 8 0 01-8 8Z" opacity="0"><animateTransform id="x3" additive="sum" attributeName="transform" begin="-.33s; x3.end+.33s" dur=".67s" type="translate" values="0 -60; 0 60"/><animate id="y3" attributeName="opacity" begin="-.33s; y3.end+.33s" dur=".67s" keyTimes="0; .25; 1" values="0; 1; 0"/></path></symbol><symbol id="h" viewBox="0 0 102.7 186.8"><path fill="url(#b)" stroke="#f6a823" stroke-miterlimit="10" stroke-width="4" d="m34.8 2-32 96h32l-16 80 80-112h-48l32-64h-48z"><animate id="x1" attributeName="opacity" begin="0s; x1.end+.67s" dur="1.33s" keyTimes="0; .38; .5; .63; .75; .86; .94; 1" values="1; 1; 0; 1; 0; 1; 0; 1"/></path></symbol></defs><use xlink:href="#f" width="350" height="222" transform="translate(81 145)"/><use xlink:href="#g" width="129" height="57" transform="translate(191.5 343.5)"/><use xlink:href="#h" width="102.7" height="186.7" transform="translate(205.23 291)"/></svg>
@@ -1,0 +1,1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><defs><linearGradient id="a" x1="99.5" x2="232.6" y1="30.7" y2="261.4" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f3f7fe"/><stop offset=".5" stop-color="#f3f7fe"/><stop offset="1" stop-color="#deeafb"/></linearGradient><linearGradient id="b" x1="8.7" x2="80.9" y1="17.1" y2="142.1" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f7b23b"/><stop offset=".5" stop-color="#f7b23b"/><stop offset="1" stop-color="#f59e0b"/></linearGradient><symbol id="c" viewBox="0 0 350 222"><path fill="url(#a)" stroke="#e6effc" stroke-miterlimit="10" stroke-width="6" d="m291 107-2.5.1A83.9 83.9 0 00135.6 43 56 56 0 0051 91a56.6 56.6 0 00.8 9A60 60 0 0063 219l4-.2v.2h224a56 56 0 000-112Z"/></symbol><symbol id="d" viewBox="0 0 102.7 186.8"><path fill="url(#b)" stroke="#f6a823" stroke-miterlimit="10" stroke-width="4" d="m34.8 2-32 96h32l-16 80 80-112h-48l32-64h-48z"><animate id="x1" attributeName="opacity" begin="0s; x1.end+.67s" dur="1.33s" keyTimes="0; .38; .5; .63; .75; .86; .94; 1" values="1; 1; 0; 1; 0; 1; 0; 1"/></path></symbol></defs><use xlink:href="#c" width="350" height="222" transform="translate(81 145)"/><use xlink:href="#d" width="102.7" height="186.7" transform="translate(205.23 291)"/></svg>
@@ -1,0 +1,1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><defs><linearGradient id="a" x1="138.5" x2="224.2" y1="5.1" y2="153.5" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#d4d7dd"/><stop offset=".5" stop-color="#d4d7dd"/><stop offset="1" stop-color="#bec1c6"/></linearGradient><linearGradient xlink:href="#a" id="b" x1="77.7" x2="169" y1="96.2" y2="254.4"/><symbol id="c" viewBox="0 0 348 240"><path fill="none" stroke="url(#a)" stroke-dasharray="148" stroke-linecap="round" stroke-miterlimit="10" stroke-width="24" d="M267.2 24.3A40 40 0 11296 92H12"><animate attributeName="stroke-dashoffset" dur="6s" repeatCount="indefinite" values="0; 2960"/></path><path fill="none" stroke="url(#b)" stroke-dasharray="110" stroke-linecap="round" stroke-miterlimit="10" stroke-width="24" d="M151.2 215.7A40 40 0 10180 148H12"><animate attributeName="stroke-dashoffset" dur="6s" repeatCount="indefinite" values="0; 1540"/></path></symbol></defs><use xlink:href="#c" width="348" height="240" transform="translate(82 136)"/></svg>
@@ -175,11 +175,22 @@
}
pub mod responses {
use std::borrow::Cow;
use std::{
borrow::Cow,
fmt::{Display, Formatter},
};
use serde::Deserialize;
use serde::{
de,
de::{MapAccess, Visitor},
Deserialize, Deserializer,
};
use serde_json::value::RawValue;
use strum::EnumString;
use yoke::Yokeable;
use crate::theme::Icon;
#[derive(Deserialize, Yokeable, Debug)]
pub struct AreaRegistryList<'a>(#[serde(borrow)] pub Vec<Area<'a>>);
@@ -268,40 +279,102 @@
pub unique_id: Option<Cow<'a, str>>,
}
#[derive(Deserialize, Yokeable, Debug)]
pub struct StatesList<'a>(#[serde(borrow)] pub Vec<State<'a>>);
#[derive(Yokeable, Debug, Deserialize)]
pub struct StatesList<'a>(#[serde(borrow, bound(deserialize = "'a: 'de"))] pub Vec<State<'a>>);
#[derive(Deserialize, Debug)]
pub enum State<'a> {
Sun {
#[serde(borrow)]
state: Cow<'a, str>,
attributes: StateSunAttributes,
},
MediaPlayer {
#[serde(borrow)]
state: Cow<'a, str>,
#[serde(borrow)]
attributes: StateMediaPlayerAttributes<'a>,
},
Camera {
#[serde(borrow)]
state: Cow<'a, str>,
#[serde(borrow)]
attributes: StateCameraAttributes<'a>,
},
Weather {
#[serde(borrow)]
state: Cow<'a, str>,
#[serde(borrow)]
attributes: StateWeatherAttributes<'a>,
},
Light {
#[serde(borrow)]
state: Cow<'a, str>,
#[serde(borrow)]
attributes: StateLightAttributes<'a>,
#[derive(Debug)]
pub struct State<'a> {
pub entity_id: Cow<'a, str>,
pub state: Cow<'a, str>,
pub attributes: StateAttributes<'a>,
}
impl<'de> Deserialize<'de> for State<'de> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_struct(
"State",
&["entity_id", "state", "attributes"],
StateVisitor {},
)
}
}
pub struct StateVisitor {}
impl<'de> Visitor<'de> for StateVisitor {
type Value = State<'de>;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("states struct")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut entity_id: Option<Cow<'de, str>> = None;
let mut state: Option<Cow<'de, str>> = None;
let mut attributes: Option<&'de RawValue> = None;
while let Some(key) = map.next_key()? {
match key {
"entity_id" => {
entity_id = Some(map.next_value()?);
}
"state" => {
state = Some(map.next_value()?);
}
"attributes" => {
attributes = Some(map.next_value()?);
}
_ => {
let _: &'de RawValue = map.next_value()?;
}
}
}
let entity_id = entity_id.ok_or_else(|| de::Error::missing_field("entity_id"))?;
let state = state.ok_or_else(|| de::Error::missing_field("state"))?;
let attributes = attributes.ok_or_else(|| de::Error::missing_field("attributes"))?;
let Some((kind, _)) = entity_id.split_once('.') else {
return Err(de::Error::custom("invalid entity_id"));
};
let attributes = match kind {
"sun" => StateAttributes::Light(serde_json::from_str(attributes.get()).unwrap()),
"media_player" => {
StateAttributes::MediaPlayer(serde_json::from_str(attributes.get()).unwrap())
}
"camera" => {
StateAttributes::Camera(serde_json::from_str(attributes.get()).unwrap())
}
"weather" => {
StateAttributes::Weather(serde_json::from_str(attributes.get()).unwrap())
}
"light" => StateAttributes::Light(serde_json::from_str(attributes.get()).unwrap()),
_ => StateAttributes::Unknown,
};
Ok(State {
entity_id,
state,
attributes,
})
}
}
#[derive(Deserialize, Debug)]
pub enum StateAttributes<'a> {
Sun(StateSunAttributes),
MediaPlayer(#[serde(borrow)] StateMediaPlayerAttributes<'a>),
Camera(#[serde(borrow)] StateCameraAttributes<'a>),
Weather(#[serde(borrow)] StateWeatherAttributes<'a>),
Light(#[serde(borrow)] StateLightAttributes<'a>),
Unknown,
}
#[derive(Deserialize, Debug)]
@@ -319,27 +392,27 @@
#[derive(Deserialize, Debug)]
pub struct StateMediaPlayerAttributes<'a> {
#[serde(borrow)]
#[serde(borrow, default)]
source_list: Vec<Cow<'a, str>>,
#[serde(borrow)]
#[serde(borrow, default)]
group_members: Vec<Cow<'a, str>>,
volume_level: f32,
is_volume_muted: bool,
volume_level: Option<f32>,
is_volume_muted: Option<bool>,
#[serde(borrow)]
media_content_id: Cow<'a, str>,
media_content_id: Option<Cow<'a, str>>,
#[serde(borrow)]
media_content_type: Cow<'a, str>,
media_content_type: Option<Cow<'a, str>>,
#[serde(borrow)]
source: Cow<'a, str>,
shuffle: bool,
source: Option<Cow<'a, str>>,
shuffle: Option<bool>,
#[serde(borrow)]
repeat: Cow<'a, str>,
queue_position: u32,
queue_size: u32,
repeat: Option<Cow<'a, str>>,
queue_position: Option<u32>,
queue_size: Option<u32>,
#[serde(borrow)]
device_class: Cow<'a, str>,
device_class: Option<Cow<'a, str>>,
#[serde(borrow)]
friendly_name: Cow<'a, str>,
friendly_name: Option<Cow<'a, str>>,
}
#[derive(Deserialize, Debug)]
@@ -349,72 +422,142 @@
#[serde(borrow)]
friendly_name: Cow<'a, str>,
#[serde(borrow)]
stream_source: Cow<'a, str>,
stream_source: Option<Cow<'a, str>>,
#[serde(borrow)]
still_image_url: Cow<'a, str>,
still_image_url: Option<Cow<'a, str>>,
#[serde(borrow)]
name: Cow<'a, str>,
name: Option<Cow<'a, str>>,
#[serde(borrow)]
id: Cow<'a, str>,
id: Option<Cow<'a, str>>,
#[serde(borrow)]
entity_picture: Cow<'a, str>,
}
#[derive(Deserialize, Debug, EnumString, Copy, Clone)]
#[serde(rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub enum WeatherCondition {
ClearNight,
Cloudy,
Fog,
Hail,
Lightning,
LightningRainy,
#[serde(rename = "partlycloudy")]
#[strum(serialize = "partlycloudy")]
PartlyCloudy,
Pouring,
Rainy,
Snowy,
SnowyRainy,
Sunny,
Windy,
WindyVariant,
Exceptional,
#[serde(other)]
Unknown,
}
impl WeatherCondition {
pub fn icon(self, day_time: bool) -> Option<Icon> {
match self {
WeatherCondition::ClearNight => Some(Icon::ClearNight),
WeatherCondition::Cloudy => Some(Icon::Cloud),
WeatherCondition::Fog => Some(Icon::Fog),
WeatherCondition::Hail => Some(Icon::Hail),
WeatherCondition::Lightning => Some(Icon::Thunderstorms),
WeatherCondition::LightningRainy => Some(Icon::ThunderstormsRain),
WeatherCondition::PartlyCloudy => Some(if day_time {
Icon::PartlyCloudyDay
} else {
Icon::PartlyCloudyNight
}),
WeatherCondition::Pouring => Some(Icon::ExtremeRain),
WeatherCondition::Rainy => Some(Icon::Rain),
WeatherCondition::Snowy | WeatherCondition::SnowyRainy => Some(Icon::Snow),
WeatherCondition::Sunny => Some(Icon::ClearDay),
WeatherCondition::Windy | WeatherCondition::WindyVariant => Some(Icon::Wind),
WeatherCondition::Exceptional | WeatherCondition::Unknown => None,
}
}
}
impl Display for WeatherCondition {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
WeatherCondition::ClearNight => "Clear",
WeatherCondition::Cloudy => "Cloudy",
WeatherCondition::Fog => "Fog",
WeatherCondition::Hail => "Hail",
WeatherCondition::Lightning | WeatherCondition::LightningRainy => "Lightning",
WeatherCondition::PartlyCloudy => "Partly Cloudy",
WeatherCondition::Pouring => "Heavy Rain",
WeatherCondition::Rainy => "Rain",
WeatherCondition::Snowy | WeatherCondition::SnowyRainy => "Snow",
WeatherCondition::Sunny => "Sunny",
WeatherCondition::Windy | WeatherCondition::WindyVariant => "Windy",
WeatherCondition::Exceptional => "Exceptional",
WeatherCondition::Unknown => "Unknown",
})
}
}
#[derive(Deserialize, Debug)]
pub struct StateWeatherAttributes<'a> {
temperature: f32,
dew_point: f32,
pub temperature: f32,
pub dew_point: f32,
#[serde(borrow)]
temperature_unit: Cow<'a, str>,
humidity: u8,
cloud_coverage: u8,
pressure: f32,
pub temperature_unit: Cow<'a, str>,
pub humidity: f32,
pub cloud_coverage: f32,
pub pressure: f32,
#[serde(borrow)]
pressure_unit: Cow<'a, str>,
wind_bearing: f32,
wind_speed: f32,
pub pressure_unit: Cow<'a, str>,
pub wind_bearing: f32,
pub wind_speed: f32,
#[serde(borrow)]
wind_speed_unit: Cow<'a, str>,
pub wind_speed_unit: Cow<'a, str>,
#[serde(borrow)]
visibility_unit: Cow<'a, str>,
pub visibility_unit: Cow<'a, str>,
#[serde(borrow)]
precipitation_unit: Cow<'a, str>,
pub precipitation_unit: Cow<'a, str>,
#[serde(borrow)]
forecast: Vec<StateWeatherAttributesForecast<'a>>,
pub forecast: Vec<StateWeatherAttributesForecast<'a>>,
}
#[derive(Deserialize, Debug)]
pub struct StateWeatherAttributesForecast<'a> {
#[serde(borrow)]
condition: Cow<'a, str>,
pub condition: Cow<'a, str>,
wind_bearing: f32,
temperature: f32,
pub wind_bearing: f32,
pub temperature: f32,
#[serde(rename = "templow")]
temperature_low: f32,
wind_speed: f32,
precipitation: u8,
humidity: u8,
pub temperature_low: f32,
pub wind_speed: f32,
pub precipitation: f32,
pub humidity: f32,
}
#[derive(Deserialize, Debug)]
pub struct StateLightAttributes<'a> {
min_color_temp_kelvin: u16,
max_color_temp_kelvin: u16,
min_mireds: u16,
max_mireds: u16,
min_color_temp_kelvin: Option<u16>,
max_color_temp_kelvin: Option<u16>,
min_mireds: Option<u16>,
max_mireds: Option<u16>,
#[serde(default)]
supported_color_modes: Vec<ColorMode>,
#[serde(borrow)]
mode: Cow<'a, str>,
mode: Option<Cow<'a, str>>,
#[serde(borrow)]
dynamics: Cow<'a, str>,
dynamics: Option<Cow<'a, str>>,
#[serde(borrow)]
friendly_name: Cow<'a, str>,
color_mode: Option<ColorMode>,
brightness: Option<u8>,
brightness: Option<f32>,
color_temp_kelvin: Option<u16>,
color_temp: Option<u16>,
xy_color: Option<(u8, u8)>,
xy_color: Option<(f32, f32)>,
}
#[derive(Deserialize, Debug)]
@@ -1,21 +1,28 @@
use std::collections::BTreeMap;
use std::{collections::BTreeMap, str::FromStr};
use internment::Intern;
use crate::hass_client::{responses::AreaRegistryList, HassRequestKind};
use crate::hass_client::{
responses::{AreaRegistryList, StateAttributes, StatesList, WeatherCondition},
HassRequestKind,
};
#[derive(Debug)]
pub struct Oracle {
client: crate::hass_client::Client,
rooms: BTreeMap<Intern<str>, Room>,
pub weather: Weather,
}
impl Oracle {
pub async fn new(hass_client: crate::hass_client::Client) -> Self {
let (rooms,) = tokio::join!(
hass_client.request::<AreaRegistryList<'_>>(HassRequestKind::AreaRegistry)
let (rooms, states) = tokio::join!(
hass_client.request::<AreaRegistryList<'_>>(HassRequestKind::AreaRegistry),
hass_client.request::<StatesList<'_>>(HassRequestKind::GetStates),
);
let states = states.get();
let rooms = rooms
.get()
.0
@@ -33,6 +40,7 @@
Self {
client: hass_client,
rooms,
weather: Weather::parse_from_states(states),
}
}
@@ -44,4 +52,45 @@
#[derive(Debug)]
pub struct Room {
pub name: Intern<str>,
}
#[derive(Debug)]
pub struct Weather {
pub temperature: i16,
pub high: i16,
pub low: i16,
pub condition: WeatherCondition,
}
impl Weather {
fn parse_from_states(states: &StatesList) -> Self {
let (state, weather) = states
.0
.iter()
.filter_map(|v| match &v.attributes {
StateAttributes::Weather(attr) => Some((&v.state, attr)),
_ => None,
})
.next()
.unwrap();
let condition = WeatherCondition::from_str(&state).unwrap_or(WeatherCondition::Unknown);
let (high, low) =
weather
.forecast
.iter()
.fold((i16::MIN, i16::MAX), |(high, low), curr| {
let temp = curr.temperature.round() as i16;
(high.max(temp), low.min(temp))
});
Self {
temperature: weather.temperature.round() as i16,
condition,
high,
low,
}
}
}
@@ -43,30 +43,56 @@
Play,
Pause,
Repeat,
Cloud,
ClearNight,
Fog,
Hail,
Thunderstorms,
ThunderstormsRain,
PartlyCloudyDay,
PartlyCloudyNight,
ExtremeRain,
Rain,
Snow,
ClearDay,
Wind,
}
impl Icon {
pub fn handle(self) -> svg::Handle {
macro_rules! image {
($path:expr) => {{
static FILE: &[u8] = include_bytes!($path);
static FILE: &[u8] = include_bytes!(concat!("../../assets/icons/", $path, ".svg"));
static HANDLE: Lazy<svg::Handle> = Lazy::new(|| svg::Handle::from_memory(FILE));
(*HANDLE).clone()
}};
}
match self {
Self::Home => image!("../../assets/icons/home.svg"),
Self::Back => image!("../../assets/icons/back.svg"),
Self::Bulb => image!("../../assets/icons/light-bulb.svg"),
Self::Hamburger => image!("../../assets/icons/hamburger.svg"),
Self::Speaker => image!("../../assets/icons/speaker.svg"),
Self::SpeakerMuted => image!("../../assets/icons/speaker-muted.svg"),
Self::Backward => image!("../../assets/icons/backward.svg"),
Self::Forward => image!("../../assets/icons/forward.svg"),
Self::Play => image!("../../assets/icons/play.svg"),
Self::Pause => image!("../../assets/icons/pause.svg"),
Self::Repeat => image!("../../assets/icons/repeat.svg"),
Self::Home => image!("home"),
Self::Back => image!("back"),
Self::Bulb => image!("light-bulb"),
Self::Hamburger => image!("hamburger"),
Self::Speaker => image!("speaker"),
Self::SpeakerMuted => image!("speaker-muted"),
Self::Backward => image!("backward"),
Self::Forward => image!("forward"),
Self::Play => image!("play"),
Self::Pause => image!("pause"),
Self::Repeat => image!("repeat"),
Self::Cloud => image!("cloud"),
Self::ClearNight => image!("clear-night"),
Self::Fog => image!("fog"),
Self::Hail => image!("hail"),
Self::Thunderstorms => image!("thunderstorms"),
Self::ThunderstormsRain => image!("thunderstorms-rain"),
Self::PartlyCloudyDay => image!("partly-cloudy-day"),
Self::PartlyCloudyNight => image!("partly-cloudy-night"),
Self::ExtremeRain => image!("extreme-rain"),
Self::Rain => image!("rain"),
Self::Snow => image!("snow"),
Self::ClearDay => image!("clear-day"),
Self::Wind => image!("wind"),
}
}
}
@@ -32,13 +32,11 @@
}
fn view(&self, _state: &Self::State) -> Element<'_, Self::Event, Renderer> {
let header = |v| {
text(v).size(60).font(Font {
weight: Weight::Bold,
stretch: Stretch::Condensed,
..Font::with_name("Helvetica Neue")
})
};
let greeting = text("Good Evening").size(60).font(Font {
weight: Weight::Bold,
stretch: Stretch::Condensed,
..Font::with_name("Helvetica Neue")
});
let room = |room, image| {
image_card::image_card(image, room).on_press(Event::OpenRoom(room))
@@ -56,9 +54,13 @@
.fold(Column::new().spacing(10), Column::push);
scrollable(
column![header("Cameras"), header("Rooms"), rooms,]
.spacing(20)
.padding(40),
column![
greeting,
crate::widgets::cards::weather::WeatherCard::new(self.oracle.clone()),
rooms,
]
.spacing(20)
.padding(40),
)
.into()
}
@@ -1,3 +1,4 @@
pub mod cards;
pub mod context_menu;
pub mod image_card;
pub mod media_player;
@@ -1,0 +1,1 @@
pub mod weather;
@@ -1,0 +1,173 @@
use std::sync::Arc;
use iced::{
advanced::{
layout::{Limits, Node},
renderer::{Quad, Style},
svg::Renderer as SvgRenderer,
text::{LineHeight, Renderer as TextRenderer, Shaping},
widget::Tree,
Layout, Renderer as AdvancedRenderer, Text, Widget,
},
alignment::{Horizontal, Vertical},
font::Weight,
gradient::Linear,
mouse::Cursor,
Alignment, Background, Color, Degrees, Element, Font, Gradient, Length, Rectangle, Renderer,
Size, Theme,
};
use crate::oracle::Oracle;
pub struct WeatherCard<M> {
pub on_click: Option<M>,
pub oracle: Arc<Oracle>,
}
impl<M> WeatherCard<M> {
pub fn new(oracle: Arc<Oracle>) -> Self {
Self {
on_click: None,
oracle,
}
}
fn build_temperature(&self) -> String {
format!("{}°", self.oracle.weather.temperature)
}
fn build_conditions(&self) -> String {
format!(
"{}\nH:{}° L:{}°",
self.oracle.weather.condition, self.oracle.weather.high, self.oracle.weather.low,
)
}
}
impl<M: Clone> Widget<M, Renderer> for WeatherCard<M> {
fn width(&self) -> Length {
Length::Fixed(192.)
}
fn height(&self) -> Length {
Length::Fixed(192.)
}
fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node {
let padding = 16.into();
let limits = limits
.height(self.height())
.width(self.width())
.pad(padding);
let container_size = limits.resolve(Size::ZERO);
let mut header_node = Node::new(renderer.measure(
&self.build_temperature(),
42.,
LineHeight::default(),
Font {
weight: Weight::Normal,
..Font::with_name("Helvetica Neue")
},
container_size,
Shaping::Basic,
));
header_node.move_to([padding.top, padding.left].into());
header_node.align(Alignment::Start, Alignment::Start, container_size);
let mut icon_node =
Node::new(Size::new(16., 16.)).translate([padding.left, -padding.bottom - 32.].into());
icon_node.align(Alignment::Start, Alignment::End, container_size);
let mut conditions_node = Node::new(renderer.measure(
&self.build_conditions(),
12.,
LineHeight::default(),
Font {
weight: Weight::Bold,
..Font::with_name("Helvetica Neue")
},
container_size,
Shaping::Basic,
))
.translate([padding.left, -padding.bottom].into());
conditions_node.align(Alignment::Start, Alignment::End, container_size);
Node::with_children(
container_size,
vec![header_node, icon_node, conditions_node],
)
}
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
_theme: &Theme,
_style: &Style,
layout: Layout<'_>,
_cursor: Cursor,
_viewport: &Rectangle,
) {
renderer.fill_quad(
Quad {
bounds: layout.bounds(),
border_radius: [20., 20., 20., 20.].into(),
border_width: 0.,
border_color: Color::WHITE,
},
Background::Gradient(Gradient::Linear(
Linear::new(Degrees(90.))
.add_stop(0.0, Color::from_rgba8(43, 44, 66, 1.0))
.add_stop(1.0, Color::from_rgba8(15, 18, 27, 1.0)),
)),
);
let mut children = layout.children();
renderer.fill_text(Text {
content: &self.build_temperature(),
bounds: children.next().unwrap().bounds(),
size: 42.,
line_height: LineHeight::default(),
color: Color::WHITE,
font: Font {
weight: Weight::Normal,
..Font::with_name("Helvetica Neue")
},
horizontal_alignment: Horizontal::Left,
vertical_alignment: Vertical::Top,
shaping: Shaping::Basic,
});
let icon_bounds = children.next().unwrap().bounds();
if let Some(icon) = self.oracle.weather.condition.icon(false) {
renderer.draw(icon.handle(), None, icon_bounds);
}
renderer.fill_text(Text {
content: &self.build_conditions(),
bounds: children.next().unwrap().bounds(),
size: 12.,
line_height: LineHeight::default(),
color: Color::WHITE,
font: Font {
weight: Weight::Bold,
..Font::with_name("Helvetica Neue")
},
horizontal_alignment: Horizontal::Left,
vertical_alignment: Vertical::Top,
shaping: Shaping::Basic,
});
}
}
impl<'a, M> From<WeatherCard<M>> for Element<'a, M>
where
M: 'a + Clone,
{
fn from(modal: WeatherCard<M>) -> Self {
Element::new(modal)
}
}