[gnome-software: 5/13] Add GsAppDetailsPage




commit 1ac36b571c0befa8808bdc4d32b081406c1f66dd
Author: Adrien Plazas <kekun plazas laposte net>
Date:   Wed Aug 4 15:20:03 2021 +0200

    Add GsAppDetailsPage
    
    This is extracted from GsUpdateDialog, which will use this widget
    instead in a later commit.

 po/POTFILES.in                   |   2 +
 src/gnome-software.gresource.xml |   1 +
 src/gs-app-details-page.c        | 460 +++++++++++++++++++++++++++++++++++++++
 src/gs-app-details-page.h        |  29 +++
 src/gs-app-details-page.ui       | 152 +++++++++++++
 src/meson.build                  |   1 +
 6 files changed, 645 insertions(+)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 68d98bc22..eccc004da 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -8,6 +8,8 @@ src/gs-age-rating-context-dialog.ui
 lib/gs-app.c
 src/gs-app-addon-row.c
 src/gs-app-addon-row.ui
+src/gs-app-details-page.c
+src/gs-app-details-page.ui
 src/gs-app-version-history-dialog.ui
 src/gs-app-version-history-row.c
 src/gs-app-version-history-row.ui
diff --git a/src/gnome-software.gresource.xml b/src/gnome-software.gresource.xml
index 1eb5ee561..8aa271e0b 100644
--- a/src/gnome-software.gresource.xml
+++ b/src/gnome-software.gresource.xml
@@ -4,6 +4,7 @@
   <file preprocess="xml-stripblanks">gs-age-rating-context-dialog.ui</file>
   <file preprocess="xml-stripblanks">gs-app-addon-row.ui</file>
   <file preprocess="xml-stripblanks">gs-app-context-bar.ui</file>
+  <file preprocess="xml-stripblanks">gs-app-details-page.ui</file>
   <file preprocess="xml-stripblanks">gs-app-version-history-dialog.ui</file>
   <file preprocess="xml-stripblanks">gs-app-version-history-row.ui</file>
   <file preprocess="xml-stripblanks">gs-app-row.ui</file>
diff --git a/src/gs-app-details-page.c b/src/gs-app-details-page.c
new file mode 100644
index 000000000..6747a5977
--- /dev/null
+++ b/src/gs-app-details-page.c
@@ -0,0 +1,460 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2013-2016 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2014-2018 Kalev Lember <klember redhat com>
+ * Copyright (C) 2021 Purism SPC
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+/**
+ * SECTION:gs-app-details-page
+ * @title: GsAppDetailsPage
+ * @include: gnome-software.h
+ * @stability: Stable
+ * @short_description: A small page showing an application's details
+ *
+ * This is a page from #GsUpdateDialog.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <handy.h>
+
+#include "gs-app-details-page.h"
+#include "gs-app-row.h"
+#include "gs-update-list.h"
+#include "gs-common.h"
+
+typedef enum {
+       PROP_APP = 1,
+       PROP_SHOW_BACK_BUTTON,
+} GsAppDetailsPageProperty;
+
+enum {
+       SIGNAL_BACK_CLICKED,
+       SIGNAL_LAST
+};
+
+static GParamSpec *obj_props[PROP_SHOW_BACK_BUTTON + 1] = { NULL, };
+
+static guint signals[SIGNAL_LAST] = { 0 };
+
+struct _GsAppDetailsPage
+{
+       GtkBox           parent_instance;
+
+       GtkWidget       *back_button;
+       GtkWidget       *box_header;
+       GtkWidget       *header_bar;
+       GtkWidget       *image_icon;
+       GtkWidget       *label_details;
+       GtkWidget       *label_name;
+       GtkWidget       *label_summary;
+       GtkWidget       *permissions_section_box;
+       GtkWidget       *permissions_section_content;
+       GtkWidget       *scrolledwindow_details;
+
+       GsApp           *app;  /* (owned) (nullable) */
+};
+
+G_DEFINE_TYPE (GsAppDetailsPage, gs_app_details_page, GTK_TYPE_BOX)
+
+static const struct {
+        GsAppPermissions permission;
+        const char *title;
+        const char *subtitle;
+} permission_display_data[] = {
+  { GS_APP_PERMISSIONS_NETWORK, N_("Network"), N_("Can communicate over the network") },
+  { GS_APP_PERMISSIONS_SYSTEM_BUS, N_("System Services"), N_("Can access D-Bus services on the system bus") 
},
+  { GS_APP_PERMISSIONS_SESSION_BUS, N_("Session Services"), N_("Can access D-Bus services on the session 
bus") },
+  { GS_APP_PERMISSIONS_DEVICES, N_("Devices"), N_("Can access system device files") },
+  { GS_APP_PERMISSIONS_HOME_FULL, N_("Home folder"), N_("Can view, edit and create files") },
+  { GS_APP_PERMISSIONS_HOME_READ, N_("Home folder"), N_("Can view files") },
+  { GS_APP_PERMISSIONS_FILESYSTEM_FULL, N_("File system"), N_("Can view, edit and create files") },
+  { GS_APP_PERMISSIONS_FILESYSTEM_READ, N_("File system"), N_("Can view files") },
+  { GS_APP_PERMISSIONS_DOWNLOADS_FULL, N_("Downloads folder"), N_("Can view, edit and create files") },
+  { GS_APP_PERMISSIONS_DOWNLOADS_READ, N_("Downloads folder"), N_("Can view files") },
+  { GS_APP_PERMISSIONS_SETTINGS, N_("Settings"), N_("Can view and change any settings") },
+  { GS_APP_PERMISSIONS_X11, N_("Legacy display system"), N_("Uses an old, insecure display system") },
+  { GS_APP_PERMISSIONS_ESCAPE_SANDBOX, N_("Sandbox escape"), N_("Can escape the sandbox and circumvent any 
other restrictions") },
+};
+
+static void
+populate_permissions_section (GsAppDetailsPage *page, GsAppPermissions permissions)
+{
+       GList *children;
+
+       children = gtk_container_get_children (GTK_CONTAINER (page->permissions_section_content));
+       for (GList *l = children; l != NULL; l = l->next)
+               gtk_widget_destroy (GTK_WIDGET (l->data));
+       g_list_free (children);
+
+       for (gsize i = 0; i < G_N_ELEMENTS (permission_display_data); i++) {
+               GtkWidget *row, *image, *box, *label;
+
+               if ((permissions & permission_display_data[i].permission) == 0)
+                       continue;
+
+               row = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+               gtk_widget_show (row);
+               if ((permission_display_data[i].permission & ~MEDIUM_PERMISSIONS) != 0) {
+                       gtk_style_context_add_class (gtk_widget_get_style_context (row), 
"permission-row-warning");
+               }
+
+               image = gtk_image_new_from_icon_name ("dialog-warning-symbolic", GTK_ICON_SIZE_MENU);
+               if ((permission_display_data[i].permission & ~MEDIUM_PERMISSIONS) == 0)
+                       gtk_widget_set_opacity (image, 0);
+
+               gtk_widget_show (image);
+               gtk_container_add (GTK_CONTAINER (row), image);
+
+               box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+               gtk_widget_show (box);
+               gtk_container_add (GTK_CONTAINER (row), box);
+
+               label = gtk_label_new (_(permission_display_data[i].title));
+               gtk_label_set_xalign (GTK_LABEL (label), 0);
+               gtk_widget_show (label);
+               gtk_container_add (GTK_CONTAINER (box), label);
+
+               label = gtk_label_new (_(permission_display_data[i].subtitle));
+               gtk_label_set_xalign (GTK_LABEL (label), 0);
+               gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label");
+               gtk_widget_show (label);
+               gtk_container_add (GTK_CONTAINER (box), label);
+
+               gtk_container_add (GTK_CONTAINER (page->permissions_section_content), row);
+       }
+}
+
+static void
+set_updates_description_ui (GsAppDetailsPage *page, GsApp *app)
+{
+       AsComponentKind kind;
+       g_autoptr(GIcon) icon = NULL;
+       guint icon_size;
+       const gchar *update_details;
+
+       /* FIXME support app == NULL */
+
+       /* set window title */
+       kind = gs_app_get_kind (app);
+       if (kind == AS_COMPONENT_KIND_GENERIC &&
+           gs_app_get_special_kind (app) == GS_APP_SPECIAL_KIND_OS_UPDATE) {
+               hdy_header_bar_set_title (HDY_HEADER_BAR (page->header_bar),
+                                         gs_app_get_name (app));
+       } else if (gs_app_get_source_default (app) != NULL &&
+                  gs_app_get_update_version (app) != NULL) {
+               g_autofree gchar *tmp = NULL;
+               /* Translators: This is the source and upgrade version of an
+                * application, shown to the user when they view more detailed
+                * information about pending updates. The source is of the form
+                * ‘deja-dup’ (a package name) or
+                * ‘app/org.gnome.Builder/x86_64/main’ (a flatpak ID), and the
+                * version is of the form ‘40.4-1.fc34’ (a version number). */
+               tmp = g_strdup_printf (_("%s %s"),
+                                      gs_app_get_source_default (app),
+                                      gs_app_get_update_version (app));
+               hdy_header_bar_set_title (HDY_HEADER_BAR (page->header_bar), tmp);
+       } else if (gs_app_get_source_default (app) != NULL) {
+               hdy_header_bar_set_title (HDY_HEADER_BAR (page->header_bar),
+                                         gs_app_get_source_default (app));
+       } else {
+               hdy_header_bar_set_title (HDY_HEADER_BAR (page->header_bar),
+                                         gs_app_get_update_version (app));
+       }
+
+       /* set update header */
+       gtk_widget_set_visible (page->box_header, kind == AS_COMPONENT_KIND_DESKTOP_APP);
+       update_details = gs_app_get_update_details (app);
+       if (update_details == NULL) {
+               /* TRANSLATORS: this is where the packager did not write
+                * a description for the update */
+               update_details = _("No update description available.");
+       }
+       gtk_label_set_label (GTK_LABEL (page->label_details), update_details);
+       gtk_label_set_label (GTK_LABEL (page->label_name), gs_app_get_name (app));
+       gtk_label_set_label (GTK_LABEL (page->label_summary), gs_app_get_summary (app));
+
+       /* set the icon; fall back to 64px if 96px isn’t available, which sometimes
+        * happens at 2× scale factor (hi-DPI) */
+       icon_size = 96;
+       icon = gs_app_get_icon_for_size (app,
+                                        icon_size,
+                                        gtk_widget_get_scale_factor (page->image_icon),
+                                        NULL);
+       if (icon == NULL) {
+               icon_size = 64;
+               icon = gs_app_get_icon_for_size (app,
+                                                icon_size,
+                                                gtk_widget_get_scale_factor (page->image_icon),
+                                                NULL);
+       }
+       if (icon == NULL) {
+               icon_size = 96;
+               icon = gs_app_get_icon_for_size (app,
+                                                icon_size,
+                                                gtk_widget_get_scale_factor (page->image_icon),
+                                                "system-component-application");
+       }
+
+       gtk_image_set_pixel_size (GTK_IMAGE (page->image_icon), icon_size);
+       gtk_image_set_from_gicon (GTK_IMAGE (page->image_icon), icon,
+                                 GTK_ICON_SIZE_INVALID);
+
+       if (gs_app_has_quirk (app, GS_APP_QUIRK_NEW_PERMISSIONS)) {
+               gtk_widget_show (page->permissions_section_box);
+               populate_permissions_section (page, gs_app_get_update_permissions (app));
+       } else {
+               gtk_widget_hide (page->permissions_section_box);
+       }
+}
+
+/**
+ * gs_app_details_page_get_app:
+ * @page: a #GsAppDetailsPage
+ *
+ * Get the value of #GsAppDetailsPage:app.
+ *
+ * Returns: (nullable) (transfer none): the app
+ *
+ * Since: 41
+ */
+GsApp *
+gs_app_details_page_get_app (GsAppDetailsPage *page)
+{
+       g_return_val_if_fail (GS_IS_APP_DETAILS_PAGE (page), NULL);
+       return page->app;
+}
+
+/**
+ * gs_app_details_page_set_app:
+ * @page: a #GsAppDetailsPage
+ * @app: (transfer none) (nullable): new app
+ *
+ * Set the value of #GsAppDetailsPage:app.
+ *
+ * Since: 41
+ */
+void
+gs_app_details_page_set_app (GsAppDetailsPage *page, GsApp *app)
+{
+       g_return_if_fail (GS_IS_APP_DETAILS_PAGE (page));
+       g_return_if_fail (!app || GS_IS_APP (app));
+
+       if (page->app == app)
+               return;
+
+       g_set_object (&page->app, app);
+
+       set_updates_description_ui (page, app);
+
+       g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_APP]);
+}
+
+/**
+ * gs_app_details_page_get_show_back_button:
+ * @page: a #GsAppDetailsPage
+ *
+ * Get the value of #GsAppDetailsPage:show-back-button.
+ *
+ * Returns: whether to show the back button
+ *
+ * Since: 41
+ */
+gboolean
+gs_app_details_page_get_show_back_button (GsAppDetailsPage *page)
+{
+       g_return_val_if_fail (GS_IS_APP_DETAILS_PAGE (page), FALSE);
+       return gtk_widget_get_visible (page->back_button);
+}
+
+/**
+ * gs_app_details_page_set_show_back_button:
+ * @page: a #GsAppDetailsPage
+ * @show_back_button: whether to show the back button
+ *
+ * Set the value of #GsAppDetailsPage:show-back-button.
+ *
+ * Since: 41
+ */
+void
+gs_app_details_page_set_show_back_button (GsAppDetailsPage *page, gboolean show_back_button)
+{
+       g_return_if_fail (GS_IS_APP_DETAILS_PAGE (page));
+
+       show_back_button = !!show_back_button;
+
+       if (gtk_widget_get_visible (page->back_button) == show_back_button)
+               return;
+
+       gtk_widget_set_visible (page->back_button, show_back_button);
+
+       g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_SHOW_BACK_BUTTON]);
+}
+
+static void
+back_clicked_cb (GtkWidget *widget, GsAppDetailsPage *page)
+{
+       g_signal_emit (page, signals[SIGNAL_BACK_CLICKED], 0);
+}
+
+static void
+scrollbar_mapped_cb (GtkWidget *sb, GtkScrolledWindow *swin)
+{
+       GtkWidget *frame;
+
+       frame = gtk_bin_get_child (GTK_BIN (gtk_bin_get_child (GTK_BIN (swin))));
+
+       if (gtk_widget_get_mapped (GTK_WIDGET (sb))) {
+               gtk_scrolled_window_set_shadow_type (swin, GTK_SHADOW_IN);
+               if (GTK_IS_FRAME (frame))
+                       gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
+       } else {
+               if (GTK_IS_FRAME (frame))
+                       gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+               gtk_scrolled_window_set_shadow_type (swin, GTK_SHADOW_NONE);
+       }
+}
+
+static void
+gs_app_details_page_dispose (GObject *object)
+{
+       GsAppDetailsPage *page = GS_APP_DETAILS_PAGE (object);
+
+       g_clear_object (&page->app);
+
+       G_OBJECT_CLASS (gs_app_details_page_parent_class)->dispose (object);
+}
+
+static void
+gs_app_details_page_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+       GsAppDetailsPage *page = GS_APP_DETAILS_PAGE (object);
+
+       switch ((GsAppDetailsPageProperty) prop_id) {
+       case PROP_APP:
+               g_value_set_object (value, gs_app_details_page_get_app (page));
+               break;
+       case PROP_SHOW_BACK_BUTTON:
+               g_value_set_boolean (value, gs_app_details_page_get_show_back_button (page));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gs_app_details_page_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+       GsAppDetailsPage *page = GS_APP_DETAILS_PAGE (object);
+
+       switch ((GsAppDetailsPageProperty) prop_id) {
+       case PROP_APP:
+               gs_app_details_page_set_app (page, g_value_get_object (value));
+               break;
+       case PROP_SHOW_BACK_BUTTON:
+               gs_app_details_page_set_show_back_button (page, g_value_get_boolean (value));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gs_app_details_page_init (GsAppDetailsPage *page)
+{
+       GtkWidget *scrollbar;
+
+       gtk_widget_init_template (GTK_WIDGET (page));
+
+       scrollbar = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (page->scrolledwindow_details));
+       g_signal_connect (scrollbar, "map", G_CALLBACK (scrollbar_mapped_cb), page->scrolledwindow_details);
+       g_signal_connect (scrollbar, "unmap", G_CALLBACK (scrollbar_mapped_cb), page->scrolledwindow_details);
+
+}
+
+static void
+gs_app_details_page_class_init (GsAppDetailsPageClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+       object_class->dispose = gs_app_details_page_dispose;
+       object_class->get_property = gs_app_details_page_get_property;
+       object_class->set_property = gs_app_details_page_set_property;
+
+       /**
+        * GsAppDetailsPage:app: (nullable)
+        *
+        * The app to present.
+        *
+        * Since: 41
+        */
+       obj_props[PROP_APP] =
+               g_param_spec_object ("app", NULL, NULL,
+                                    GS_TYPE_APP,
+                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+       /**
+        * GsAppDetailsPage:show-back-button
+        *
+        * Whether to show the back button.
+        *
+        * Since: 41
+        */
+       obj_props[PROP_SHOW_BACK_BUTTON] =
+               g_param_spec_boolean ("show-back-button", NULL, NULL,
+                                    TRUE,
+                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+       g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_props), obj_props);
+
+       /**
+        * GsAppDetailsPage:back-clicked:
+        * @app: a #GsApp
+        *
+        * Emitted when the back button got activated and the #GsUpdateDialog
+        * containing this page is expected to go back.
+        *
+        * Since: 41
+        */
+       signals[SIGNAL_BACK_CLICKED] =
+               g_signal_new ("back-clicked",
+                             G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+                             0, NULL, NULL, g_cclosure_marshal_generic,
+                             G_TYPE_NONE, 0);
+
+       gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/Software/gs-app-details-page.ui");
+
+       gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, back_button);
+       gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, box_header);
+       gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, header_bar);
+       gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, image_icon);
+       gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, label_details);
+       gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, label_name);
+       gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, label_summary);
+       gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, permissions_section_box);
+       gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, permissions_section_content);
+       gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, scrolledwindow_details);
+       gtk_widget_class_bind_template_callback (widget_class, back_clicked_cb);
+}
+
+/**
+ * gs_app_details_page_new:
+ *
+ * Create a new #GsAppDetailsPage.
+ *
+ * Returns: (transfer full): a new #GsAppDetailsPage
+ * Since: 41
+ */
+GtkWidget *
+gs_app_details_page_new (void)
+{
+       return GTK_WIDGET (g_object_new (GS_TYPE_APP_DETAILS_PAGE, NULL));
+}
diff --git a/src/gs-app-details-page.h b/src/gs-app-details-page.h
new file mode 100644
index 000000000..60b823abd
--- /dev/null
+++ b/src/gs-app-details-page.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2021 Purism SPC
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "gnome-software-private.h"
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_APP_DETAILS_PAGE (gs_app_details_page_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsAppDetailsPage, gs_app_details_page, GS, APP_DETAILS_PAGE, GtkBox)
+
+GtkWidget      *gs_app_details_page_new                        (void);
+GsApp          *gs_app_details_page_get_app                    (GsAppDetailsPage       *page);
+void            gs_app_details_page_set_app                    (GsAppDetailsPage       *page,
+                                                                GsApp                  *app);
+gboolean        gs_app_details_page_get_show_back_button       (GsAppDetailsPage       *page);
+void            gs_app_details_page_set_show_back_button       (GsAppDetailsPage       *page,
+                                                                gboolean                show_back_button);
+
+G_END_DECLS
diff --git a/src/gs-app-details-page.ui b/src/gs-app-details-page.ui
new file mode 100644
index 000000000..1049d75a0
--- /dev/null
+++ b/src/gs-app-details-page.ui
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.10"/>
+  <template class="GsAppDetailsPage" parent="GtkBox">
+    <property name="orientation">vertical</property>
+
+    <child>
+      <object class="HdyHeaderBar" id="header_bar">
+        <property name="show_close_button">True</property>
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkButton" id="back_button">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="back_clicked_cb"/>
+            <style>
+              <class name="image-button"/>
+            </style>
+            <child internal-child="accessible">
+              <object class="AtkObject">
+                <property name="accessible-name" translatable="yes">Go back</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="icon_name">go-previous-symbolic</property>
+                <property name="icon_size">1</property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkBox" id="box7">
+        <property name="visible">True</property>
+        <property name="margin_start">6</property>
+        <property name="margin_end">6</property>
+        <property name="margin_top">6</property>
+        <property name="margin_bottom">9</property>
+        <property name="border_width">5</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">9</property>
+        <child>
+          <object class="GtkBox" id="box_header">
+            <property name="visible">True</property>
+            <property name="spacing">9</property>
+            <child>
+              <object class="GtkImage" id="image_icon">
+                <property name="visible">True</property>
+                <property name="pixel_size">96</property>
+                <property name="icon_name">system-component-application</property>
+                <property name="icon_size">0</property>
+                <style>
+                  <class name="icon-dropshadow"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox" id="box9">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">3</property>
+                <child>
+                  <object class="GtkLabel" id="label_name">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label">Inkscape</property>
+                    <property name="selectable">True</property>
+                    <property name="wrap">True</property>
+                    <property name="max_width_chars">50</property>
+                    <property name="width_chars">50</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                      <attribute name="scale" value="1.3999999999999999"/>
+                    </attributes>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label_summary">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label">Vector based drawing program</property>
+                    <property name="selectable">True</property>
+                    <property name="wrap">True</property>
+                    <property name="max_width_chars">50</property>
+                    <property name="width_chars">50</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox" id="permissions_section_box">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">6</property>
+            <property name="margin_top">12</property>
+            <property name="margin_bottom">18</property>
+            <child>
+              <object class="GtkLabel" id="permissions_section_title">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="halign">start</property>
+                <property name="margin_bottom">6</property>
+                <property name="label" translatable="yes">Requires additional permissions</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox" id="permissions_section_content">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">12</property>
+                <property name="margin-start">18</property>
+                <property name="margin-end">18</property>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolledwindow_details">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="vexpand">True</property>
+            <property name="hscrollbar_policy">never</property>
+            <property name="vscrollbar_policy">automatic</property>
+            <property name="shadow_type">none</property>
+            <child>
+              <object class="GtkLabel" id="label_details">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="yalign">0</property>
+                <property name="margin">6</property>
+                <property name="label">New in kmod 14-1
+* Moo
+* bar</property>
+                <property name="wrap">True</property>
+                <property name="selectable">True</property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/meson.build b/src/meson.build
index a15cb41f2..3cc514778 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -29,6 +29,7 @@ gnome_software_sources = [
   'gs-app-version-history-row.c',
   'gs-application.c',
   'gs-app-context-bar.c',
+  'gs-app-details-page.c',
   'gs-app-row.c',
   'gs-app-tile.c',
   'gs-basic-auth-dialog.c',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]