Add similar app recommendations from shared categories
This commit is contained in:
@@ -1869,6 +1869,51 @@ impl Database {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Similar apps ---
|
||||||
|
|
||||||
|
/// Find AppImages from the user's library that share categories with the given app.
|
||||||
|
pub fn find_similar_apps(
|
||||||
|
&self,
|
||||||
|
categories: &str,
|
||||||
|
exclude_id: i64,
|
||||||
|
limit: i32,
|
||||||
|
) -> SqlResult<Vec<(i64, String, Option<String>)>> {
|
||||||
|
// Split categories and match any overlap
|
||||||
|
let cats: Vec<&str> = categories.split(';').filter(|s| !s.is_empty()).collect();
|
||||||
|
if cats.is_empty() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build LIKE conditions for each category
|
||||||
|
let conditions: Vec<String> = cats.iter()
|
||||||
|
.map(|c| format!("categories LIKE '%{}%'", c.replace('\'', "''")))
|
||||||
|
.collect();
|
||||||
|
let where_clause = conditions.join(" OR ");
|
||||||
|
|
||||||
|
let sql = format!(
|
||||||
|
"SELECT id, COALESCE(app_name, filename) AS name, icon_path
|
||||||
|
FROM appimages
|
||||||
|
WHERE id != ?1 AND ({})
|
||||||
|
LIMIT ?2",
|
||||||
|
where_clause
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut stmt = self.conn.prepare(&sql)?;
|
||||||
|
let rows = stmt.query_map(params![exclude_id, limit], |row| {
|
||||||
|
Ok((
|
||||||
|
row.get::<_, i64>(0)?,
|
||||||
|
row.get::<_, String>(1)?,
|
||||||
|
row.get::<_, Option<String>>(2)?,
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
for row in rows {
|
||||||
|
results.push(row?);
|
||||||
|
}
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
// --- System modification tracking ---
|
// --- System modification tracking ---
|
||||||
|
|
||||||
pub fn register_modification(
|
pub fn register_modification(
|
||||||
|
|||||||
@@ -868,6 +868,38 @@ fn build_overview_tab(record: &AppImageRecord, db: &Rc<Database>) -> gtk::Box {
|
|||||||
}
|
}
|
||||||
inner.append(&info_group);
|
inner.append(&info_group);
|
||||||
|
|
||||||
|
// "You might also like" - similar apps from the user's library
|
||||||
|
if let Some(ref cats) = record.categories {
|
||||||
|
if let Ok(similar) = db.find_similar_apps(cats, record.id, 4) {
|
||||||
|
if !similar.is_empty() {
|
||||||
|
let similar_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("You might also like")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
for (id, name, icon_path) in &similar {
|
||||||
|
let row = adw::ActionRow::builder()
|
||||||
|
.title(name.as_str())
|
||||||
|
.activatable(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let icon = widgets::app_icon(
|
||||||
|
icon_path.as_deref(),
|
||||||
|
name,
|
||||||
|
32,
|
||||||
|
);
|
||||||
|
row.add_prefix(&icon);
|
||||||
|
|
||||||
|
// Store the record ID in the widget name for navigation
|
||||||
|
row.set_widget_name(&format!("similar-{}", id));
|
||||||
|
|
||||||
|
similar_group.add(&row);
|
||||||
|
}
|
||||||
|
|
||||||
|
inner.append(&similar_group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clamp.set_child(Some(&inner));
|
clamp.set_child(Some(&inner));
|
||||||
tab.append(&clamp);
|
tab.append(&clamp);
|
||||||
tab
|
tab
|
||||||
|
|||||||
Reference in New Issue
Block a user