changes
This commit is contained in:
Generated
+25
@@ -485,8 +485,10 @@ dependencies = [
|
|||||||
name = "common"
|
name = "common"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"iced",
|
"iced",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2912,6 +2914,8 @@ dependencies = [
|
|||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
@@ -3180,6 +3184,19 @@ dependencies = [
|
|||||||
"syn 3.0.5",
|
"syn 3.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.151"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -3197,6 +3214,8 @@ dependencies = [
|
|||||||
"common",
|
"common",
|
||||||
"iced",
|
"iced",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4847,3 +4866,9 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time"
|
|||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
toml = "1.1"
|
toml = "1.1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
clap = { version = "4.6.6", features = ["derive", "help"] }
|
clap = { version = "4.6.6", features = ["derive", "help"] }
|
||||||
|
|
||||||
|
|||||||
@@ -11,3 +11,5 @@ path = "src/lib.rs"
|
|||||||
iced = { workspace = true }
|
iced = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
chrono = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ mod wire;
|
|||||||
pub use config::BarbarConfig;
|
pub use config::BarbarConfig;
|
||||||
pub use effect::ModuleEffect;
|
pub use effect::ModuleEffect;
|
||||||
pub use messages::{ClockKind, ClockMsg, ClockPayload};
|
pub use messages::{ClockKind, ClockMsg, ClockPayload};
|
||||||
|
pub use messages::modules::WeatherKind;
|
||||||
|
pub use messages::services::WeatherResponse;
|
||||||
pub use module::BarModule;
|
pub use module::BarModule;
|
||||||
pub use service::Service;
|
pub use service::Service;
|
||||||
pub use wire::{Endpoint, Inbox, Namespace, Target, Wire};
|
pub use wire::{Endpoint, Inbox, Namespace, Target, Wire};
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
//! Messages owned by bar modules.
|
//! Messages owned by bar modules.
|
||||||
|
|
||||||
mod clock;
|
mod clock;
|
||||||
|
mod weather;
|
||||||
|
|
||||||
pub use clock::ClockMsg;
|
pub use clock::ClockMsg;
|
||||||
|
pub use weather::WeatherKind;
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
//! Weather module messages.
|
||||||
|
|
||||||
|
/// A module-local message for the weather tile (bar/popup interactions).
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum WeatherKind {
|
||||||
|
Poke,
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
//! Messages owned by services.
|
//! Messages owned by services.
|
||||||
|
|
||||||
mod datetime;
|
mod datetime;
|
||||||
|
mod weather;
|
||||||
|
|
||||||
pub use datetime::{ClockKind, ClockPayload};
|
pub use datetime::{ClockKind, ClockPayload};
|
||||||
|
pub use weather::{WeatherKind, WeatherPayload, WeatherResponse};
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::Service;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||||
|
pub enum WeatherKind {
|
||||||
|
State,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WeatherKind {
|
||||||
|
pub fn key(self) -> Service {
|
||||||
|
Service(match self {
|
||||||
|
Self::State => "weather.state",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct WeatherPayload {
|
||||||
|
pub kind: WeatherKind,
|
||||||
|
pub payload: Arc<dyn Any + Send + Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open-Meteo forecast response.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct WeatherResponse {
|
||||||
|
pub latitude: f64,
|
||||||
|
pub longitude: f64,
|
||||||
|
pub generationtime_ms: f64,
|
||||||
|
pub utc_offset_seconds: i64,
|
||||||
|
pub timezone: String,
|
||||||
|
pub timezone_abbreviation: String,
|
||||||
|
pub elevation: f64,
|
||||||
|
pub hourly_units: HourlyUnits,
|
||||||
|
pub hourly: Hourly,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct HourlyUnits {
|
||||||
|
pub time: String,
|
||||||
|
pub temperature_2m: String,
|
||||||
|
pub rain: String,
|
||||||
|
pub showers: String,
|
||||||
|
pub precipitation: String,
|
||||||
|
pub relative_humidity_2m: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Hourly {
|
||||||
|
#[serde(deserialize_with = "naive_dt::deserialize")]
|
||||||
|
pub time: Vec<chrono::NaiveDateTime>,
|
||||||
|
pub temperature_2m: Vec<f64>,
|
||||||
|
pub rain: Vec<f64>,
|
||||||
|
pub showers: Vec<f64>,
|
||||||
|
pub precipitation: Vec<f64>,
|
||||||
|
pub relative_humidity_2m: Vec<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open-Meteo emits local timestamps as `"%Y-%m-%dT%H:%M"` (no seconds).
|
||||||
|
mod naive_dt {
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use serde::{de::Error, Deserialize, Deserializer};
|
||||||
|
|
||||||
|
const FMT: &str = "%Y-%m-%dT%H:%M";
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(d: D) -> Result<Vec<NaiveDateTime>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Vec::<String>::deserialize(d)?
|
||||||
|
.iter()
|
||||||
|
.map(|s| NaiveDateTime::parse_from_str(s, FMT).map_err(D::Error::custom))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ pub(crate) struct Bar {
|
|||||||
pub(crate) active_popup: Option<String>,
|
pub(crate) active_popup: Option<String>,
|
||||||
/// Module registry keyed by stable module id.
|
/// Module registry keyed by stable module id.
|
||||||
pub(crate) modules: BTreeMap<&'static str, Box<dyn BarModule>>,
|
pub(crate) modules: BTreeMap<&'static str, Box<dyn BarModule>>,
|
||||||
|
/// Left, Middle, Right ; Module Order
|
||||||
|
pub(crate) order: (Vec<String>, Vec<String>, Vec<String>),
|
||||||
/// Surface id of the open popup.
|
/// Surface id of the open popup.
|
||||||
pub(crate) popup_id: Option<window::Id>,
|
pub(crate) popup_id: Option<window::Id>,
|
||||||
/// Fan-out routing table: module id -> services it wants.
|
/// Fan-out routing table: module id -> services it wants.
|
||||||
@@ -21,7 +23,10 @@ pub(crate) struct Bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Bar {
|
impl Bar {
|
||||||
pub(crate) fn new(modules_config_table: &Table) -> Self {
|
pub(crate) fn new(
|
||||||
|
modules_config_table: &Table,
|
||||||
|
modules_order: (Vec<String>, Vec<String>, Vec<String>),
|
||||||
|
) -> Self {
|
||||||
// Registry owns construction: adding a module never edits this file.
|
// Registry owns construction: adding a module never edits this file.
|
||||||
let modules: BTreeMap<&'static str, Box<dyn BarModule>> =
|
let modules: BTreeMap<&'static str, Box<dyn BarModule>> =
|
||||||
modules::all(modules_config_table.clone())
|
modules::all(modules_config_table.clone())
|
||||||
@@ -41,6 +46,7 @@ impl Bar {
|
|||||||
Self {
|
Self {
|
||||||
active_popup: None,
|
active_popup: None,
|
||||||
modules,
|
modules,
|
||||||
|
order: modules_order,
|
||||||
popup_id: None,
|
popup_id: None,
|
||||||
routes,
|
routes,
|
||||||
inputs,
|
inputs,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use iced::widget::{column, container, text};
|
use iced::widget::{container, row, text};
|
||||||
use iced::{window, Alignment, Element, Length, Theme};
|
use iced::{window, Alignment, Element, Length, Theme};
|
||||||
|
|
||||||
use crate::app::Bar;
|
use crate::app::Bar;
|
||||||
@@ -24,11 +24,7 @@ fn bar_view(bar: &Bar) -> Element<'_, Message> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
container(
|
container(row(children).width(Length::Fill).align_y(Alignment::Center))
|
||||||
column(children)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.align_x(Alignment::Center),
|
|
||||||
)
|
|
||||||
.padding(8)
|
.padding(8)
|
||||||
.align_x(Alignment::Center)
|
.align_x(Alignment::Center)
|
||||||
.into()
|
.into()
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
# modules
|
||||||
|
|
||||||
|
Bar modules. Each module renders a piece of the bar (or its popup) and is
|
||||||
|
object-safe behind `common::BarModule`, so core holds them as
|
||||||
|
`Box<dyn BarModule>` and never knows their message types — everything crosses
|
||||||
|
the `common::Wire` boundary.
|
||||||
|
|
||||||
|
Message types are shared, so they live in the `common` crate, under
|
||||||
|
`common/src/messages/modules/`. That is what lets a module and core name the
|
||||||
|
same payload without either depending on the other.
|
||||||
|
|
||||||
|
## Getting started: add a module
|
||||||
|
|
||||||
|
1. Declare the module's messages in `crates/common/src/messages/modules/greeter.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
//! Greeter module messages.
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum GreeterMsg {
|
||||||
|
Poke,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Re-export them from `crates/common/src/messages/modules/mod.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
mod clock;
|
||||||
|
mod greeter;
|
||||||
|
|
||||||
|
pub use clock::ClockMsg;
|
||||||
|
pub use greeter::GreeterMsg;
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Create `crates/modules/src/greeter.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use iced::widget::text;
|
||||||
|
use iced::{Element, Task};
|
||||||
|
|
||||||
|
use common::{BarModule, Endpoint, GreeterMsg, ModuleEffect, Service, Wire};
|
||||||
|
|
||||||
|
pub struct Greeter {
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Greeter {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { text: "hi".into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BarModule for Greeter {
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"greeter"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _window_id: Option<iced::window::Id>) -> Element<'_, Wire> {
|
||||||
|
text(&self.text).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, msg: Wire) -> Task<ModuleEffect> {
|
||||||
|
match msg.downcast::<GreeterMsg>() {
|
||||||
|
Some(GreeterMsg::Poke) => {
|
||||||
|
self.text = "poked".into();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
None => Task::none(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Route keys this module subscribes to.
|
||||||
|
fn services(&self) -> Vec<Service> {
|
||||||
|
Vec::new(
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optional: read `module.greeter` from the config table.
|
||||||
|
fn config(&mut self, _config: toml::Table) -> Result<(), Box<dyn Error>> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Register it in `crates/modules/src/lib.rs`'s `all()` and re-export it:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
mod greeter;
|
||||||
|
pub use greeter::Greeter;
|
||||||
|
|
||||||
|
pub fn all(module_config: toml::Table) -> Vec<Box<dyn BarModule>> {
|
||||||
|
vec![
|
||||||
|
Box::new(clock::Clock::new(Some(module_config.clone()))),
|
||||||
|
Box::new(greeter::Greeter::default()),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it — core picks it up by `id()`. Use `wire` targeting:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Module-local message back to yourself:
|
||||||
|
Wire::module(Endpoint::module(self.id()), self.id(), GreeterMsg::Poke)
|
||||||
|
```
|
||||||
|
|
||||||
|
To talk to a service, add it to `services()` and send via
|
||||||
|
`Wire::service(...)`; service ticks arrive in `update()` and you `downcast`
|
||||||
|
to the payload type (e.g. `common::ClockPayload`). See `src/clock.rs` for a
|
||||||
|
full example.
|
||||||
@@ -2,12 +2,17 @@
|
|||||||
//! a new file under `src/` plus one entry in `all()` — core never changes.
|
//! a new file under `src/` plus one entry in `all()` — core never changes.
|
||||||
|
|
||||||
mod clock;
|
mod clock;
|
||||||
|
mod weather;
|
||||||
|
|
||||||
pub use clock::{Clock, ClockError};
|
pub use clock::{Clock, ClockError};
|
||||||
use common::BarModule;
|
use common::BarModule;
|
||||||
use toml::Table;
|
use toml::Table;
|
||||||
|
pub use weather::*;
|
||||||
|
|
||||||
/// One instance of every enabled module, ready to insert by `id()`.
|
/// One instance of every enabled module, ready to insert by `id()`.
|
||||||
pub fn all(module_config: Table) -> Vec<Box<dyn BarModule>> {
|
pub fn all(module_config: Table) -> Vec<Box<dyn BarModule>> {
|
||||||
vec![Box::new(clock::Clock::new(Some(module_config)))]
|
vec![
|
||||||
|
Box::new(clock::Clock::new(Some(module_config))),
|
||||||
|
Box::new(weather::WeatherModule::default()),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use common::{BarModule, Endpoint, ModuleEffect, Service, WeatherKind, Wire};
|
||||||
|
use iced::{
|
||||||
|
widget::{container, mouse_area, text},
|
||||||
|
Element, Task,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct WeatherModule {
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WeatherModule {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { text: "hi".into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BarModule for WeatherModule {
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"weather"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _window_id: Option<iced::window::Id>) -> Element<'_, Wire> {
|
||||||
|
let me = Endpoint::module(self.id());
|
||||||
|
container(mouse_area(text(&self.text).size(16)).on_press(Wire::module(
|
||||||
|
me,
|
||||||
|
self.id(),
|
||||||
|
WeatherKind::Poke,
|
||||||
|
)))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, msg: Wire) -> Task<ModuleEffect> {
|
||||||
|
match msg.downcast::<WeatherKind>() {
|
||||||
|
Some(WeatherKind::Poke) => {
|
||||||
|
self.text = "poked".into();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
None => Task::none(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Route keys this module subscribes to.
|
||||||
|
fn services(&self) -> Vec<Service> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optional: read `module.weather` from the config table.
|
||||||
|
fn config(&mut self, _config: toml::Table) -> Result<(), Box<dyn Error>> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,4 +12,6 @@ common = { path = "../common" }
|
|||||||
iced = { workspace = true }
|
iced = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
reqwest = "0.13.5"
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
reqwest = { version = "0.13.5", features = ["json"] }
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
# services
|
||||||
|
|
||||||
|
Background services. A service owns a route key, runs as an iced
|
||||||
|
`Subscription`, receives `common::Wire`s on its `Inbox`, and publishes
|
||||||
|
payloads back to every module subscribed to its key. Core maps a `Service`
|
||||||
|
route key to a subscription through `IntoSubscription`.
|
||||||
|
|
||||||
|
Message types are shared, so they live in the `common` crate, under
|
||||||
|
`common/src/messages/services/`. That is what lets a service and the modules
|
||||||
|
that consume it name the same payload without either depending on the other.
|
||||||
|
|
||||||
|
## Getting started: add a service
|
||||||
|
|
||||||
|
1. Declare the service's route key and payload in
|
||||||
|
`crates/common/src/messages/services/greeter.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
//! Greeter service messages.
|
||||||
|
|
||||||
|
use crate::Service;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||||
|
pub enum GreeterKind {
|
||||||
|
Hello,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GreeterKind {
|
||||||
|
/// Route key a module subscribes under to receive this kind's ticks.
|
||||||
|
pub fn key(self) -> Service {
|
||||||
|
Service(match self {
|
||||||
|
Self::Hello => "greeter.hello",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct GreeterPayload {
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Re-export them from `crates/common/src/messages/services/mod.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
mod datetime;
|
||||||
|
mod greeter;
|
||||||
|
mod weather;
|
||||||
|
|
||||||
|
pub use datetime::{ClockKind, ClockPayload};
|
||||||
|
pub use greeter::{GreeterKind, GreeterPayload};
|
||||||
|
pub use weather::*;
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Create `crates/services/src/greeter.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use common::{Endpoint, Inbox, Wire, GreeterPayload};
|
||||||
|
use iced::{futures::channel::mpsc, Subscription};
|
||||||
|
|
||||||
|
/// Route-key namespace owned by this service; keys look like `"greeter.hello"`.
|
||||||
|
pub const NAMESPACE: &str = "greeter.";
|
||||||
|
|
||||||
|
pub struct GreeterService;
|
||||||
|
|
||||||
|
impl GreeterService {
|
||||||
|
pub fn run(inbox: Inbox) -> Subscription<Wire> {
|
||||||
|
Subscription::run_with(inbox, |inbox| {
|
||||||
|
let key = inbox.key();
|
||||||
|
let mut _rx = inbox.take().expect("inbox receiver is taken once");
|
||||||
|
|
||||||
|
iced::stream::channel(0, move |mut sender: mpsc::Sender<Wire>| async move {
|
||||||
|
let mut interval = tokio::time::interval(Duration::from_secs(5));
|
||||||
|
loop {
|
||||||
|
interval.tick().await;
|
||||||
|
// Fan-out: publish to every module subscribed to `key`.
|
||||||
|
let wire = Wire::topic(Endpoint::service(key), key, GreeterPayload {
|
||||||
|
value: "hello".into(),
|
||||||
|
});
|
||||||
|
if sender.send(wire).await.is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Add one arm to the `IntoSubscription` match in `crates/services/src/lib.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub mod greeter;
|
||||||
|
|
||||||
|
impl IntoSubscription for Service {
|
||||||
|
fn into_subscription(self, inbox: Inbox) -> Subscription<Wire> {
|
||||||
|
match self.0 {
|
||||||
|
key if key.starts_with(datetime::NAMESPACE) => datetime::ClockTicker::run(inbox),
|
||||||
|
key if key.starts_with(weather::NAMESPACE) => weather::WeatherService::run(inbox),
|
||||||
|
key if key.starts_with(greeter::NAMESPACE) => greeter::GreeterService::run(inbox),
|
||||||
|
_ => Subscription::none(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then a module subscribes by returning `GreeterKind::Hello.key()` from its
|
||||||
|
`services()` and downcasts `GreeterPayload` in `update()`.
|
||||||
|
|
||||||
|
`run_with(inbox, ..)` keys the subscription by the inbox route key, so the
|
||||||
|
same subscription returned on every rebuild is not restarted. The receiver is
|
||||||
|
taken once inside the closure — don't call `take()` anywhere else.
|
||||||
@@ -22,7 +22,7 @@ impl IntoSubscription for Service {
|
|||||||
fn into_subscription(self, inbox: Inbox) -> Subscription<Wire> {
|
fn into_subscription(self, inbox: Inbox) -> Subscription<Wire> {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
key if key.starts_with(datetime::NAMESPACE) => datetime::ClockTicker::run(inbox),
|
key if key.starts_with(datetime::NAMESPACE) => datetime::ClockTicker::run(inbox),
|
||||||
key if key.starts_with(weather::NAMESPACE) => datetime::ClockTicker::run(inbox),
|
key if key.starts_with(weather::NAMESPACE) => weather::WeatherService::run(inbox),
|
||||||
_ => Subscription::none(),
|
_ => Subscription::none(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,53 @@
|
|||||||
pub struct WeatherService {}
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use common::{Inbox, WeatherResponse, Wire};
|
||||||
|
use iced::{futures::channel::mpsc, Subscription};
|
||||||
|
use tokio::time::interval;
|
||||||
|
|
||||||
pub const NAMESPACE: &str = "weather.";
|
pub const NAMESPACE: &str = "weather.";
|
||||||
|
|
||||||
|
const FORECAST_URL: &str = "https://api.open-meteo.com/v1/forecast\
|
||||||
|
?latitude=51.3714&longitude=-0.2302\
|
||||||
|
&hourly=temperature_2m,rain,showers,precipitation,relative_humidity_2m";
|
||||||
|
|
||||||
|
pub struct WeatherService {
|
||||||
|
client: reqwest::Client,
|
||||||
|
latest: Option<WeatherResponse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WeatherService {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
client: reqwest::Client::default(),
|
||||||
|
latest: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn run(inbox: Inbox) -> Subscription<Wire> {
|
||||||
|
Subscription::run_with(inbox, |inbox| {
|
||||||
|
let _key = inbox.key();
|
||||||
|
let mut _rx = inbox.take().expect("cant take");
|
||||||
|
|
||||||
|
iced::stream::channel(0, move |mut _sender: mpsc::Sender<Wire>| async move {
|
||||||
|
let mut weather = WeatherService::new();
|
||||||
|
let mut interval = interval(Duration::from_mins(30));
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
_ = interval.tick() => {
|
||||||
|
weather.latest = weather.get_weather().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async fn get_weather(&self) -> Option<WeatherResponse> {
|
||||||
|
self.client
|
||||||
|
.get(FORECAST_URL)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.ok()?
|
||||||
|
.json::<WeatherResponse>()
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user