Improve catalog view with relative timestamps, auto-refresh, and installed badges
Show app count with relative last-refreshed time in subtitle. Auto-refresh catalog on first visit when empty. Show "Installed" badge on catalog entries that match already-installed AppImages instead of the Install button.
This commit is contained in:
@@ -95,20 +95,10 @@ pub fn build_catalog_page(db: &Rc<Database>) -> adw::NavigationPage {
|
|||||||
stack.add_named(&scrolled, Some("results"));
|
stack.add_named(&scrolled, Some("results"));
|
||||||
|
|
||||||
// Show empty or results based on catalog data
|
// Show empty or results based on catalog data
|
||||||
let sources = catalog::get_sources(db);
|
|
||||||
let enabled_sources: Vec<_> = sources.iter().filter(|s| s.enabled).collect();
|
|
||||||
let app_count = db.catalog_app_count().unwrap_or(0);
|
let app_count = db.catalog_app_count().unwrap_or(0);
|
||||||
if app_count > 0 {
|
if app_count > 0 {
|
||||||
stack.set_visible_child_name("results");
|
stack.set_visible_child_name("results");
|
||||||
if let Some(src) = enabled_sources.first() {
|
update_catalog_subtitle(&title, app_count);
|
||||||
let synced = src.last_synced.as_deref().unwrap_or("never");
|
|
||||||
title.set_subtitle(&format!(
|
|
||||||
"{} apps from {} ({}), synced: {}",
|
|
||||||
src.app_count, src.name, src.source_type.as_str(), synced,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
title.set_subtitle(&format!("{} apps available", app_count));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
stack.set_visible_child_name("empty");
|
stack.set_visible_child_name("empty");
|
||||||
}
|
}
|
||||||
@@ -195,7 +185,7 @@ pub fn build_catalog_page(db: &Rc<Database>) -> adw::NavigationPage {
|
|||||||
toast_c.add_toast(adw::Toast::new(
|
toast_c.add_toast(adw::Toast::new(
|
||||||
&format!("Catalog refreshed: {} apps", count),
|
&format!("Catalog refreshed: {} apps", count),
|
||||||
));
|
));
|
||||||
title_c.set_subtitle(&format!("{} apps available", count));
|
update_catalog_subtitle(&title_c, count as i64);
|
||||||
stack_c.set_visible_child_name("results");
|
stack_c.set_visible_child_name("results");
|
||||||
populate_categories(&db_c, &cat_box_c, &active_cat_c, &results_c, &search_c);
|
populate_categories(&db_c, &cat_box_c, &active_cat_c, &results_c, &search_c);
|
||||||
populate_results(&db_c, "", None, &results_c, &toast_c);
|
populate_results(&db_c, "", None, &results_c, &toast_c);
|
||||||
@@ -221,6 +211,11 @@ pub fn build_catalog_page(db: &Rc<Database>) -> adw::NavigationPage {
|
|||||||
wire_refresh(&refresh_btn);
|
wire_refresh(&refresh_btn);
|
||||||
wire_refresh(&refresh_header_btn);
|
wire_refresh(&refresh_header_btn);
|
||||||
|
|
||||||
|
// Auto-refresh on first visit when catalog is empty
|
||||||
|
if app_count == 0 {
|
||||||
|
refresh_btn.emit_clicked();
|
||||||
|
}
|
||||||
|
|
||||||
adw::NavigationPage::builder()
|
adw::NavigationPage::builder()
|
||||||
.title(&i18n("Catalog"))
|
.title(&i18n("Catalog"))
|
||||||
.tag("catalog")
|
.tag("catalog")
|
||||||
@@ -228,6 +223,18 @@ pub fn build_catalog_page(db: &Rc<Database>) -> adw::NavigationPage {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the catalog subtitle to show app count and relative last-refreshed time.
|
||||||
|
fn update_catalog_subtitle(title: &adw::WindowTitle, app_count: i64) {
|
||||||
|
let settings = gtk::gio::Settings::new(crate::config::APP_ID);
|
||||||
|
let last_refreshed = settings.string("catalog-last-refreshed");
|
||||||
|
if last_refreshed.is_empty() {
|
||||||
|
title.set_subtitle(&format!("{} apps available", app_count));
|
||||||
|
} else {
|
||||||
|
let relative = widgets::relative_time(&last_refreshed);
|
||||||
|
title.set_subtitle(&format!("{} apps - Refreshed {}", app_count, relative));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn populate_results(
|
fn populate_results(
|
||||||
db: &Rc<Database>,
|
db: &Rc<Database>,
|
||||||
query: &str,
|
query: &str,
|
||||||
@@ -254,6 +261,14 @@ fn populate_results(
|
|||||||
let toast_ref = toast_overlay.clone();
|
let toast_ref = toast_overlay.clone();
|
||||||
let db_ref = db.clone();
|
let db_ref = db.clone();
|
||||||
|
|
||||||
|
// Get installed app names for matching
|
||||||
|
let installed_names: std::collections::HashSet<String> = db
|
||||||
|
.get_all_appimages()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|r| r.app_name.as_ref().map(|n| n.to_lowercase()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
for app in &results {
|
for app in &results {
|
||||||
let row = adw::ActionRow::builder()
|
let row = adw::ActionRow::builder()
|
||||||
.title(&app.name)
|
.title(&app.name)
|
||||||
@@ -280,6 +295,16 @@ fn populate_results(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show "Installed" badge if already installed, otherwise show Install button
|
||||||
|
let is_installed = installed_names.contains(&app.name.to_lowercase());
|
||||||
|
if is_installed {
|
||||||
|
let installed_badge = widgets::status_badge(&i18n("Installed"), "success");
|
||||||
|
installed_badge.set_valign(gtk::Align::Center);
|
||||||
|
row.add_suffix(&installed_badge);
|
||||||
|
list_box.append(&row);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Install button
|
// Install button
|
||||||
let install_btn = gtk::Button::builder()
|
let install_btn = gtk::Button::builder()
|
||||||
.label(&i18n("Install"))
|
.label(&i18n("Install"))
|
||||||
|
|||||||
Reference in New Issue
Block a user