From 1bafb135f081ec0eed07f348ce3eef2337160569 Mon Sep 17 00:00:00 2001 From: lashman Date: Fri, 27 Feb 2026 10:06:30 +0200 Subject: [PATCH] Rewrite update dialog text to plain language for WCAG readability --- src/ui/update_dialog.rs | 155 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 149 insertions(+), 6 deletions(-) diff --git a/src/ui/update_dialog.rs b/src/ui/update_dialog.rs index 1cc63e3..201beca 100644 --- a/src/ui/update_dialog.rs +++ b/src/ui/update_dialog.rs @@ -1,6 +1,8 @@ use adw::prelude::*; use gtk::gio; +use std::path::PathBuf; use std::rc::Rc; +use crate::config::APP_ID; use crate::core::database::{AppImageRecord, Database}; use crate::core::updater; @@ -43,7 +45,6 @@ pub fn show_update_dialog( match result { Ok((type_label, raw_info, Some(check_result))) => { // Store update info in DB - let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(); db_ref .update_update_info( record_id, @@ -57,14 +58,48 @@ pub fn show_update_dialog( db_ref.set_update_available(record_id, Some(version), check_result.download_url.as_deref()).ok(); } - let body = format!( - "{} -> {}\n\nA new version is available.", + let mut body = format!( + "{} -> {}", record_clone.app_version.as_deref().unwrap_or("unknown"), check_result.latest_version.as_deref().unwrap_or("unknown"), ); + if let Some(size) = check_result.file_size { + body.push_str(&format!(" ({})", humansize::format_size(size, humansize::BINARY))); + } + body.push_str("\n\nA new version is available."); + if let Some(ref notes) = check_result.release_notes { + if !notes.is_empty() { + // Truncate long release notes for the dialog + let truncated: String = notes.chars().take(300).collect(); + let suffix = if truncated.len() < notes.len() { "..." } else { "" }; + body.push_str(&format!("\n\n{}{}", truncated, suffix)); + } + } dialog_ref.set_heading(Some("Update Available")); dialog_ref.set_body(&body); - // Future: add "Update" response to trigger download + + // Add "Update Now" button if we have a download URL + if let Some(download_url) = check_result.download_url { + dialog_ref.add_response("update", "Update Now"); + dialog_ref.set_response_appearance("update", adw::ResponseAppearance::Suggested); + dialog_ref.set_default_response(Some("update")); + + let db_update = db_ref.clone(); + let record_path = record_clone.path.clone(); + let new_version = check_result.latest_version.clone(); + dialog_ref.connect_response(None, move |dlg, response| { + if response == "update" { + start_update( + dlg, + &record_path, + &download_url, + record_id, + new_version.as_deref(), + &db_update, + ); + } + }); + } } else { dialog_ref.set_heading(Some("Up to Date")); dialog_ref.set_body(&format!( @@ -83,8 +118,8 @@ pub fn show_update_dialog( } else { dialog_ref.set_heading(Some("No Update Info")); dialog_ref.set_body( - "This AppImage does not contain update information. \ - Updates must be downloaded manually.", + "This app does not support automatic updates. \ + Check the developer's website for newer versions.", ); } } @@ -98,6 +133,114 @@ pub fn show_update_dialog( dialog.present(Some(parent)); } +/// Start the actual update download + apply process. +fn start_update( + dialog: &adw::AlertDialog, + appimage_path: &str, + download_url: &str, + record_id: i64, + new_version: Option<&str>, + db: &Rc, +) { + dialog.set_heading(Some("Updating...")); + dialog.set_body("Downloading update. This may take a moment."); + dialog.set_response_enabled("update", false); + + let path = appimage_path.to_string(); + let url = download_url.to_string(); + let version = new_version.map(|s| s.to_string()); + let db_ref = db.clone(); + let dialog_weak = dialog.downgrade(); + + glib::spawn_future_local(async move { + let result = gio::spawn_blocking(move || { + let p = std::path::Path::new(&path); + // Always keep old version initially - cleanup decision happens after + updater::perform_update(p, Some(&url), true, None) + }) + .await; + + let Some(dialog) = dialog_weak.upgrade() else { return }; + + match result { + Ok(Ok(applied)) => { + // Record the update in history using the actual new version + let actual_version = applied.new_version.as_deref().or(version.as_deref()); + if let Some(ver) = actual_version { + db_ref.record_update(record_id, None, Some(ver), Some("download"), None, true).ok(); + } + db_ref.clear_update_available(record_id).ok(); + + let success_body = format!( + "Updated to {}\nPath: {}", + applied.new_version.as_deref().unwrap_or("latest"), + applied.new_path.display(), + ); + dialog.set_heading(Some("Update Complete")); + dialog.set_body(&success_body); + dialog.set_response_enabled("update", false); + + // Handle old version cleanup + if let Some(old_path) = applied.old_path_backup { + handle_old_version_cleanup(&dialog, old_path); + } + } + Ok(Err(e)) => { + dialog.set_heading(Some("Update Failed")); + dialog.set_body(&format!("The update could not be applied: {}", e)); + } + Err(_) => { + dialog.set_heading(Some("Update Failed")); + dialog.set_body("An unexpected error occurred during the update."); + } + } + }); +} + +/// After a successful update, handle cleanup of the old version based on user preference. +fn handle_old_version_cleanup(dialog: &adw::AlertDialog, old_path: PathBuf) { + let settings = gio::Settings::new(APP_ID); + let policy = settings.string("update-cleanup"); + + match policy.as_str() { + "always" => { + // Auto-remove without asking + if old_path.exists() { + if let Err(e) = std::fs::remove_file(&old_path) { + log::warn!("Failed to remove old version {}: {}", old_path.display(), e); + } else { + log::info!("Auto-removed old version: {}", old_path.display()); + } + } + } + "never" => { + // Keep the backup, just inform + dialog.set_body(&format!( + "Update complete. The old version is saved at:\n{}", + old_path.display() + )); + } + _ => { + // "ask" - prompt the user + dialog.set_body(&format!( + "Update complete.\n\nRemove the old version?\n{}", + old_path.display() + )); + dialog.add_response("remove-old", "Remove Old Version"); + dialog.set_response_appearance("remove-old", adw::ResponseAppearance::Destructive); + + let path = old_path.clone(); + dialog.connect_response(None, move |_dlg, response| { + if response == "remove-old" { + if path.exists() { + std::fs::remove_file(&path).ok(); + } + } + }); + } + } +} + /// Batch check all AppImages for updates. Returns count of updates found. pub fn batch_check_updates(db: &Database) -> u32 { let records = match db.get_all_appimages() {