Add UX enhancements: carousel, filter chips, command palette, and more

This commit is contained in:
2026-03-01 00:39:43 +02:00
parent 960eab965d
commit 5112338c0f
25 changed files with 1711 additions and 481 deletions

View File

@@ -6,6 +6,7 @@ use gtk::gio;
use crate::core::catalog;
use crate::core::database::{CatalogApp, Database};
use crate::core::fuse;
use crate::core::github_enrichment;
use crate::core::github_enrichment::AppImageAsset;
use crate::i18n::i18n;
@@ -100,14 +101,15 @@ pub fn build_catalog_detail_page(
&& (app.latest_version.is_none() || is_enrichment_stale(app.github_enriched_at.as_deref()));
let awaiting_github = needs_enrichment && app.github_download_url.is_none();
// Check if already installed
let installed_names: std::collections::HashSet<String> = db
// Check if already installed (map name -> record id for launching)
let installed_map: std::collections::HashMap<String, i64> = db
.get_all_appimages()
.unwrap_or_default()
.iter()
.filter_map(|r| r.app_name.as_ref().map(|n| n.to_lowercase()))
.filter_map(|r| r.app_name.as_ref().map(|n| (n.to_lowercase(), r.id)))
.collect();
let is_installed = installed_names.contains(&app.name.to_lowercase());
let installed_record_id = installed_map.get(&app.name.to_lowercase()).copied();
let is_installed = installed_record_id.is_some();
let install_slot = gtk::Box::new(gtk::Orientation::Horizontal, 0);
@@ -120,6 +122,17 @@ pub fn build_catalog_detail_page(
let awaiting_ocs = has_ocs && !is_installed;
if is_installed {
// Show Launch button for installed apps
if let Some(record_id) = installed_record_id {
let launch_btn = gtk::Button::builder()
.label(&i18n("Launch"))
.css_classes(["suggested-action", "pill"])
.build();
launch_btn.set_action_name(Some("win.launch-appimage"));
launch_btn.set_action_target_value(Some(&record_id.to_variant()));
widgets::set_pointer_cursor(&launch_btn);
button_box.append(&launch_btn);
}
let installed_badge = widgets::status_badge(&i18n("Installed"), "success");
installed_badge.set_valign(gtk::Align::Center);
button_box.append(&installed_badge);
@@ -166,6 +179,37 @@ pub fn build_catalog_detail_page(
}
info_box.append(&button_box);
// "What you'll get" info and compatibility check for install
if !is_installed {
let size_hint = app.ocs_downloadsize.filter(|&s| s > 0)
.map(|s| format!(" ({})", widgets::format_size(s)))
.unwrap_or_default();
let install_info = gtk::Label::builder()
.label(&format!(
"Downloads to ~/Applications and adds to your app launcher{}",
size_hint
))
.css_classes(["caption", "dim-label"])
.wrap(true)
.xalign(0.0)
.halign(gtk::Align::Start)
.build();
info_box.append(&install_info);
// System compatibility check
let fuse_info = fuse::detect_system_fuse();
let (compat_text, compat_class) = if fuse_info.status.is_functional() {
("Works with your system", "success")
} else {
("May need additional setup to run", "warning")
};
let compat_badge = widgets::status_badge(compat_text, compat_class);
compat_badge.set_halign(gtk::Align::Start);
compat_badge.set_margin_top(2);
info_box.append(&compat_badge);
}
header_box.append(&info_box);
content.append(&header_box);
@@ -1243,9 +1287,17 @@ fn format_ocs_file_label(file: &catalog::OcsDownloadFile) -> String {
if !file.version.is_empty() {
parts.push(format!("v{}", file.version));
}
if let Some(ref arch) = file.arch {
parts.push(arch.clone());
}
if !file.filename.is_empty() {
parts.push(file.filename.clone());
}
if let Some(ref pkg_type) = file.pkg_type {
if pkg_type != "appimage" {
parts.push(format!("[{}]", pkg_type));
}
}
if let Some(size_kb) = file.size_kb {
if size_kb > 0 {
parts.push(format!("({})", widgets::format_size(size_kb * 1024)));