added 'days until' and UI improvements
This commit is contained in:
@@ -42,6 +42,12 @@ struct WeatherState {
|
||||
tomorrow_summary: String,
|
||||
}
|
||||
|
||||
struct Countdown {
|
||||
label: String,
|
||||
month: u32,
|
||||
day: u32,
|
||||
}
|
||||
|
||||
fn get_config_dir() -> PathBuf {
|
||||
if let Ok(home) = env::var("HOME") {
|
||||
PathBuf::from(home).join(".config/hypr/scripts/statmon")
|
||||
@@ -112,8 +118,9 @@ fn fetch_weather_and_forecast(api_key: &str) -> (String, String) {
|
||||
if let Some(list) = json["list"].as_array() {
|
||||
let mut max_pop_today = 0.0f64;
|
||||
let mut max_pop_tomorrow = 0.0f64;
|
||||
let mut today_rain = false;
|
||||
let mut tomorrow_rain = false;
|
||||
|
||||
let mut max_rain_id_today: Option<u64> = None;
|
||||
let mut max_rain_id_tomorrow: Option<u64> = None;
|
||||
|
||||
let now = Local::now().date_naive();
|
||||
let tomorrow = now + chrono::Days::new(1);
|
||||
@@ -122,31 +129,53 @@ fn fetch_weather_and_forecast(api_key: &str) -> (String, String) {
|
||||
if let Some(dt_txt) = item["dt_txt"].as_str() {
|
||||
if let Ok(date) = NaiveDate::parse_from_str(&dt_txt[..10], "%Y-%m-%d") {
|
||||
let pop = item["pop"].as_f64().unwrap_or(0.0);
|
||||
let weather_main = item["weather"][0]["main"].as_str().unwrap_or("");
|
||||
|
||||
// Extract rain condition IDs in the 3xx (drizzle) and 5xx (rain) ranges
|
||||
let weather_ids: Vec<u64> = item["weather"]
|
||||
.as_array()
|
||||
.map(|arr| {
|
||||
arr.iter()
|
||||
.filter_map(|w| w["id"].as_u64())
|
||||
.filter(|&id| (300..600).contains(&id))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let max_block_id = weather_ids.into_iter().max();
|
||||
|
||||
if date == now {
|
||||
if pop > max_pop_today { max_pop_today = pop; }
|
||||
if weather_main.to_lowercase().contains("rain") || pop > 0.3 {
|
||||
today_rain = true;
|
||||
if let Some(id) = max_block_id {
|
||||
max_rain_id_today = Some(max_rain_id_today.map_or(id, |m| m.max(id)));
|
||||
}
|
||||
} else if date == tomorrow {
|
||||
if pop > max_pop_tomorrow { max_pop_tomorrow = pop; }
|
||||
if weather_main.to_lowercase().contains("rain") || pop > 0.3 {
|
||||
tomorrow_rain = true;
|
||||
if let Some(id) = max_block_id {
|
||||
max_rain_id_tomorrow = Some(max_rain_id_tomorrow.map_or(id, |m| m.max(id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper closure to map max weather ID to human-readable status
|
||||
let classify_rain = |max_id: Option<u64>| -> &'static str {
|
||||
match max_id {
|
||||
Some(502..=504 | 522 | 531) => "Heavy Rain",
|
||||
Some(501 | 521) => "Moderate Rain",
|
||||
Some(500 | 520 | 300..=321) => "Light Rain",
|
||||
_ => "Clear/No Major Rain",
|
||||
}
|
||||
};
|
||||
|
||||
today_str = format!(
|
||||
"{} (Rain Chance: {:.0}%)",
|
||||
if today_rain { "Rain Expected" } else { "Clear/No Major Rain" },
|
||||
classify_rain(max_rain_id_today),
|
||||
max_pop_today * 100.0
|
||||
);
|
||||
tomorrow_str = format!(
|
||||
"{} (Rain Chance: {:.0}%)",
|
||||
if tomorrow_rain { "Rain Expected" } else { "Clear/No Major Rain" },
|
||||
classify_rain(max_rain_id_tomorrow),
|
||||
max_pop_tomorrow * 100.0
|
||||
);
|
||||
}
|
||||
@@ -181,9 +210,46 @@ fn get_upcoming_birthdays() -> Vec<String> {
|
||||
upcoming
|
||||
}
|
||||
|
||||
fn render_calendar_and_birthdays(
|
||||
fn get_countdowns() -> Vec<String> {
|
||||
let today = Local::now().date_naive();
|
||||
let current_year = today.year();
|
||||
|
||||
let targets = vec![
|
||||
Countdown { label: "Avy's Birthday".to_string(), month: 4, day: 6 },
|
||||
Countdown { label: "4th of July".to_string(), month: 7, day: 4 },
|
||||
Countdown { label: "Cal's Birthday".to_string(), month: 9, day: 24 },
|
||||
Countdown { label: "Halloween".to_string(), month: 10, day: 31 },
|
||||
Countdown { label: "Christmas".to_string(), month: 12, day: 25 },
|
||||
];
|
||||
|
||||
let mut results = Vec::new();
|
||||
|
||||
for t in targets {
|
||||
let target_date = if let Some(d) = NaiveDate::from_ymd_opt(current_year, t.month, t.day) {
|
||||
if d >= today {
|
||||
d
|
||||
} else {
|
||||
NaiveDate::from_ymd_opt(current_year + 1, t.month, t.day).unwrap()
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let diff = target_date.signed_duration_since(today).num_days();
|
||||
if diff == 0 {
|
||||
results.push(format!("{:<14}: TODAY!", t.label));
|
||||
} else {
|
||||
results.push(format!("{:<14}: {} days", t.label, diff));
|
||||
}
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
fn render_bottom_section(
|
||||
stdout: &mut std::io::Stdout,
|
||||
upcoming_birthdays: &[String],
|
||||
countdowns: &[String],
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let now = Local::now();
|
||||
let year = now.year();
|
||||
@@ -202,14 +268,15 @@ fn render_calendar_and_birthdays(
|
||||
|
||||
let start_weekday = first_of_month.weekday().num_days_from_sunday();
|
||||
|
||||
// Side-by-Side Headers
|
||||
// 3-Column Headers
|
||||
execute!(stdout, SetForegroundColor(ACCENT))?;
|
||||
write!(stdout, " {:<27}", format!("CALENDAR ({} {})", now.format("%B"), year))?;
|
||||
execute!(stdout, SetForegroundColor(ACCENT))?;
|
||||
writeln!(stdout, "UPCOMING EVENTS")?;
|
||||
write!(stdout, "{:<34}", "UPCOMING EVENTS")?;
|
||||
writeln!(stdout, "DAYS UNTIL")?;
|
||||
|
||||
execute!(stdout, SetForegroundColor(FG_MAIN))?;
|
||||
write!(stdout, " Su Mo Tu We Th Fr Sa ")?;
|
||||
write!(stdout, "--------------------------------- ")?;
|
||||
writeln!(stdout, "----------------------------------")?;
|
||||
|
||||
// Build Calendar Lines
|
||||
@@ -239,9 +306,13 @@ fn render_calendar_and_birthdays(
|
||||
}
|
||||
}
|
||||
|
||||
let total_rows = calendar_lines.len().max(upcoming_birthdays.len().max(1));
|
||||
let total_rows = calendar_lines
|
||||
.len()
|
||||
.max(upcoming_birthdays.len().max(1))
|
||||
.max(countdowns.len());
|
||||
|
||||
for i in 0..total_rows {
|
||||
// Col 1: Calendar
|
||||
execute!(stdout, SetForegroundColor(FG_MAIN))?;
|
||||
if i < calendar_lines.len() {
|
||||
write!(stdout, " {} ", calendar_lines[i])?;
|
||||
@@ -251,12 +322,23 @@ fn render_calendar_and_birthdays(
|
||||
|
||||
write!(stdout, " ")?;
|
||||
|
||||
// Col 2: Upcoming Events
|
||||
if i < upcoming_birthdays.len() {
|
||||
execute!(stdout, SetForegroundColor(ACCENT))?;
|
||||
writeln!(stdout, "• {}", upcoming_birthdays[i])?;
|
||||
write!(stdout, "{:<32}", format!("• {}", upcoming_birthdays[i]))?;
|
||||
} else if i == 0 && upcoming_birthdays.is_empty() {
|
||||
execute!(stdout, SetForegroundColor(FG_MAIN))?;
|
||||
writeln!(stdout, "No events in next 2 days")?;
|
||||
write!(stdout, "{:<32}", "No events in next 2 days")?;
|
||||
} else {
|
||||
write!(stdout, " ")?;
|
||||
}
|
||||
|
||||
write!(stdout, " ")?;
|
||||
|
||||
// Col 3: Days Until
|
||||
if i < countdowns.len() {
|
||||
execute!(stdout, SetForegroundColor(ACCENT))?;
|
||||
writeln!(stdout, "• {}", countdowns[i])?;
|
||||
} else {
|
||||
writeln!(stdout)?;
|
||||
}
|
||||
@@ -422,7 +504,6 @@ fn print_line(stdout: &mut std::io::Stdout, label: &str, value: &str, val_color:
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Load .env from ~/.config/hypr/scripts/statmon/.env
|
||||
let env_path = get_config_dir().join(".env");
|
||||
let _ = dotenvy::from_path(&env_path);
|
||||
|
||||
@@ -453,8 +534,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tomorrow_summary: "Fetching...".to_string(),
|
||||
};
|
||||
|
||||
let mut upcoming_birthdays: Vec<String>;
|
||||
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
|
||||
loop {
|
||||
@@ -467,7 +546,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
weather.aqi_desc = aqi_desc;
|
||||
weather.aqi_val = aqi_val;
|
||||
|
||||
if aqi_val >= 5 {
|
||||
if aqi_val >= 4 {
|
||||
send_notification("Air Quality Warning", &format!("Air quality in 95662 is concerning: {}", weather.aqi_desc), "critical");
|
||||
}
|
||||
last_aqi_fetch = Instant::now();
|
||||
@@ -481,8 +560,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
last_weather_fetch = Instant::now();
|
||||
}
|
||||
|
||||
// Always check birthdays on every refresh loop
|
||||
upcoming_birthdays = get_upcoming_birthdays();
|
||||
let upcoming_birthdays = get_upcoming_birthdays();
|
||||
let countdowns = get_countdowns();
|
||||
|
||||
sys.refresh_all();
|
||||
networks.refresh();
|
||||
@@ -589,10 +668,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
print_line(&mut stdout, "Weather Today", &weather.today_summary, FG_MAIN)?;
|
||||
print_line(&mut stdout, "Weather Tomorrow", &weather.tomorrow_summary, FG_MAIN)?;
|
||||
|
||||
// Calendar & Birthdays Section
|
||||
// Bottom Section (Calendar, Events, Days Until)
|
||||
execute!(stdout, SetForegroundColor(FG_MAIN))?;
|
||||
println!("-----------------------------------------------------------------------------------------------");
|
||||
render_calendar_and_birthdays(&mut stdout, &upcoming_birthdays)?;
|
||||
render_bottom_section(&mut stdout, &upcoming_birthdays, &countdowns)?;
|
||||
|
||||
execute!(stdout, SetForegroundColor(FG_MAIN))?;
|
||||
println!("===============================================================================================");
|
||||
|
||||
Reference in New Issue
Block a user