diff --git a/data/styles/CategoryPage.scss b/data/styles/CategoryPage.scss new file mode 100644 index 000000000..7ad0bb77d --- /dev/null +++ b/data/styles/CategoryPage.scss @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2023-2024 elementary, Inc. (https://elementary.io) + */ + +categorypage { + .header { + padding: rem(12px); + } +} diff --git a/data/styles/Index.scss b/data/styles/Index.scss index f9e520d5c..59615b1a2 100644 --- a/data/styles/Index.scss +++ b/data/styles/Index.scss @@ -4,6 +4,7 @@ @import 'AppInfoView.scss'; @import 'Banner.scss'; @import 'Category.scss'; +@import 'CategoryPage.scss'; @import 'HomePage.scss'; @import 'MainWindow.scss'; @import 'ProgressButton.scss'; diff --git a/src/Core/Package.vala b/src/Core/Package.vala index 0877e4127..3cf6c38d8 100644 --- a/src/Core/Package.vala +++ b/src/Core/Package.vala @@ -925,4 +925,30 @@ public class AppCenterCore.Package : Object { backend_details = new PackageDetails (); } } + + public bool matches_search (string search_term) { + if (search_term == "") { + return true; + } + + var _search_term = search_term.down (); + + if (_search_term in get_name ().down ()) { + return true; + } + + if (_search_term in get_summary ().down ()) { + return true; + } + + if (_search_term in get_description ().down ()) { + return true; + } + + if (_search_term in component.get_keywords_table ()) { + return true; + } + + return false; + } } diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 58bff3f94..000b8ef0e 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -370,20 +370,14 @@ public class AppCenter.MainWindow : Gtk.ApplicationWindow { if (navigation_view.visible_page is Homepage) { view_mode_revealer.reveal_child = true; - configure_search (true, _("Search Apps"), ""); + configure_search (true); } else if (navigation_view.visible_page is CategoryView) { var current_category = ((CategoryView) navigation_view.visible_page).category; view_mode_revealer.reveal_child = false; - configure_search (true, _("Search %s").printf (current_category.name), ""); + configure_search (false); } else if (navigation_view.visible_page == search_view) { - if (previous_child is CategoryView) { - var previous_category = ((CategoryView) previous_child).category; - configure_search (true, _("Search %s").printf (previous_category.name)); - view_mode_revealer.reveal_child = false; - } else { - configure_search (true); - view_mode_revealer.reveal_child = true; - } + configure_search (true); + view_mode_revealer.reveal_child = true; } else if (navigation_view.visible_page is Views.AppInfoView) { view_mode_revealer.reveal_child = false; configure_search (false); @@ -490,11 +484,11 @@ public class AppCenter.MainWindow : Gtk.ApplicationWindow { } } - private void configure_search (bool sensitive, string? placeholder_text = _("Search Apps"), string? search_term = null) { + private void configure_search (bool sensitive, bool? clear = false) { search_entry.sensitive = sensitive; - search_entry.placeholder_text = placeholder_text; + search_entry.visible = sensitive; - if (search_term != null) { + if (clear) { search_entry.text = ""; } diff --git a/src/Views/CategoryView.vala b/src/Views/CategoryView.vala index 6e7b0d2b5..4bd0db6f6 100644 --- a/src/Views/CategoryView.vala +++ b/src/Views/CategoryView.vala @@ -20,9 +20,9 @@ public class AppCenter.CategoryView : Adw.NavigationPage { public AppStream.Category category { get; construct; } + private Gtk.SearchEntry search_entry; private Gtk.Stack stack; - private Gtk.ScrolledWindow scrolled; - private Gtk.Box box; + private Gtk.Box main_box; private SubcategoryFlowbox free_flowbox; private SubcategoryFlowbox paid_flowbox; private SubcategoryFlowbox recently_updated_flowbox; @@ -31,21 +31,39 @@ public class AppCenter.CategoryView : Adw.NavigationPage { Object (category: category); } + class construct { + set_css_name ("categorypage"); + } + construct { + search_entry = new Gtk.SearchEntry () { + hexpand = true, + placeholder_text = _("Search %s").printf (category.name) + }; + + var clamp = new Adw.Clamp () { + child = search_entry + }; + clamp.add_css_class ("header"); + recently_updated_flowbox = new SubcategoryFlowbox (_("Recently Updated")); paid_flowbox = new SubcategoryFlowbox (_("Paid Apps")); free_flowbox = new SubcategoryFlowbox (_("Free Apps")); - box = new Gtk.Box (Gtk.Orientation.VERTICAL, 48) { + var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 48) { margin_top = 12, margin_end = 12, margin_bottom = 24, - margin_start = 12 + margin_start = 12, + vexpand = true }; + box.append (recently_updated_flowbox); + box.append (paid_flowbox); + box.append (free_flowbox); - scrolled = new Gtk.ScrolledWindow () { + var scrolled = new Gtk.ScrolledWindow () { child = box, hscrollbar_policy = Gtk.PolicyType.NEVER }; @@ -56,9 +74,13 @@ public class AppCenter.CategoryView : Adw.NavigationPage { }; spinner.start (); + main_box = new Gtk.Box (VERTICAL, 0); + main_box.append (clamp); + main_box.append (scrolled); + stack = new Gtk.Stack (); stack.add_child (spinner); - stack.add_child (scrolled); + stack.add_child (main_box); child = stack; title = category.name; @@ -80,14 +102,36 @@ public class AppCenter.CategoryView : Adw.NavigationPage { AppCenterCore.Client.get_default ().installed_apps_changed.connect (() => { populate (); }); + + search_entry.search_changed.connect (() => { + recently_updated_flowbox.search_text = search_entry.text; + paid_flowbox.search_text = search_entry.text; + free_flowbox.search_text = search_entry.text; + }); + + // Forward only printable keys, not navigation keys + var eventcontrollerkey = new Gtk.EventControllerKey (); + eventcontrollerkey.key_pressed.connect ((keyval, keycode, state) => { + var mods = state & Gtk.accelerator_get_default_mod_mask (); + var is_printable_char = ((unichar) Gdk.keyval_to_unicode (keyval)).isprint (); + + if ( + (is_printable_char && mods == 0) || + (is_printable_char && mods == SHIFT_MASK) + ) { + eventcontrollerkey.forward (search_entry.get_delegate ()); + search_entry.grab_focus (); + return Gdk.EVENT_STOP; + } + + return Gdk.EVENT_PROPAGATE; + }); + + add_controller (eventcontrollerkey); } private void populate () { get_packages.begin ((obj, res) => { - while (box.get_first_child () != null) { - box.remove (box.get_first_child ()); - }; - recently_updated_flowbox.clear (); free_flowbox.clear (); paid_flowbox.clear (); @@ -140,19 +184,11 @@ public class AppCenter.CategoryView : Adw.NavigationPage { } } - if (recently_updated_flowbox.has_children) { - box.append (recently_updated_flowbox); - } - - if (paid_flowbox.has_children) { - box.append (paid_flowbox); - } - - if (free_flowbox.has_children) { - box.append (free_flowbox); - } + free_flowbox.visible = free_flowbox.has_children; + paid_flowbox.visible = paid_flowbox.has_children; + recently_updated_flowbox.visible = recently_updated_flowbox.has_children; - stack.visible_child = scrolled; + stack.visible_child = main_box; }); } @@ -177,6 +213,7 @@ public class AppCenter.CategoryView : Adw.NavigationPage { private class SubcategoryFlowbox : Gtk.Box { public signal void show_package (AppCenterCore.Package package); + public string search_text { get; set; default = ""; } public string? label { get; construct; } public bool has_children { @@ -205,8 +242,10 @@ public class AppCenter.CategoryView : Adw.NavigationPage { valign = Gtk.Align.START }; flowbox.set_sort_func ((Gtk.FlowBoxSortFunc) package_row_compare); + flowbox.set_filter_func (filter_func); - orientation = Gtk.Orientation.VERTICAL; + orientation = VERTICAL; + visible = false; if (label != null) { var header = new Granite.HeaderLabel (label) { @@ -221,6 +260,11 @@ public class AppCenter.CategoryView : Adw.NavigationPage { var row = (Widgets.ListPackageRowGrid) child.get_child (); show_package (row.package); }); + + notify["search-text"].connect (() => { + flowbox.invalidate_filter (); + visible = has_children; + }); } public void add_package (AppCenterCore.Package package) { @@ -249,5 +293,10 @@ public class AppCenter.CategoryView : Adw.NavigationPage { #endif return row1.package.get_name ().collate (row2.package.get_name ()); } + + private bool filter_func (Gtk.FlowBoxChild child) { + var package = ((Widgets.ListPackageRowGrid) child.get_child ()).package; + return package.matches_search (search_text); + } } }