ai: add tooltip for location/update time
This commit is contained in:
parent
1c4fc30f4b
commit
e45b1fc1b6
1 changed files with 66 additions and 9 deletions
75
src/app.rs
75
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::alignment::{Horizontal, Vertical};
|
||||||
use cosmic::iced::platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup};
|
use cosmic::iced::platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup};
|
||||||
use cosmic::widget::{self, autosize, container, rectangle_tracker::*};
|
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::applet::cosmic_panel_config::PanelAnchor;
|
||||||
use cosmic::iced_core::Shadow;
|
use cosmic::iced_core::Shadow;
|
||||||
use futures_util::SinkExt;
|
use futures_util::SinkExt;
|
||||||
|
|
@ -17,8 +17,12 @@ const WEATHER_UPDATE_INTERVAL_MINUTES: u64 = 15;
|
||||||
pub struct AppModel {
|
pub struct AppModel {
|
||||||
/// Application state which is managed by the COSMIC runtime.
|
/// Application state which is managed by the COSMIC runtime.
|
||||||
core: cosmic::Core,
|
core: cosmic::Core,
|
||||||
/// Current weather data
|
/// Current weather data (icon|temp)
|
||||||
weather_text: String,
|
weather_text: String,
|
||||||
|
/// Detected location from wttr.in
|
||||||
|
location: String,
|
||||||
|
/// Last successful update time
|
||||||
|
last_updated: String,
|
||||||
/// Full weather report for popup
|
/// Full weather report for popup
|
||||||
full_weather: String,
|
full_weather: String,
|
||||||
/// Loading state
|
/// Loading state
|
||||||
|
|
@ -34,12 +38,13 @@ pub struct AppModel {
|
||||||
/// Messages emitted by the applet.
|
/// Messages emitted by the applet.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
WeatherUpdate(Result<String, String>),
|
WeatherUpdate(Result<(String, String), String>),
|
||||||
FullWeatherUpdate(Result<String, String>),
|
FullWeatherUpdate(Result<String, String>),
|
||||||
RefreshWeather,
|
RefreshWeather,
|
||||||
TogglePopup,
|
TogglePopup,
|
||||||
CloseRequested(window::Id),
|
CloseRequested(window::Id),
|
||||||
Rectangle(RectangleUpdate<u32>),
|
Rectangle(RectangleUpdate<u32>),
|
||||||
|
Surface(surface::Action),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl cosmic::Application for AppModel {
|
impl cosmic::Application for AppModel {
|
||||||
|
|
@ -63,6 +68,8 @@ impl cosmic::Application for AppModel {
|
||||||
let app = AppModel {
|
let app = AppModel {
|
||||||
core,
|
core,
|
||||||
weather_text: "Loading...".to_string(),
|
weather_text: "Loading...".to_string(),
|
||||||
|
location: String::new(),
|
||||||
|
last_updated: String::new(),
|
||||||
full_weather: String::new(),
|
full_weather: String::new(),
|
||||||
is_loading: true,
|
is_loading: true,
|
||||||
popup: None,
|
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 limits = Limits::NONE.min_width(1.).min_height(1.);
|
||||||
|
|
||||||
let element: Element<'_, Self::Message> = if let Some(tracker) = self.rectangle_tracker.as_ref() {
|
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 {
|
} else {
|
||||||
container(button).into()
|
container(tooltip).into()
|
||||||
};
|
};
|
||||||
|
|
||||||
autosize::autosize(
|
autosize::autosize(
|
||||||
|
|
@ -206,7 +230,11 @@ impl cosmic::Application for AppModel {
|
||||||
Message::WeatherUpdate(result) => {
|
Message::WeatherUpdate(result) => {
|
||||||
self.is_loading = false;
|
self.is_loading = false;
|
||||||
match result {
|
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) => {
|
Err(e) => {
|
||||||
eprintln!("Weather fetch error: {}", e);
|
eprintln!("Weather fetch error: {}", e);
|
||||||
self.weather_text = if e.contains("network") || e.contains("timeout") {
|
self.weather_text = if e.contains("network") || e.contains("timeout") {
|
||||||
|
|
@ -307,17 +335,46 @@ impl cosmic::Application for AppModel {
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
Message::Surface(action) => {
|
||||||
|
cosmic::task::message(cosmic::Action::Cosmic(
|
||||||
|
cosmic::app::Action::Surface(action),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_weather() -> Result<String, String> {
|
async fn fetch_weather() -> Result<(String, String), String> {
|
||||||
let response = reqwest::get(&format!("{}/?format=%c|%t", WTTR_URL))
|
let response = reqwest::get(&format!("{}/?format=%l|%c|%t", WTTR_URL))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let text = response.text().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> {
|
async fn fetch_full_weather() -> Result<String, String> {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue