Implement Driftwood AppImage manager - Phases 1 and 2
This commit is contained in:
156
src/ui/duplicate_dialog.rs
Normal file
156
src/ui/duplicate_dialog.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use adw::prelude::*;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::core::database::Database;
|
||||
use crate::core::duplicates::{self, DuplicateGroup, MatchReason, MemberRecommendation};
|
||||
use super::widgets;
|
||||
|
||||
/// Show a dialog listing duplicate/multi-version AppImages with resolution options.
|
||||
pub fn show_duplicate_dialog(
|
||||
parent: &impl IsA<gtk::Widget>,
|
||||
db: &Rc<Database>,
|
||||
toast_overlay: &adw::ToastOverlay,
|
||||
) {
|
||||
let groups = duplicates::detect_duplicates(db);
|
||||
|
||||
if groups.is_empty() {
|
||||
let dialog = adw::AlertDialog::builder()
|
||||
.heading("No Duplicates Found")
|
||||
.body("No duplicate or multi-version AppImages were detected.")
|
||||
.build();
|
||||
dialog.add_response("ok", "OK");
|
||||
dialog.set_default_response(Some("ok"));
|
||||
dialog.present(Some(parent));
|
||||
return;
|
||||
}
|
||||
|
||||
let summary = duplicates::summarize_duplicates(&groups);
|
||||
|
||||
let dialog = adw::Dialog::builder()
|
||||
.title("Duplicates & Old Versions")
|
||||
.content_width(600)
|
||||
.content_height(500)
|
||||
.build();
|
||||
|
||||
let toolbar = adw::ToolbarView::new();
|
||||
let header = adw::HeaderBar::new();
|
||||
toolbar.add_top_bar(&header);
|
||||
|
||||
let scrolled = gtk::ScrolledWindow::builder()
|
||||
.vexpand(true)
|
||||
.build();
|
||||
|
||||
let content = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.spacing(16)
|
||||
.margin_top(16)
|
||||
.margin_bottom(16)
|
||||
.margin_start(16)
|
||||
.margin_end(16)
|
||||
.build();
|
||||
|
||||
// Summary banner
|
||||
let summary_text = format!(
|
||||
"{} groups found ({} exact duplicates, {} with multiple versions). \
|
||||
Potential savings: {}",
|
||||
summary.total_groups,
|
||||
summary.exact_duplicates,
|
||||
summary.multi_version,
|
||||
widgets::format_size(summary.total_potential_savings as i64),
|
||||
);
|
||||
let summary_label = gtk::Label::builder()
|
||||
.label(&summary_text)
|
||||
.wrap(true)
|
||||
.halign(gtk::Align::Start)
|
||||
.build();
|
||||
summary_label.add_css_class("dim-label");
|
||||
content.append(&summary_label);
|
||||
|
||||
// Build a list for each duplicate group
|
||||
for group in &groups {
|
||||
content.append(&build_group_widget(group));
|
||||
}
|
||||
|
||||
scrolled.set_child(Some(&content));
|
||||
toolbar.set_content(Some(&scrolled));
|
||||
dialog.set_child(Some(&toolbar));
|
||||
|
||||
dialog.present(Some(parent));
|
||||
}
|
||||
|
||||
fn build_group_widget(group: &DuplicateGroup) -> gtk::Box {
|
||||
let container = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.spacing(8)
|
||||
.build();
|
||||
|
||||
// Group header
|
||||
let header_box = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Horizontal)
|
||||
.spacing(8)
|
||||
.build();
|
||||
|
||||
let name_label = gtk::Label::builder()
|
||||
.label(&group.app_name)
|
||||
.css_classes(["heading"])
|
||||
.halign(gtk::Align::Start)
|
||||
.hexpand(true)
|
||||
.build();
|
||||
header_box.append(&name_label);
|
||||
|
||||
let reason_badge = widgets::status_badge(
|
||||
group.match_reason.label(),
|
||||
match group.match_reason {
|
||||
MatchReason::ExactDuplicate => "error",
|
||||
MatchReason::MultiVersion => "warning",
|
||||
MatchReason::SameVersionDifferentPath => "warning",
|
||||
},
|
||||
);
|
||||
header_box.append(&reason_badge);
|
||||
|
||||
container.append(&header_box);
|
||||
|
||||
// Savings info
|
||||
if group.potential_savings > 0 {
|
||||
let savings_label = gtk::Label::builder()
|
||||
.label(&format!(
|
||||
"Potential savings: {}",
|
||||
widgets::format_size(group.potential_savings as i64)
|
||||
))
|
||||
.halign(gtk::Align::Start)
|
||||
.build();
|
||||
savings_label.add_css_class("dim-label");
|
||||
container.append(&savings_label);
|
||||
}
|
||||
|
||||
// Members list
|
||||
let list_box = gtk::ListBox::new();
|
||||
list_box.add_css_class("boxed-list");
|
||||
list_box.set_selection_mode(gtk::SelectionMode::None);
|
||||
|
||||
for member in &group.members {
|
||||
let record = &member.record;
|
||||
let version = record.app_version.as_deref().unwrap_or("unknown");
|
||||
let size = widgets::format_size(record.size_bytes);
|
||||
|
||||
let row = adw::ActionRow::builder()
|
||||
.title(&format!("{} ({})", version, size))
|
||||
.subtitle(&record.path)
|
||||
.build();
|
||||
|
||||
// Recommendation badge
|
||||
let badge_class = match member.recommendation {
|
||||
MemberRecommendation::KeepNewest | MemberRecommendation::KeepIntegrated => "success",
|
||||
MemberRecommendation::RemoveOlder | MemberRecommendation::RemoveDuplicate => "error",
|
||||
MemberRecommendation::UserChoice => "neutral",
|
||||
};
|
||||
let badge = widgets::status_badge(member.recommendation.label(), badge_class);
|
||||
badge.set_valign(gtk::Align::Center);
|
||||
row.add_suffix(&badge);
|
||||
|
||||
list_box.append(&row);
|
||||
}
|
||||
|
||||
container.append(&list_box);
|
||||
container
|
||||
}
|
||||
Reference in New Issue
Block a user