Add Phase 5 enhancements: security, i18n, analysis, backup, notifications
- Database v8 migration: tags, pinned, avg_startup_ms columns - Security scanning with CVE matching and batch scan - Bundled library extraction and vulnerability reports - Desktop notification system for security alerts - Backup/restore system for AppImage configurations - i18n framework with gettext support - Runtime analysis and Wayland compatibility detection - AppStream metadata and Flatpak-style build support - File watcher module for live directory monitoring - Preferences panel with GSettings integration - CLI interface for headless operation - Detail view: tabbed layout with ViewSwitcher in title bar, health score, sandbox controls, changelog links - Library view: sort dropdown, context menu enhancements - Dashboard: system status, disk usage, launch history - Security report page with scan and export - Packaging: meson build, PKGBUILD, metainfo
This commit is contained in:
@@ -2,7 +2,6 @@ use gtk::prelude::*;
|
||||
use gtk::accessible::Property as AccessibleProperty;
|
||||
|
||||
use crate::core::database::AppImageRecord;
|
||||
use crate::core::fuse::FuseStatus;
|
||||
use crate::core::wayland::WaylandStatus;
|
||||
use super::widgets;
|
||||
|
||||
@@ -11,25 +10,20 @@ pub fn build_app_card(record: &AppImageRecord) -> gtk::FlowBoxChild {
|
||||
let card = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.spacing(6)
|
||||
.margin_top(14)
|
||||
.margin_bottom(14)
|
||||
.margin_start(14)
|
||||
.margin_end(14)
|
||||
.halign(gtk::Align::Center)
|
||||
.halign(gtk::Align::Fill)
|
||||
.build();
|
||||
card.add_css_class("card");
|
||||
card.set_size_request(200, -1);
|
||||
|
||||
// Icon (72x72) with integration emblem overlay
|
||||
let name = record.app_name.as_deref().unwrap_or(&record.filename);
|
||||
|
||||
// Icon (64x64) with integration emblem overlay
|
||||
let icon_widget = widgets::app_icon(
|
||||
record.icon_path.as_deref(),
|
||||
name,
|
||||
72,
|
||||
64,
|
||||
);
|
||||
icon_widget.add_css_class("icon-dropshadow");
|
||||
|
||||
// If integrated, overlay a small checkmark emblem
|
||||
if record.integrated {
|
||||
let overlay = gtk::Overlay::new();
|
||||
overlay.set_child(Some(&icon_widget));
|
||||
@@ -47,13 +41,14 @@ pub fn build_app_card(record: &AppImageRecord) -> gtk::FlowBoxChild {
|
||||
card.append(&icon_widget);
|
||||
}
|
||||
|
||||
// App name - .title-3 for more visual weight
|
||||
// App name
|
||||
let name_label = gtk::Label::builder()
|
||||
.label(name)
|
||||
.css_classes(["title-3"])
|
||||
.css_classes(["title-4"])
|
||||
.ellipsize(gtk::pango::EllipsizeMode::End)
|
||||
.max_width_chars(20)
|
||||
.build();
|
||||
card.append(&name_label);
|
||||
|
||||
// Version + size combined on one line
|
||||
let version_text = record.app_version.as_deref().unwrap_or("");
|
||||
@@ -61,28 +56,56 @@ pub fn build_app_card(record: &AppImageRecord) -> gtk::FlowBoxChild {
|
||||
let meta_text = if version_text.is_empty() {
|
||||
size_text
|
||||
} else {
|
||||
format!("{} - {}", version_text, size_text)
|
||||
format!("{} - {}", version_text, size_text)
|
||||
};
|
||||
let meta_label = gtk::Label::builder()
|
||||
.label(&meta_text)
|
||||
.css_classes(["caption", "dimmed", "numeric"])
|
||||
.ellipsize(gtk::pango::EllipsizeMode::End)
|
||||
.build();
|
||||
|
||||
card.append(&name_label);
|
||||
card.append(&meta_label);
|
||||
|
||||
// Description snippet (if available)
|
||||
if let Some(ref desc) = record.description {
|
||||
if !desc.is_empty() {
|
||||
let snippet = if desc.len() > 60 {
|
||||
format!("{}...", &desc[..desc.char_indices().take_while(|&(i, _)| i < 57).last().map(|(i, c)| i + c.len_utf8()).unwrap_or(57)])
|
||||
} else {
|
||||
desc.clone()
|
||||
};
|
||||
let desc_label = gtk::Label::builder()
|
||||
.label(&snippet)
|
||||
.css_classes(["caption", "dimmed"])
|
||||
.ellipsize(gtk::pango::EllipsizeMode::End)
|
||||
.lines(2)
|
||||
.wrap(true)
|
||||
.xalign(0.5)
|
||||
.max_width_chars(25)
|
||||
.build();
|
||||
card.append(&desc_label);
|
||||
}
|
||||
}
|
||||
|
||||
// Single most important badge (priority: Update > FUSE issue > Wayland issue)
|
||||
if let Some(badge) = build_priority_badge(record) {
|
||||
let badge = build_priority_badge(record);
|
||||
let has_badge = badge.is_some();
|
||||
if let Some(badge) = badge {
|
||||
let badge_box = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Horizontal)
|
||||
.halign(gtk::Align::Center)
|
||||
.margin_top(4)
|
||||
.margin_top(2)
|
||||
.build();
|
||||
badge_box.append(&badge);
|
||||
card.append(&badge_box);
|
||||
}
|
||||
|
||||
// Status border: green for healthy, amber for attention needed
|
||||
if record.integrated && !has_badge {
|
||||
card.add_css_class("status-ok");
|
||||
} else if has_badge {
|
||||
card.add_css_class("status-attention");
|
||||
}
|
||||
|
||||
let child = gtk::FlowBoxChild::builder()
|
||||
.child(&card)
|
||||
.build();
|
||||
@@ -95,9 +118,14 @@ pub fn build_app_card(record: &AppImageRecord) -> gtk::FlowBoxChild {
|
||||
child
|
||||
}
|
||||
|
||||
/// Return the single most important badge for a card.
|
||||
/// Priority: Update available > FUSE issue > Wayland issue.
|
||||
/// Return the single most important badge for a record.
|
||||
/// Priority: Analyzing > Update available > FUSE issue > Wayland issue.
|
||||
pub fn build_priority_badge(record: &AppImageRecord) -> Option<gtk::Label> {
|
||||
// 0. Analysis in progress (highest priority)
|
||||
if record.app_name.is_none() && record.analysis_status.as_deref() != Some("complete") {
|
||||
return Some(widgets::status_badge("Analyzing...", "info"));
|
||||
}
|
||||
|
||||
// 1. Update available (highest priority)
|
||||
if let (Some(ref latest), Some(ref current)) = (&record.latest_version, &record.app_version) {
|
||||
if crate::core::updater::version_is_newer(latest, current) {
|
||||
@@ -106,10 +134,18 @@ pub fn build_priority_badge(record: &AppImageRecord) -> Option<gtk::Label> {
|
||||
}
|
||||
|
||||
// 2. FUSE issue
|
||||
// The database stores AppImageFuseStatus values (per-app), not FuseStatus (system).
|
||||
// Check both: per-app statuses like "native_fuse"/"static_runtime" are fine,
|
||||
// "extract_and_run" is slow but works, "cannot_launch" is a real problem.
|
||||
// System statuses like "fully_functional" are also fine.
|
||||
if let Some(ref fs) = record.fuse_status {
|
||||
let status = FuseStatus::from_str(fs);
|
||||
if !status.is_functional() {
|
||||
return Some(widgets::status_badge(status.label(), status.badge_class()));
|
||||
let is_ok = matches!(
|
||||
fs.as_str(),
|
||||
"native_fuse" | "static_runtime" | "fully_functional"
|
||||
);
|
||||
let is_slow = fs.as_str() == "extract_and_run";
|
||||
if !is_ok && !is_slow {
|
||||
return Some(widgets::status_badge("Needs setup", "warning"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +153,7 @@ pub fn build_priority_badge(record: &AppImageRecord) -> Option<gtk::Label> {
|
||||
if let Some(ref ws) = record.wayland_status {
|
||||
let status = WaylandStatus::from_str(ws);
|
||||
if status != WaylandStatus::Unknown && status != WaylandStatus::Native {
|
||||
return Some(widgets::status_badge(status.label(), status.badge_class()));
|
||||
return Some(widgets::status_badge("May look blurry", "neutral"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user