diff --git a/data/styles/HomePage.scss b/data/styles/HomePage.scss index 18bf65205..d0ffcbafb 100644 --- a/data/styles/HomePage.scss +++ b/data/styles/HomePage.scss @@ -75,3 +75,7 @@ homepage { } } } + +.icon-dim { + filter: blur(0.75px) saturate(10%); +} diff --git a/src/Application.vala b/src/Application.vala index 912f4d1cd..d0630649c 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -130,7 +130,7 @@ public class AppCenter.App : Gtk.Application { activate (); }); - var flatpak_backend = AppCenterCore.FlatpakBackend.get_default (); + unowned var flatpak_backend = AppCenterCore.FlatpakBackend.get_default (); flatpak_backend.operation_finished.connect (on_operation_finished); var update_manager = AppCenterCore.UpdateManager.get_default (); diff --git a/src/Core/FlatpakBackend.vala b/src/Core/FlatpakBackend.vala index 293bc1f58..babd5ab79 100644 --- a/src/Core/FlatpakBackend.vala +++ b/src/Core/FlatpakBackend.vala @@ -51,6 +51,7 @@ public class AppCenterCore.FlatpakPackage : Package { public class AppCenterCore.FlatpakBackend : Object { public signal void operation_finished (Package package, Package.State operation, Error? error); public signal void cache_flush_needed (); + public signal void on_metadata_remote_preprocessed (string remote_title); // Based on https://github.com/flatpak/flatpak/blob/417e3949c0ecc314e69311e3ee8248320d3e3d52/common/flatpak-run-private.h private const string FLATPAK_METADATA_GROUP_APPLICATION = "Application"; @@ -1112,6 +1113,8 @@ public class AppCenterCore.FlatpakBackend : Object { } else { continue; } + + on_metadata_remote_preprocessed (remote.get_title ()); } } diff --git a/src/Core/Package.vala b/src/Core/Package.vala index 69b79e793..07aaa934d 100644 --- a/src/Core/Package.vala +++ b/src/Core/Package.vala @@ -107,6 +107,8 @@ public class AppCenterCore.Package : Object { public ChangeInformation change_information { public get; private set; } public GLib.Cancellable action_cancellable { public get; private set; } public State state { public get; private set; default = State.NOT_INSTALLED; } + public bool uses_generic_icon { public get; private set; } + public bool icon_available { public get; private set; } public double progress { get { @@ -649,79 +651,102 @@ public class AppCenterCore.Package : Object { } public GLib.Icon get_icon (uint size, uint scale_factor) { - GLib.Icon? icon = null; - uint current_size = 0; - uint current_scale = 0; uint pixel_size = size * scale_factor; - unowned var icons = component.get_icons (); - foreach (unowned var _icon in icons) { - switch (_icon.get_kind ()) { + uint cached_current_size = 0; + uint cached_current_scale = 0; + + AppStream.Icon? stock_icon = null; + AppStream.Icon? cached_icon = null; + AppStream.Icon? local_icon = null; + + uses_generic_icon = false; + icon_available = true; + unowned var all_icons = component.get_icons (); + foreach (var icon in all_icons) { + switch (icon.get_kind ()) { case AppStream.IconKind.STOCK: - unowned string icon_name = _icon.get_name (); - if (Gtk.IconTheme.get_for_display (Gdk.Display.get_default ()).has_icon (icon_name)) { - return new ThemedIcon (icon_name); + if (Gtk.IconTheme.get_for_display (Gdk.Display.get_default ()) + .has_icon (icon.get_name ())) { + stock_icon = icon; } - break; - case AppStream.IconKind.CACHED: case AppStream.IconKind.LOCAL: - var icon_scale = _icon.get_scale (); - var icon_width = _icon.get_width () * icon_scale; - bool is_bigger = (icon_width > current_size && current_size < pixel_size); - bool has_better_dpi = (icon_width == current_size && current_scale < icon_scale && scale_factor <= icon_scale); - if (is_bigger || has_better_dpi) { - var file = File.new_for_path (_icon.get_filename ()); - icon = new FileIcon (file); - current_size = icon_width; - current_scale = icon_scale; - } - + local_icon = icon; break; + case AppStream.IconKind.CACHED: + var icon_scale = icon.get_scale (); + var icon_width = icon.get_width () * icon_scale; - case AppStream.IconKind.UNKNOWN: - warning ("'%s' is an unknown kind of AppStream icon", _icon.get_name ()); - break; + bool is_bigger = icon_width > cached_current_size + && cached_current_size < pixel_size; + bool has_better_dpi = icon_width == cached_current_size + && cached_current_scale < icon_scale + && scale_factor <= icon_scale; - case AppStream.IconKind.REMOTE: - warning ("'%s' is a remote AppStream icon", _icon.get_name ()); - break; - } - } - - if (icon == null) { - switch (component.get_kind ()) { - case AppStream.ComponentKind.ADDON: - icon = new ThemedIcon ("extension"); - break; - case AppStream.ComponentKind.FONT: - icon = new ThemedIcon ("font-x-generic"); + if (is_bigger || has_better_dpi) { + cached_icon = icon; + cached_current_size = icon_width; + cached_current_scale = icon_scale; + } break; - case AppStream.ComponentKind.ICON_THEME: - icon = new ThemedIcon ("preferences-desktop-theme"); + case AppStream.IconKind.REMOTE: + debug ("'%s' is an unknown kind of AppStream icon", icon.get_name ()); + // We are not loading remote icons for now due to blocking events downloading the file + // TODO: Dynamically load remote icons without blocking get_icon method break; - case AppStream.ComponentKind.CODEC: - case AppStream.ComponentKind.CONSOLE_APP: - case AppStream.ComponentKind.DESKTOP_APP: - case AppStream.ComponentKind.DRIVER: - case AppStream.ComponentKind.FIRMWARE: - case AppStream.ComponentKind.GENERIC: - - case AppStream.ComponentKind.INPUT_METHOD: //ComponentKind.INPUTMETHOD is deprecated has same value so cannot be included - case AppStream.ComponentKind.LOCALIZATION: - case AppStream.ComponentKind.OPERATING_SYSTEM: - case AppStream.ComponentKind.REPOSITORY: - case AppStream.ComponentKind.RUNTIME: - case AppStream.ComponentKind.SERVICE: - case AppStream.ComponentKind.UNKNOWN: - case AppStream.ComponentKind.WEB_APP: - debug ("component kind not handled %s", component.get_kind ().to_string ()); - icon = new ThemedIcon ("application-default-icon"); + case AppStream.IconKind.UNKNOWN: + debug ("'%s' is an unknown kind of AppStream icon", icon.get_name ()); break; } } - return icon; + // Respecting the recommended order by the AppStream API + // https://www.freedesktop.org/software/appstream/docs/chap-CatalogData.html#tag-ct-icon + // STOCK -> CACHED -> LOCAL -> REMOTE + GLib.File? file = null; + if (stock_icon != null) { + return new ThemedIcon (stock_icon.get_name ()); + } else if (cached_icon != null) { + file = File.new_for_path (cached_icon.get_filename ()); + } else if (local_icon != null) { + file = File.new_for_path (local_icon.get_filename ()); + } else { + // Either a remote or unkonw icon type + icon_available = false; + } + + if (file != null && file.query_exists ()) { + return new FileIcon (file); + } + + uses_generic_icon = true; + switch (component.get_kind ()) { + case AppStream.ComponentKind.ADDON: + return new ThemedIcon ("extension"); + case AppStream.ComponentKind.FONT: + return new ThemedIcon ("font-x-generic"); + case AppStream.ComponentKind.ICON_THEME: + return new ThemedIcon ("preferences-desktop-theme"); + case AppStream.ComponentKind.CODEC: + case AppStream.ComponentKind.CONSOLE_APP: + case AppStream.ComponentKind.DESKTOP_APP: + case AppStream.ComponentKind.DRIVER: + case AppStream.ComponentKind.FIRMWARE: + case AppStream.ComponentKind.GENERIC: + + case AppStream.ComponentKind.INPUT_METHOD: //ComponentKind.INPUTMETHOD is deprecated has same value so cannot be included + case AppStream.ComponentKind.LOCALIZATION: + case AppStream.ComponentKind.OPERATING_SYSTEM: + case AppStream.ComponentKind.REPOSITORY: + case AppStream.ComponentKind.RUNTIME: + case AppStream.ComponentKind.SERVICE: + case AppStream.ComponentKind.UNKNOWN: + case AppStream.ComponentKind.WEB_APP: + default: + debug ("Component kind not handled %s", component.get_kind ().to_string ()); + return new ThemedIcon ("application-default-icon"); + } } public Package? get_plugin_host_package () { diff --git a/src/Services/SearchProvider.vala b/src/Services/SearchProvider.vala index 05149e855..69cb3aea9 100644 --- a/src/Services/SearchProvider.vala +++ b/src/Services/SearchProvider.vala @@ -41,7 +41,7 @@ public class SearchProvider : Object { public HashTable[] get_result_metas (string[] results) throws GLib.Error { var result = new GenericArray> (); - var flatpak_backend = AppCenterCore.FlatpakBackend.get_default (); + unowned var flatpak_backend = AppCenterCore.FlatpakBackend.get_default (); foreach (var str in results) { var package = flatpak_backend.get_package_for_component_id (str); if (package != null) { diff --git a/src/Views/AppInfoView.vala b/src/Views/AppInfoView.vala index 5373dbebd..7b816b78c 100644 --- a/src/Views/AppInfoView.vala +++ b/src/Views/AppInfoView.vala @@ -67,7 +67,8 @@ public class AppCenter.Views.AppInfoView : Adw.NavigationPage { } construct { - AppCenterCore.FlatpakBackend.get_default ().cache_flush_needed.connect (() => { + unowned var backend = AppCenterCore.FlatpakBackend.get_default (); + backend.cache_flush_needed.connect (() => { to_recycle = true; }); @@ -154,6 +155,9 @@ public class AppCenter.Views.AppInfoView : Adw.NavigationPage { var app_icon = new Gtk.Image () { pixel_size = 128 }; + var app_icon_updated = new Gtk.Image () { + pixel_size = 128 + }; var badge_image = new Gtk.Image () { halign = Gtk.Align.END, @@ -161,8 +165,14 @@ public class AppCenter.Views.AppInfoView : Adw.NavigationPage { pixel_size = 64 }; + var app_icon_stack = new Gtk.Stack () { + transition_type = Gtk.StackTransitionType.CROSSFADE + }; + app_icon_stack.add_child (app_icon); + app_icon_stack.add_child (app_icon_updated); + var app_icon_overlay = new Gtk.Overlay () { - child = app_icon, + child = app_icon_stack, valign = Gtk.Align.START }; @@ -183,6 +193,16 @@ public class AppCenter.Views.AppInfoView : Adw.NavigationPage { } } + if (package.uses_generic_icon && package.icon_available) { + app_icon.add_css_class ("icon-dim"); + backend.on_metadata_remote_preprocessed.connect ((remote_title) => { + if (package.origin_description == remote_title) { + app_icon_updated.set_from_gicon (package.get_icon (128, scale_factor)); + app_icon_stack.visible_child = app_icon_updated; + } + }); + } + var app_title = new Gtk.Label (package.get_name ()) { selectable = true, wrap = true, diff --git a/src/Views/Homepage.vala b/src/Views/Homepage.vala index a5dae8c42..1443ac309 100644 --- a/src/Views/Homepage.vala +++ b/src/Views/Homepage.vala @@ -33,7 +33,8 @@ public class AppCenter.Homepage : Adw.NavigationPage { private Gtk.Revealer recently_updated_revealer; private Widgets.Banner appcenter_banner; - private Gtk.Button return_button; + private Gtk.EventControllerMotion banner_motion_controller; + private Gtk.Label updates_badge; private Gtk.Revealer updates_badge_revealer; @@ -48,7 +49,7 @@ public class AppCenter.Homepage : Adw.NavigationPage { hexpand = true; vexpand = true; - var banner_motion_controller = new Gtk.EventControllerMotion (); + banner_motion_controller = new Gtk.EventControllerMotion (); banner_carousel = new Adw.Carousel () { allow_long_swipes = true @@ -79,7 +80,8 @@ public class AppCenter.Homepage : Adw.NavigationPage { recently_updated_grid.attach (recently_updated_carousel, 0, 1); recently_updated_revealer = new Gtk.Revealer () { - child = recently_updated_grid + child = recently_updated_grid, + reveal_child = false }; var categories_label = new Granite.HeaderLabel (_("Categories")) { @@ -250,7 +252,6 @@ public class AppCenter.Homepage : Adw.NavigationPage { var headerbar = new Gtk.HeaderBar () { show_title_buttons = true }; - headerbar.pack_start (return_button); if (!Utils.is_running_in_guest_session ()) { headerbar.pack_end (updates_overlay); } @@ -282,16 +283,9 @@ public class AppCenter.Homepage : Adw.NavigationPage { "#7239b3" ); banner_carousel.append (appcenter_banner); - - banner_carousel.page_changed.connect (page_changed_handler); } - load_banners_and_carousels.begin ((obj, res) => { - load_banners_and_carousels.end (res); - banner_timeout_start (); - banner_motion_controller.enter.connect (banner_timeout_stop); - banner_motion_controller.leave.connect (banner_timeout_start); - }); + load_banners_and_carousels (); category_flow.child_activated.connect ((child) => { var card = (AbstractCategoryCard) child; @@ -314,14 +308,10 @@ public class AppCenter.Homepage : Adw.NavigationPage { }); } - private void page_changed_handler () { - banner_carousel.remove (appcenter_banner); - banner_carousel.page_changed.disconnect (page_changed_handler); - } + private void load_banners_and_carousels () { + unowned var backend = AppCenterCore.FlatpakBackend.get_default (); - private async void load_banners_and_carousels () { - unowned var fp_client = AppCenterCore.FlatpakBackend.get_default (); - var packages_by_release_date = fp_client.get_featured_packages_by_release_date (); + var packages_by_release_date = backend.get_featured_packages_by_release_date (); var packages_in_banner = new Gee.LinkedList (); foreach (var package in packages_by_release_date) { @@ -332,7 +322,7 @@ public class AppCenter.Homepage : Adw.NavigationPage { var installed = false; foreach (var origin_package in package.origin_packages) { try { - if (AppCenterCore.FlatpakBackend.get_default ().is_package_installed (origin_package)) { + if (backend.is_package_installed (origin_package)) { installed = true; break; } @@ -349,6 +339,14 @@ public class AppCenter.Homepage : Adw.NavigationPage { show_package (package); }); + if (package.uses_generic_icon && package.icon_available) { + backend.on_metadata_remote_preprocessed.connect ((remote_title) => { + if (remote_title == package.origin_description) { + banner.update_icon (package.get_icon (128, get_app_scale_factor ())); + } + }); + } + banner_carousel.append (banner); } } @@ -367,7 +365,7 @@ public class AppCenter.Homepage : Adw.NavigationPage { var installed = false; foreach (var origin_package in package.origin_packages) { try { - if (AppCenterCore.FlatpakBackend.get_default ().is_package_installed (origin_package)) { + if (backend.is_package_installed (origin_package)) { installed = true; break; } @@ -378,11 +376,24 @@ public class AppCenter.Homepage : Adw.NavigationPage { if (!installed) { var package_row = new AppCenter.Widgets.ListPackageRowGrid (package); + if (package.uses_generic_icon && package.icon_available) { + backend.on_metadata_remote_preprocessed.connect ((remote_title) => { + if (remote_title == package.origin_description) { + package_row.update_icon (package.get_icon (128, get_app_scale_factor ())); + } + }); + } recently_updated_carousel.append (package_row); } } + banner_carousel.remove (appcenter_banner); + banner_carousel.scroll_to (banner_carousel.get_nth_page (0), false); recently_updated_revealer.reveal_child = recently_updated_carousel.get_first_child () != null; + + banner_timeout_start (); + banner_motion_controller.enter.connect (banner_timeout_stop); + banner_motion_controller.leave.connect (banner_timeout_start); } private void banner_timeout_start () { @@ -428,6 +439,18 @@ public class AppCenter.Homepage : Adw.NavigationPage { }); } + private int get_app_scale_factor () { + var scale_factor = 1; + var app = ((Gtk.Application) Application.get_default ()); + if (app != null) { + if (app.active_window != null) { + scale_factor = app.active_window.get_scale_factor (); + } + } + + return scale_factor; + } + private abstract class AbstractCategoryCard : Gtk.FlowBoxChild { public AppStream.Category category { get; protected set; } diff --git a/src/Widgets/AppContainers/AbstractPackageRowGrid.vala b/src/Widgets/AppContainers/AbstractPackageRowGrid.vala index dd1ed2cab..e640c82d6 100644 --- a/src/Widgets/AppContainers/AbstractPackageRowGrid.vala +++ b/src/Widgets/AppContainers/AbstractPackageRowGrid.vala @@ -30,16 +30,28 @@ public abstract class AppCenter.Widgets.AbstractPackageRowGrid : Gtk.Box { protected ActionStack action_stack; protected Gtk.Label package_name; protected Gtk.Overlay app_icon_overlay; + protected Gtk.Stack image_stack; + protected Gtk.Image updated_icon_image; protected AbstractPackageRowGrid (AppCenterCore.Package package) { Object (package: package); } construct { - var app_icon = new Gtk.Image () { + var icon_image = new Gtk.Image () { pixel_size = 48 }; + updated_icon_image = new Gtk.Image () { + pixel_size = 48 + }; + + image_stack = new Gtk.Stack () { + transition_type = Gtk.StackTransitionType.CROSSFADE + }; + image_stack.add_child (icon_image); + image_stack.add_child (updated_icon_image); + var badge_image = new Gtk.Image () { halign = Gtk.Align.END, valign = Gtk.Align.END, @@ -47,7 +59,7 @@ public abstract class AppCenter.Widgets.AbstractPackageRowGrid : Gtk.Box { }; app_icon_overlay = new Gtk.Overlay () { - child = app_icon + child = image_stack }; action_stack = new ActionStack (package) { @@ -58,12 +70,14 @@ public abstract class AppCenter.Widgets.AbstractPackageRowGrid : Gtk.Box { var plugin_host_package = package.get_plugin_host_package (); if (package.kind == AppStream.ComponentKind.ADDON && plugin_host_package != null) { - app_icon.gicon = plugin_host_package.get_icon (app_icon.pixel_size, scale_factor); + icon_image.gicon = plugin_host_package.get_icon (icon_image.pixel_size, scale_factor); + updated_icon_image.gicon = plugin_host_package.get_icon (updated_icon_image.pixel_size, scale_factor); badge_image.gicon = package.get_icon (badge_image.pixel_size / 2, scale_factor); app_icon_overlay.add_overlay (badge_image); } else { - app_icon.gicon = package.get_icon (app_icon.pixel_size, scale_factor); + icon_image.gicon = package.get_icon (icon_image.pixel_size, scale_factor); + updated_icon_image.gicon = package.get_icon (updated_icon_image.pixel_size, scale_factor); if (package.is_runtime_updates) { badge_image.icon_name = "system-software-update"; @@ -71,9 +85,19 @@ public abstract class AppCenter.Widgets.AbstractPackageRowGrid : Gtk.Box { } } + if (package.uses_generic_icon && package.icon_available) { + icon_image.add_css_class ("icon-dim"); + } + margin_top = 6; margin_start = 12; margin_bottom = 6; margin_end = 12; } + + public void update_icon (Icon icon) { + updated_icon_image.clear (); + updated_icon_image.set_from_gicon (icon); + image_stack.visible_child = updated_icon_image; + } } diff --git a/src/Widgets/AppContainers/ListPackageRowGrid.vala b/src/Widgets/AppContainers/ListPackageRowGrid.vala index c7bbfa5e3..9825e7842 100644 --- a/src/Widgets/AppContainers/ListPackageRowGrid.vala +++ b/src/Widgets/AppContainers/ListPackageRowGrid.vala @@ -55,6 +55,7 @@ public class AppCenter.Widgets.ListPackageRowGrid : AbstractPackageRowGrid { column_spacing = 12, row_spacing = 3 }; + grid.attach (app_icon_overlay, 0, 0, 1, 2); grid.attach (package_name, 1, 0); grid.attach (package_summary, 1, 1); diff --git a/src/Widgets/BackButton.vala b/src/Widgets/BackButton.vala index eab1ec7a1..c0c1d8343 100644 --- a/src/Widgets/BackButton.vala +++ b/src/Widgets/BackButton.vala @@ -24,7 +24,9 @@ public class AppCenter.BackButton : Gtk.Button { var current_page = (Adw.NavigationPage) get_ancestor (typeof (Adw.NavigationPage)); var navigation_view = (Adw.NavigationView) get_ancestor (typeof (Adw.NavigationView)); var previous_page = navigation_view.get_previous_page (current_page); - label_widget.label = previous_page.title; + if (previous_page != null) { + label_widget.label = previous_page.title; + } }); } } diff --git a/src/Widgets/Banner.vala b/src/Widgets/Banner.vala index c7e012959..a2ef8c2d7 100644 --- a/src/Widgets/Banner.vala +++ b/src/Widgets/Banner.vala @@ -32,44 +32,54 @@ public class AppCenter.Widgets.Banner : Gtk.Button { public string description { get; construct; } public string app_name { get; construct; } public string summary { get; construct; } + public bool uses_generic_icon { get; construct set; } + + private Gtk.Stack image_stack; + private Gtk.Image updated_icon_image; public Banner (string name, string summary, string description, Icon icon, string brand_color) { Object ( - brand_color: brand_color, + app_name: name, + summary: summary, description: description, icon: icon, - app_name: name, - summary: summary + brand_color: brand_color, + uses_generic_icon: false ); } public Banner.from_package (AppCenterCore.Package package) { // Can't get widget scale factor before it's realized - var scale_factor = ((Gtk.Application) Application.get_default ()).active_window.get_scale_factor (); + var scale_factor = 1; + var app = ((Gtk.Application) Application.get_default ()); + if (app != null) { + if (app.active_window != null) { + scale_factor = app.active_window.get_scale_factor (); + } + } + + var pkg_icon = package.get_icon (128, scale_factor); Object ( - name: package.get_name (), + app_name: package.get_name (), summary: package.get_summary (), description: package.get_description (), - icon: package.get_icon (128, scale_factor), - brand_color: package.get_color_primary () + icon: pkg_icon, + brand_color: package.get_color_primary (), + uses_generic_icon: package.uses_generic_icon && package.icon_available ); } construct { var name_label = new Gtk.Label (app_name) { - max_width_chars = 50, use_markup = true, - wrap = true, xalign = 0 }; name_label.add_css_class ("name"); var summary_label = new Gtk.Label (summary) { - max_width_chars = 50, use_markup = true, - wrap = true, xalign = 0 }; summary_label.add_css_class ("summary"); @@ -82,7 +92,6 @@ public class AppCenter.Widgets.Banner : Gtk.Button { var description_label = new Gtk.Label (description) { ellipsize = Pango.EllipsizeMode.END, lines = 2, - max_width_chars = 50, use_markup = true, wrap = true, xalign = 0 @@ -90,6 +99,15 @@ public class AppCenter.Widgets.Banner : Gtk.Button { description_label.add_css_class ("description"); var icon_image = new Gtk.Image.from_gicon (icon); + if (uses_generic_icon) { + icon_image.add_css_class ("icon-dim"); + } + updated_icon_image = new Gtk.Image.from_gicon (icon); + image_stack = new Gtk.Stack () { + transition_type = Gtk.StackTransitionType.CROSSFADE, + }; + image_stack.add_child (icon_image); + image_stack.add_child (updated_icon_image); var inner_box = new Gtk.Box (VERTICAL, 0) { valign = CENTER @@ -98,10 +116,10 @@ public class AppCenter.Widgets.Banner : Gtk.Button { inner_box.append (summary_label); inner_box.append (description_label); - var outer_box = new Gtk.Box (HORIZONTAL, 0) { + var outer_box = new Gtk.Box (HORIZONTAL, 24) { halign = CENTER }; - outer_box.append (icon_image); + outer_box.append (image_stack); outer_box.append (inner_box); add_css_class ("banner"); @@ -131,4 +149,10 @@ public class AppCenter.Widgets.Banner : Gtk.Button { critical ("Unable to set accent color: %s", e.message); } } + + public void update_icon (Icon icon) { + uses_generic_icon = false; + updated_icon_image.set_from_gicon (icon); + image_stack.visible_child = updated_icon_image; + } }