use adw::prelude::*; use gtk::gio; use crate::config::APP_ID; pub fn show_preferences_dialog(parent: &impl IsA) { let dialog = adw::PreferencesDialog::new(); dialog.set_title("Preferences"); let settings = gio::Settings::new(APP_ID); // --- General page --- let general_page = adw::PreferencesPage::builder() .title("General") .icon_name("emblem-system-symbolic") .build(); // Appearance group let appearance_group = adw::PreferencesGroup::builder() .title("Appearance") .build(); let theme_row = adw::ComboRow::builder() .title("Color Scheme") .subtitle("Choose light, dark, or follow system preference") .build(); let model = gtk::StringList::new(&["Follow System", "Light", "Dark"]); theme_row.set_model(Some(&model)); let current = settings.string("color-scheme"); theme_row.set_selected(match current.as_str() { "force-light" => 1, "force-dark" => 2, _ => 0, }); let settings_clone = settings.clone(); theme_row.connect_selected_notify(move |row| { let value = match row.selected() { 1 => "force-light", 2 => "force-dark", _ => "default", }; settings_clone.set_string("color-scheme", value).ok(); }); appearance_group.add(&theme_row); general_page.add(&appearance_group); // Scan Locations group let scan_group = adw::PreferencesGroup::builder() .title("Scan Locations") .description("Directories to scan for AppImage files") .build(); let dirs = settings.strv("scan-directories"); let dir_list_box = gtk::ListBox::new(); dir_list_box.add_css_class("boxed-list"); dir_list_box.set_selection_mode(gtk::SelectionMode::None); for dir in &dirs { add_directory_row(&dir_list_box, &dir, &settings); } scan_group.add(&dir_list_box); // Add location button let add_button = gtk::Button::builder() .label("Add Location") .build(); add_button.add_css_class("flat"); let settings_add = settings.clone(); let list_box_ref = dir_list_box.clone(); let dialog_weak = dialog.downgrade(); add_button.connect_clicked(move |_| { let file_dialog = gtk::FileDialog::builder() .title("Choose a directory") .modal(true) .build(); let settings_ref = settings_add.clone(); let list_ref = list_box_ref.clone(); let dlg = dialog_weak.upgrade(); // Get the root window as the transient parent for the file dialog let parent_window: Option = dlg .as_ref() .and_then(|d| d.root()) .and_then(|r| r.downcast::().ok()); file_dialog.select_folder( parent_window.as_ref(), None::<&gio::Cancellable>, move |result| { if let Ok(file) = result { if let Some(path) = file.path() { let path_str = path.to_string_lossy().to_string(); let mut current_dirs: Vec = settings_ref .strv("scan-directories") .iter() .map(|s| s.to_string()) .collect(); if !current_dirs.contains(&path_str) { current_dirs.push(path_str.clone()); let refs: Vec<&str> = current_dirs.iter().map(|s| s.as_str()).collect(); settings_ref.set_strv("scan-directories", refs).ok(); add_directory_row(&list_ref, &path_str, &settings_ref); } } } }, ); }); scan_group.add(&add_button); general_page.add(&scan_group); dialog.add(&general_page); dialog.present(Some(parent)); } fn add_directory_row(list_box: >k::ListBox, dir: &str, settings: &gio::Settings) { let row = adw::ActionRow::builder() .title(dir) .build(); let remove_btn = gtk::Button::builder() .icon_name("edit-delete-symbolic") .valign(gtk::Align::Center) .tooltip_text("Remove") .build(); remove_btn.add_css_class("flat"); let list_ref = list_box.clone(); let settings_ref = settings.clone(); let dir_str = dir.to_string(); let row_ref = row.clone(); remove_btn.connect_clicked(move |_| { let current_dirs: Vec = settings_ref .strv("scan-directories") .iter() .map(|s| s.to_string()) .filter(|s| s != &dir_str) .collect(); let refs: Vec<&str> = current_dirs.iter().map(|s| s.as_str()).collect(); settings_ref.set_strv("scan-directories", refs).ok(); list_ref.remove(&row_ref); }); row.add_suffix(&remove_btn); list_box.append(&row); }