commit 517d3016c8e7eb589cdf0aadb4aba0f9c6460d8d
parent 49085bf580622d1182b4248601d7c9c78a3c63c6
Author: Sylvia Ivory <git@sivory.net>
Date: Wed, 18 Jun 2025 22:50:12 -0700
Implement geocode search
Diffstat:
| M | src/main.rs | | | 12 | ++++++++++-- |
| M | src/settings.rs | | | 101 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ |
2 files changed, 96 insertions(+), 17 deletions(-)
diff --git a/src/main.rs b/src/main.rs
@@ -1,9 +1,10 @@
-use std::{cell::RefCell, rc::Rc};
+use std::{cell::RefCell, rc::Rc, sync::Arc};
use chrono::{
DateTime, Local,
format::{Item, StrftimeItems},
};
+use fjordgard_weather::MeteoClient;
use iced::{
Color, Element, Font, Length, Size, Subscription, Task,
font::Weight,
@@ -23,6 +24,7 @@ mod settings;
struct Fjordgard {
config: Rc<RefCell<Config>>,
+ meteo: Arc<MeteoClient>,
time: DateTime<Local>,
background: BackgroundKind,
format_string: String,
@@ -62,9 +64,12 @@ impl Fjordgard {
.parse_to_owned()
.unwrap();
+ let meteo = MeteoClient::new(None).unwrap();
+
(
Self {
config: Rc::new(RefCell::new(config)),
+ meteo: Arc::new(meteo),
time: Local::now(),
background: BackgroundKind::Color(Color::from_rgb8(255, 255, 255)),
format_string,
@@ -109,7 +114,10 @@ impl Fjordgard {
..Default::default()
});
- self.settings_window = Some(settings::Settings::new(self.config.clone()));
+ self.settings_window = Some(settings::Settings::new(
+ self.config.clone(),
+ self.meteo.clone(),
+ ));
open.map(Message::SettingsOpened)
} else {
diff --git a/src/settings.rs b/src/settings.rs
@@ -1,11 +1,9 @@
-use std::{cell::RefCell, rc::Rc};
+use std::{cell::RefCell, rc::Rc, sync::Arc};
+use fjordgard_weather::{MeteoClient, model::Location};
use iced::{
Background, Border, Color, Element, Length, Task, Theme,
- widget::{
- button, column, combo_box, container, row, scrollable, text, text_input, vertical_space,
- },
- window,
+ widget::{button, column, combo_box, container, row, scrollable, text, text_input, tooltip},
};
use rfd::{AsyncFileDialog, FileHandle};
use strum::VariantArray;
@@ -20,7 +18,8 @@ pub enum WeatherLocation {
Coordinates,
}
-pub struct Location {
+#[derive(Debug, Clone)]
+pub struct LocationRow {
name: String,
latitude: f64,
longitude: f64,
@@ -28,6 +27,7 @@ pub struct Location {
pub struct Settings {
config: Rc<RefCell<Config>>,
+ meteo: Arc<MeteoClient>,
backgrounds: combo_box::State<BackgroundMode>,
locations: combo_box::State<WeatherLocation>,
file_selector_open: bool,
@@ -41,7 +41,8 @@ pub struct Settings {
latitude: String,
longitude: String,
- location_results: Vec<Location>,
+ location_results: Vec<LocationRow>,
+ location_fetch_error: Option<String>,
}
#[derive(Debug, Clone)]
@@ -52,6 +53,8 @@ pub enum Message {
Location(WeatherLocation),
Name(String),
NameSubmitted,
+ Geocode(Result<Vec<Location>, String>),
+ LocationSelected(LocationRow),
Latitude(String),
Longitude(String),
Save,
@@ -60,7 +63,7 @@ pub enum Message {
}
impl Settings {
- pub fn new(config: Rc<RefCell<Config>>) -> Self {
+ pub fn new(config: Rc<RefCell<Config>>, meteo: Arc<MeteoClient>) -> Self {
let original_config = config.borrow().clone();
let location = original_config.location;
@@ -89,6 +92,7 @@ impl Settings {
Self {
config,
+ meteo,
backgrounds: combo_box::State::new(BackgroundMode::VARIANTS.to_vec()),
locations: combo_box::State::new(WeatherLocation::VARIANTS.to_vec()),
file_selector_open: false,
@@ -103,6 +107,7 @@ impl Settings {
name,
location_results: vec![],
+ location_fetch_error: None,
}
}
@@ -129,6 +134,48 @@ impl Settings {
self.name = name;
Task::none()
}
+ Message::NameSubmitted => {
+ self.location_fetch_error = None;
+ let meteo = self.meteo.clone();
+ let name = self.name.clone();
+
+ Task::future(async move { meteo.geocode(&name, None).await })
+ .map(|r| Message::Geocode(r.map_err(|e| e.to_string())))
+ }
+ Message::Geocode(locations) => {
+ match locations {
+ Err(s) => {
+ self.location_fetch_error = Some(s);
+ }
+ Ok(res) => {
+ self.location_results = res
+ .iter()
+ .map(|l| {
+ let level1 = if let Some(admin1) = &l.admin1 {
+ format!(", {admin1}")
+ } else {
+ String::new()
+ };
+
+ LocationRow {
+ name: format!("{}{level1}, {}", l.name, l.country),
+ latitude: l.latitude,
+ longitude: l.longitude,
+ }
+ })
+ .collect()
+ }
+ };
+
+ Task::none()
+ }
+ Message::LocationSelected(loc) => {
+ self.name = loc.name;
+ self.latitude = loc.latitude.to_string();
+ self.longitude = loc.longitude.to_string();
+
+ Task::none()
+ }
Message::Latitude(latitude) => {
self.latitude = latitude;
Task::none()
@@ -229,10 +276,39 @@ impl Settings {
"{} ({}, {})",
res.name, res.latitude, res.longitude
)))
- .style(button::text),
+ .style(button::text)
+ .on_press_with(|| Message::LocationSelected(res.clone())),
)
}
+ let location_style = if self.location_fetch_error.is_some() {
+ save_message = None;
+ text_input_error
+ } else {
+ text_input::default
+ };
+
+ let mut location_row: Element<Message> = row![
+ text("Location").width(Length::FillPortion(1)),
+ text_input("", &self.name)
+ .width(Length::FillPortion(2))
+ .on_input_maybe(name)
+ .on_submit(Message::NameSubmitted)
+ .style(location_style)
+ ]
+ .into();
+
+ if let Some(err) = &self.location_fetch_error {
+ location_row = tooltip(
+ location_row,
+ container(err.as_ref())
+ .padding(5)
+ .style(container::rounded_box),
+ tooltip::Position::Top,
+ )
+ .into()
+ };
+
scrollable(
container(
column![
@@ -272,12 +348,7 @@ impl Settings {
.on_input_maybe(longitude)
.style(longitude_style)
],
- row![
- text("Location").width(Length::FillPortion(1)),
- text_input("", &self.name)
- .width(Length::FillPortion(2))
- .on_input_maybe(name),
- ],
+ location_row,
scrollable(results)
.height(Length::Fixed(
64.0 * (self.location_results.len().clamp(0, 1) as f32)