Add desktop notifications for budget threshold crossings
After saving an expense, checks if the category budget reaches 75%, 90%, or 100% thresholds. Sends gio::Notification for each newly crossed threshold. Tracks sent notifications in database to prevent duplicates within the same month.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use adw::prelude::*;
|
||||
use gtk::glib;
|
||||
use chrono::Datelike;
|
||||
use gtk::{gio, glib};
|
||||
use outlay_core::db::Database;
|
||||
use outlay_core::exchange::ExchangeRateService;
|
||||
use outlay_core::models::{NewTransaction, TransactionType};
|
||||
@@ -12,7 +13,7 @@ pub struct LogView {
|
||||
}
|
||||
|
||||
impl LogView {
|
||||
pub fn new(db: Rc<Database>) -> Self {
|
||||
pub fn new(db: Rc<Database>, app: &adw::Application) -> Self {
|
||||
let toast_overlay = adw::ToastOverlay::new();
|
||||
let container = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
|
||||
@@ -248,6 +249,7 @@ impl LogView {
|
||||
let toast_overlay_ref = toast_overlay.clone();
|
||||
let rate_ref = exchange_rate.clone();
|
||||
let currency_codes_save: Vec<String> = currency_codes.iter().map(|s| s.to_string()).collect();
|
||||
let app_ref = app.clone();
|
||||
|
||||
save_button.connect_clicked(move |_| {
|
||||
let amount_text = amount_row_ref.text();
|
||||
@@ -314,6 +316,29 @@ impl LogView {
|
||||
let toast = adw::Toast::new(msg);
|
||||
toast_overlay_ref.add_toast(toast);
|
||||
|
||||
// Check budget notifications for expenses
|
||||
if txn_type == TransactionType::Expense {
|
||||
let month_str = format!("{:04}-{:02}", date.year(), date.month());
|
||||
if let Ok(thresholds) = db_ref.check_budget_thresholds(category_id, &month_str) {
|
||||
let cat_name = db_ref
|
||||
.get_category(category_id)
|
||||
.map(|c| c.name)
|
||||
.unwrap_or_else(|_| "Category".to_string());
|
||||
let progress = db_ref
|
||||
.get_budget_progress(category_id, &month_str)
|
||||
.ok()
|
||||
.flatten();
|
||||
let pct = progress.map(|(_, _, p)| p).unwrap_or(0.0);
|
||||
|
||||
for threshold in thresholds {
|
||||
Self::send_budget_notification(
|
||||
&app_ref, &cat_name, pct, threshold,
|
||||
);
|
||||
db_ref.record_notification(category_id, &month_str, threshold).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
amount_row_ref.set_text("");
|
||||
note_row_ref.set_text("");
|
||||
|
||||
@@ -344,6 +369,26 @@ impl LogView {
|
||||
}
|
||||
}
|
||||
|
||||
fn send_budget_notification(
|
||||
app: &adw::Application,
|
||||
category: &str,
|
||||
percentage: f64,
|
||||
threshold: u32,
|
||||
) {
|
||||
let notification = gio::Notification::new("Budget Alert");
|
||||
let body = match threshold {
|
||||
75 => format!("{} is at {:.0}% of budget", category, percentage),
|
||||
90 => format!("{} is at {:.0}% of budget - almost at limit!", category, percentage),
|
||||
100 => format!("{} is over budget at {:.0}%!", category, percentage),
|
||||
_ => return,
|
||||
};
|
||||
notification.set_body(Some(&body));
|
||||
app.send_notification(
|
||||
Some(&format!("budget-{}-{}", category, threshold)),
|
||||
¬ification,
|
||||
);
|
||||
}
|
||||
|
||||
fn populate_categories_from_db(
|
||||
db: &Database,
|
||||
model: >k::StringList,
|
||||
|
||||
@@ -35,7 +35,7 @@ impl MainWindow {
|
||||
content_stack.set_transition_type(gtk::StackTransitionType::Crossfade);
|
||||
|
||||
// Log view
|
||||
let log_view = LogView::new(db.clone());
|
||||
let log_view = LogView::new(db.clone(), app);
|
||||
let log_scroll = gtk::ScrolledWindow::builder()
|
||||
.hscrollbar_policy(gtk::PolicyType::Never)
|
||||
.child(&log_view.container)
|
||||
|
||||
Reference in New Issue
Block a user