fix failed full weather fetch and implement cache for full
Some checks failed
CI / Clippy (push) Failing after 1m32s
CI / Build (release) (push) Successful in 2m27s
CI / Build (debug) (push) Successful in 3m44s
CI / Notify (push) Successful in 2s

This commit is contained in:
Emil Lerch 2026-04-20 05:16:16 -07:00
parent e155018474
commit 21a2e5299c
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -51,6 +51,10 @@ pub struct AppModel {
last_updated: String,
/// Full weather report for popup
full_weather: String,
/// Last error from a full-weather fetch, if any. Kept separate from
/// `full_weather` so that a failed fetch does not poison the cache
/// and block the next popup-open from retrying.
full_weather_error: Option<String>,
/// Loading state
is_loading: bool,
/// Popup window ID
@ -102,6 +106,7 @@ impl cosmic::Application for AppModel {
location: String::new(),
last_updated: String::new(),
full_weather: String::new(),
full_weather_error: None,
is_loading: true,
popup: None,
rectangle: Rectangle::default(),
@ -175,11 +180,24 @@ impl cosmic::Application for AppModel {
}
fn view_window(&self, _id: window::Id) -> Element<'_, Self::Message> {
// Pick what to render: the cached forecast, a transient error
// (not stored in `full_weather` so the next open retries), or a
// loading placeholder while the in-flight request completes.
let display_text: String = if !self.full_weather.is_empty() {
self.full_weather.clone()
} else if let Some(err) = &self.full_weather_error {
format!(
"Failed to load weather:\n{err}\n\nClose and reopen to retry."
)
} else {
"Loading…".to_string()
};
// Calculate the width needed for the monospace content.
// The default monospace font is ~8.4px per character at 14px font size,
// plus padding for container (12*2) and popup chrome (~32).
#[allow(clippy::cast_precision_loss)]
let max_line_len = self.full_weather.lines()
let max_line_len = display_text.lines()
.map(|line| line.chars().count())
.max()
.unwrap_or(40) as f32;
@ -188,7 +206,7 @@ impl cosmic::Application for AppModel {
let popup_width = content_width.max(400.0);
let content = container(
widget::text(&self.full_weather)
widget::text(display_text)
.font(Font::MONOSPACE)
.wrapping(cosmic::iced::widget::text::Wrapping::None)
.width(cosmic::iced::Length::Fixed(content_width))
@ -351,16 +369,25 @@ impl cosmic::Application for AppModel {
match result {
Ok(weather) => {
self.full_weather = weather;
self.full_weather_error = None;
}
Err(e) => {
eprintln!("Full weather fetch error: {e}");
self.full_weather = "Failed to load weather".to_string();
// Leave `full_weather` empty so that the next
// popup open triggers a refetch (see toggle_popup).
self.full_weather_error = Some(e);
}
}
Task::none()
}
Message::RefreshWeather => {
self.retry_count = 0;
// Invalidate the popup cache on each scheduled refresh so
// that the next popup open fetches a fresh forecast. This
// piggybacks the popup's freshness on the panel's 15-minute
// refresh cadence (WEATHER_UPDATE_INTERVAL_MINUTES).
self.full_weather.clear();
self.full_weather_error = None;
let units = self.units;
Task::perform(fetch_weather(units), |result| {
cosmic::Action::App(Message::WeatherUpdate(result))
@ -371,6 +398,7 @@ impl cosmic::Application for AppModel {
self.retry_count = 0;
// Clear cached full weather so the popup re-fetches on next open
self.full_weather.clear();
self.full_weather_error = None;
let units = self.units;
Task::perform(fetch_weather(units), |result| {
cosmic::Action::App(Message::WeatherUpdate(result))