瀏覽代碼

add weather_app to examples, update for 0.5

Jeremy Arnold 1 年之前
父節點
當前提交
89ae287fed
共有 2 個文件被更改,包括 263 次插入0 次删除
  1. 4 0
      Cargo.toml
  2. 259 0
      examples/weather_app.rs

+ 4 - 0
Cargo.toml

@@ -171,3 +171,7 @@ required-features = ["http"]
 [[example]]
 name = "suspense"
 required-features = ["http"]
+
+[[example]]
+name = "weather_app"
+required-features = ["http"]

+ 259 - 0
examples/weather_app.rs

@@ -0,0 +1,259 @@
+#![allow(non_snake_case)]
+
+use dioxus::prelude::*;
+use serde::{Deserialize, Serialize};
+
+fn main() {
+    launch(app);
+}
+
+fn app() -> Element {
+    let country = use_signal(|| WeatherLocation {
+        name: "Berlin".to_string(),
+        country: "Germany".to_string(),
+        latitude: 52.5244,
+        longitude: 13.4105,
+        id: 2950159,
+    });
+
+    let current_weather =
+        use_resource(move || async move { get_weather(&country.read().clone()).await });
+
+    rsx! {
+        link {
+            rel: "stylesheet",
+            href: "https://unpkg.com/tailwindcss@^2.0/dist/tailwind.min.css"
+        }
+        div { class: "mx-auto p-4 bg-gray-100 h-screen flex justify-center",
+            div { class: "flex items-center justify-center flex-row",
+                div { class: "flex items-start justify-center flex-row",
+                    SearchBox { country: country }
+                    div { class: "flex flex-wrap w-full px-2",
+                        div { class: "bg-gray-900 text-white relative min-w-0 break-words rounded-lg overflow-hidden shadow-sm mb-4 w-full bg-white dark:bg-gray-600",
+                            div { class: "px-6 py-6 relative",
+                                if let Some(Ok(weather)) = current_weather.read().as_ref() {
+                                    CountryData {
+                                        country: country.read().clone(),
+                                        weather: weather.clone(),
+                                    }
+                                    Forecast {
+                                        weather: weather.clone(),
+                                    }
+
+                                } else {
+                                    p {
+                                        "Loading.."
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+#[allow(non_snake_case)]
+#[component]
+fn CountryData(weather: WeatherResponse, country: WeatherLocation) -> Element {
+    let today = "Today";
+    let max_temp = weather.daily.temperature_2m_max.first().unwrap();
+    let min_temp = weather.daily.temperature_2m_min.first().unwrap();
+
+    rsx! {
+        div { class: "flex mb-4 justify-between items-center",
+            div {
+                h5 { class: "mb-0 font-medium text-xl", "{country.name} 🏞️" }
+                h6 { class: "mb-0", "{today}" }
+            }
+            div {
+                div { class: "flex items-center",
+                    span { "Temp min" }
+                    span { class: "px-2 inline-block", "👉 {min_temp}°" }
+                }
+                div { class: "flex items-center",
+                    span { "Temp max" }
+                    span { class: "px-2 inline-block ", "👉 {max_temp}º" }
+                }
+            }
+        }
+    }
+}
+
+#[allow(non_snake_case)]
+#[component]
+fn Forecast(weather: WeatherResponse) -> Element {
+    let today = (weather.daily.temperature_2m_max.first().unwrap()
+        + weather.daily.temperature_2m_max.first().unwrap())
+        / 2.0;
+    let tomorrow = (weather.daily.temperature_2m_max.get(1).unwrap()
+        + weather.daily.temperature_2m_max.get(1).unwrap())
+        / 2.0;
+    let past_tomorrow = (weather.daily.temperature_2m_max.get(2).unwrap()
+        + weather.daily.temperature_2m_max.get(2).unwrap())
+        / 2.0;
+    rsx! {
+        div { class: "px-6 pt-4 relative",
+            div { class: "w-full h-px bg-gray-100 mb-4" }
+            div { p { class: "text-center w-full mb-4", "👇 Forecast 📆" } }
+            div { class: "text-center justify-between items-center flex",
+                div { class: "text-center mb-0 flex items-center justify-center flex-col mx-4 w-16",
+                    span { class: "block my-1", "Today" }
+                    span { class: "block my-1", "{today}°" }
+                }
+                div { class: "text-center mb-0 flex items-center justify-center flex-col mx-8 w-16",
+                    span { class: "block my-1", "Tomorrow" }
+                    span { class: "block my-1", "{tomorrow}°" }
+                }
+                div { class: "text-center mb-0 flex items-center justify-center flex-col mx-2 w-30",
+                    span { class: "block my-1", "Past Tomorrow" }
+                    span { class: "block my-1", "{past_tomorrow}°" }
+                }
+            }
+        }
+    }
+}
+
+#[component]
+fn SearchBox(mut country: Signal<WeatherLocation>) -> Element {
+    let mut input = use_signal(|| "".to_string());
+
+    let locations = use_resource(move || async move {
+        let current_location = input.read().clone();
+        get_locations(&current_location).await
+    });
+
+    rsx! {
+        div {
+            div { class: "inline-flex flex-col justify-center relative text-gray-500",
+                div { class: "relative",
+                    input {
+                        class: "p-2 pl-8 rounded-lg border border-gray-200 bg-gray-200 focus:bg-white focus:outline-none focus:ring-2 focus:ring-yellow-600 focus:border-transparent",
+                        placeholder: "Country name",
+                        "type": "text",
+                        autofocus: true,
+                        oninput: move |e| input.set(e.value())
+                    }
+                    svg {
+                        class: "w-4 h-4 absolute left-2.5 top-3.5",
+                        "viewBox": "0 0 24 24",
+                        fill: "none",
+                        stroke: "currentColor",
+                        xmlns: "http://www.w3.org/2000/svg",
+                        path {
+                            d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z",
+                            "stroke-linejoin": "round",
+                            "stroke-linecap": "round",
+                            "stroke-width": "2"
+                        }
+                    }
+                }
+                ul { class: "bg-white border border-gray-100 w-full mt-2 max-h-72 overflow-auto",
+                    {
+                        if let Some(Ok(locs)) = locations.read().as_ref() {
+                            rsx! {
+                                {
+                                    locs.iter().cloned().map(move |wl| {
+                                        rsx! {
+                                            li { class: "pl-8 pr-2 py-1 border-b-2 border-gray-100 relative cursor-pointer hover:bg-yellow-50 hover:text-gray-900",
+                                                onclick: move |_| country.set(wl.clone()),
+                                                MapIcon {}
+                                                b {
+                                                    "{wl.name}"
+                                                }
+                                                " · {wl.country}"
+                                            }
+                                        }
+                                    })
+                                }
+                            }
+                        } else {
+                            rsx! { "loading locations..." }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+fn MapIcon() -> Element {
+    rsx! {
+        svg {
+            class: "stroke-current absolute w-4 h-4 left-2 top-2",
+            stroke: "currentColor",
+            xmlns: "http://www.w3.org/2000/svg",
+            "viewBox": "0 0 24 24",
+            fill: "none",
+            path {
+                "stroke-linejoin": "round",
+                "stroke-width": "2",
+                "stroke-linecap": "round",
+                d: "M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
+            }
+            path {
+                "stroke-linecap": "round",
+                "stroke-linejoin": "round",
+                d: "M15 11a3 3 0 11-6 0 3 3 0 016 0z",
+                "stroke-width": "2"
+            }
+        }
+    }
+}
+
+#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
+struct WeatherLocation {
+    id: usize,
+    name: String,
+    latitude: f32,
+    longitude: f32,
+    country: String,
+}
+
+type WeatherLocations = Vec<WeatherLocation>;
+
+#[derive(Debug, Default, Serialize, Deserialize)]
+struct SearchResponse {
+    results: WeatherLocations,
+}
+
+async fn get_locations(input: &str) -> reqwest::Result<WeatherLocations> {
+    let res = reqwest::get(&format!(
+        "https://geocoding-api.open-meteo.com/v1/search?name={input}"
+    ))
+    .await?
+    .json::<SearchResponse>()
+    .await?;
+
+    Ok(res.results)
+}
+
+#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
+struct WeatherResponse {
+    daily: DailyWeather,
+    hourly: HourlyWeather,
+}
+
+#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
+struct HourlyWeather {
+    time: Vec<String>,
+    temperature_2m: Vec<f32>,
+}
+
+#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
+struct DailyWeather {
+    temperature_2m_min: Vec<f32>,
+    temperature_2m_max: Vec<f32>,
+}
+
+async fn get_weather(location: &WeatherLocation) -> reqwest::Result<WeatherResponse> {
+    let res = reqwest::get(&format!("https://api.open-meteo.com/v1/forecast?latitude={}&longitude={}&hourly=temperature_2m&daily=temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min&timezone=GMT", location.latitude, location.longitude))
+        .await
+        ?
+        .json::<WeatherResponse>()
+        .await
+        ?;
+
+    Ok(res)
+}