diff --git a/src/app.rs b/src/app.rs index 9b8e941..4970135 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,7 +4,7 @@ use cosmic::iced::{Color, Font, Limits, Rectangle, Subscription, window}; use cosmic::iced::alignment::{Horizontal, Vertical}; use cosmic::iced::platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup}; use cosmic::widget::{self, autosize, container, rectangle_tracker::*}; -use cosmic::{iced_futures, prelude::*}; +use cosmic::{iced_futures, prelude::*, surface}; use cosmic::applet::cosmic_panel_config::PanelAnchor; use cosmic::iced_core::Shadow; use futures_util::SinkExt; @@ -17,8 +17,12 @@ const WEATHER_UPDATE_INTERVAL_MINUTES: u64 = 15; pub struct AppModel { /// Application state which is managed by the COSMIC runtime. core: cosmic::Core, - /// Current weather data + /// Current weather data (icon|temp) weather_text: String, + /// Detected location from wttr.in + location: String, + /// Last successful update time + last_updated: String, /// Full weather report for popup full_weather: String, /// Loading state @@ -34,12 +38,13 @@ pub struct AppModel { /// Messages emitted by the applet. #[derive(Debug, Clone)] pub enum Message { - WeatherUpdate(Result), + WeatherUpdate(Result<(String, String), String>), FullWeatherUpdate(Result), RefreshWeather, TogglePopup, CloseRequested(window::Id), Rectangle(RectangleUpdate), + Surface(surface::Action), } impl cosmic::Application for AppModel { @@ -63,6 +68,8 @@ impl cosmic::Application for AppModel { let app = AppModel { core, weather_text: "Loading...".to_string(), + location: String::new(), + last_updated: String::new(), full_weather: String::new(), is_loading: true, popup: None, @@ -100,12 +107,29 @@ impl cosmic::Application for AppModel { } }; + let has_popup = self.popup.is_some(); + let tooltip_text = if self.location.is_empty() { + "Loading...".to_string() + } else if self.last_updated.is_empty() { + self.location.clone() + } else { + format!("{}\nUpdated: {}", self.location, self.last_updated) + }; + + let tooltip = self.core.applet.applet_tooltip( + button, + tooltip_text, + has_popup, + Message::Surface, + None, + ); + let limits = Limits::NONE.min_width(1.).min_height(1.); let element: Element<'_, Self::Message> = if let Some(tracker) = self.rectangle_tracker.as_ref() { - tracker.container(0, button).ignore_bounds(true).into() + tracker.container(0, tooltip).ignore_bounds(true).into() } else { - container(button).into() + container(tooltip).into() }; autosize::autosize( @@ -206,7 +230,11 @@ impl cosmic::Application for AppModel { Message::WeatherUpdate(result) => { self.is_loading = false; match result { - Ok(weather) => self.weather_text = weather, + Ok((location, weather)) => { + self.location = location; + self.weather_text = weather; + self.last_updated = format_current_time(); + } Err(e) => { eprintln!("Weather fetch error: {}", e); self.weather_text = if e.contains("network") || e.contains("timeout") { @@ -307,17 +335,46 @@ impl cosmic::Application for AppModel { } Task::none() } + Message::Surface(action) => { + cosmic::task::message(cosmic::Action::Cosmic( + cosmic::app::Action::Surface(action), + )) + } } } } -async fn fetch_weather() -> Result { - let response = reqwest::get(&format!("{}/?format=%c|%t", WTTR_URL)) +async fn fetch_weather() -> Result<(String, String), String> { + let response = reqwest::get(&format!("{}/?format=%l|%c|%t", WTTR_URL)) .await .map_err(|e| e.to_string())?; let text = response.text().await.map_err(|e| e.to_string())?; - Ok(text.trim().to_string()) + let text = text.trim(); + // Format: "location|icon|temp" + let parts: Vec<&str> = text.splitn(3, '|').collect(); + if parts.len() == 3 { + let location = parts[0].to_string(); + let weather = format!("{}|{}", parts[1], parts[2]); + Ok((location, weather)) + } else { + Ok((String::new(), text.to_string())) + } +} + +fn format_current_time() -> String { + // Include %H:%M in the wttr.in request would add another field to parse; + // instead, record the local time when we receive the update. + // We use /proc/self or a simple UTC-based approach. Since the applet + // runs locally, we can shell out or use libc. For simplicity, use + // the `date` command which respects the user's timezone. + std::process::Command::new("date") + .arg("+%H:%M") + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok()) + .map(|s| s.trim().to_string()) + .unwrap_or_default() } async fn fetch_full_weather() -> Result {