ai: add tooltip for location/update time

This commit is contained in:
Emil Lerch 2026-03-02 14:04:05 -08:00
parent 1c4fc30f4b
commit e45b1fc1b6
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -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<String, String>),
WeatherUpdate(Result<(String, String), String>),
FullWeatherUpdate(Result<String, String>),
RefreshWeather,
TogglePopup,
CloseRequested(window::Id),
Rectangle(RectangleUpdate<u32>),
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<String, String> {
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<String, String> {