[gnome-software] Add a power-user moderation view in gnome-software



commit df79804d605574eaaf301dc32601e1c9f463181e
Author: Richard Hughes <richard hughsie com>
Date:   Thu Feb 11 18:09:29 2016 +0000

    Add a power-user moderation view in gnome-software
    
    This is a prototype for power-users and is not fully complete or designed...
    
    It is, however, hugely useful for me.

 po/POTFILES.in                          |    1 +
 src/Makefile.am                         |    3 +
 src/gnome-software.gresource.xml        |    1 +
 src/gnome-software.ui                   |    8 +
 src/gs-application.c                    |    2 +
 src/gs-plugin-loader.c                  |   86 +++++++++
 src/gs-plugin-loader.h                  |    8 +
 src/gs-plugin.h                         |    9 +
 src/gs-review-row.c                     |   17 ++-
 src/gs-review-row.ui                    |   13 ++
 src/gs-review.h                         |    1 +
 src/gs-shell-moderate.c                 |  313 +++++++++++++++++++++++++++++++
 src/gs-shell-moderate.h                 |   51 +++++
 src/gs-shell-moderate.ui                |   62 ++++++
 src/gs-shell.c                          |   12 ++
 src/gs-shell.h                          |    1 +
 src/plugins/gs-plugin-xdg-app-reviews.c |  120 ++++++++----
 17 files changed, 666 insertions(+), 42 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index fdf8ea3..8b40618 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -36,6 +36,7 @@ src/gs-shell-details.c
 src/gs-shell-installed.c
 [type: gettext/glade]src/gs-shell-installed.ui
 src/gs-shell-overview.c
+[type: gettext/glade]src/gs-shell-moderate.ui
 [type: gettext/glade]src/gs-shell-overview.ui
 src/gs-shell-search.c
 [type: gettext/glade]src/gs-shell-search.ui
diff --git a/src/Makefile.am b/src/Makefile.am
index d8b12bb..dfe3aa3 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -44,6 +44,7 @@ UI_FILES =                                            \
        gs-shell-extras.ui                              \
        gs-shell-details.ui                             \
        gs-shell-installed.ui                           \
+       gs-shell-moderate.ui                            \
        gs-shell-overview.ui                            \
        gs-shell-search.ui                              \
        gs-shell-updates.ui                             \
@@ -172,6 +173,8 @@ gnome_software_SOURCES =                            \
        gs-shell-extras.h                               \
        gs-shell-installed.c                            \
        gs-shell-installed.h                            \
+       gs-shell-moderate.c                             \
+       gs-shell-moderate.h                             \
        gs-shell-overview.c                             \
        gs-shell-overview.h                             \
        gs-shell-updates.c                              \
diff --git a/src/gnome-software.gresource.xml b/src/gnome-software.gresource.xml
index 261562f..9c29e61 100644
--- a/src/gnome-software.gresource.xml
+++ b/src/gnome-software.gresource.xml
@@ -20,6 +20,7 @@
   <file preprocess="xml-stripblanks">gs-shell-extras.ui</file>
   <file preprocess="xml-stripblanks">gs-shell-details.ui</file>
   <file preprocess="xml-stripblanks">gs-shell-installed.ui</file>
+  <file preprocess="xml-stripblanks">gs-shell-moderate.ui</file>
   <file preprocess="xml-stripblanks">gs-shell-overview.ui</file>
   <file preprocess="xml-stripblanks">gs-shell-search.ui</file>
   <file preprocess="xml-stripblanks">gs-shell-updates.ui</file>
diff --git a/src/gnome-software.ui b/src/gnome-software.ui
index 6392826..03d1ef2 100644
--- a/src/gnome-software.ui
+++ b/src/gnome-software.ui
@@ -407,6 +407,14 @@
               </packing>
             </child>
             <child>
+              <object class="GsShellModerate" id="shell_moderate">
+                <property name="visible">True</property>
+              </object>
+              <packing>
+                <property name="name">moderate</property>
+              </packing>
+            </child>
+            <child>
               <object class="GsShellSearch" id="shell_search">
                 <property name="visible">True</property>
               </object>
diff --git a/src/gs-application.c b/src/gs-application.c
index 71f6f66..e811a15 100644
--- a/src/gs-application.c
+++ b/src/gs-application.c
@@ -513,6 +513,8 @@ set_mode_activated (GSimpleAction *action,
                gs_shell_set_mode (app->shell, GS_SHELL_MODE_UPDATES);
        } else if (g_strcmp0 (mode, "installed") == 0) {
                gs_shell_set_mode (app->shell, GS_SHELL_MODE_INSTALLED);
+       } else if (g_strcmp0 (mode, "moderate") == 0) {
+               gs_shell_set_mode (app->shell, GS_SHELL_MODE_MODERATE);
        } else if (g_strcmp0 (mode, "overview") == 0) {
                gs_shell_set_mode (app->shell, GS_SHELL_MODE_OVERVIEW);
        } else if (g_strcmp0 (mode, "updated") == 0) {
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index 21b17f9..9432c3e 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -1005,6 +1005,89 @@ gs_plugin_loader_get_distro_upgrades_finish (GsPluginLoader *plugin_loader,
 /******************************************************************************/
 
 /**
+ * gs_plugin_loader_get_unvoted_reviews_thread_cb:
+ **/
+static void
+gs_plugin_loader_get_unvoted_reviews_thread_cb (GTask *task,
+                                               gpointer object,
+                                               gpointer task_data,
+                                               GCancellable *cancellable)
+{
+       GsPluginLoaderAsyncState *state = (GsPluginLoaderAsyncState *) task_data;
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
+       GError *error = NULL;
+
+       state->list = gs_plugin_loader_run_results (plugin_loader,
+                                                   "gs_plugin_add_unvoted_reviews",
+                                                   state->flags,
+                                                   cancellable,
+                                                   &error);
+       if (error != NULL) {
+               g_task_return_error (task, error);
+               return;
+       }
+
+       /* filter package list */
+       gs_plugin_list_filter_duplicates (&state->list);
+
+       /* dedupe applications we already know about */
+       gs_plugin_loader_list_dedupe (plugin_loader, state->list);
+
+       /* success */
+       g_task_return_pointer (task, gs_plugin_list_copy (state->list), (GDestroyNotify) gs_plugin_list_free);
+}
+
+/**
+ * gs_plugin_loader_get_unvoted_reviews_async:
+ *
+ * This method calls all plugins that implement the gs_plugin_add_unvoted_reviews()
+ * function.
+ **/
+void
+gs_plugin_loader_get_unvoted_reviews_async (GsPluginLoader *plugin_loader,
+                                           GsPluginRefineFlags flags,
+                                           GCancellable *cancellable,
+                                           GAsyncReadyCallback callback,
+                                           gpointer user_data)
+{
+       GsPluginLoaderAsyncState *state;
+       g_autoptr(GTask) task = NULL;
+
+       g_return_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader));
+       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+       /* save state */
+       state = g_slice_new0 (GsPluginLoaderAsyncState);
+       state->flags = flags;
+
+       /* run in a thread */
+       task = g_task_new (plugin_loader, cancellable, callback, user_data);
+       g_task_set_task_data (task, state, (GDestroyNotify) gs_plugin_loader_free_async_state);
+       g_task_set_return_on_cancel (task, TRUE);
+       g_task_run_in_thread (task, gs_plugin_loader_get_unvoted_reviews_thread_cb);
+}
+
+/**
+ * gs_plugin_loader_get_unvoted_reviews_finish:
+ *
+ * Return value: (element-type GsApp) (transfer full): A list of applications
+ **/
+GList *
+gs_plugin_loader_get_unvoted_reviews_finish (GsPluginLoader *plugin_loader,
+                                            GAsyncResult *res,
+                                            GError **error)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
+       g_return_val_if_fail (G_IS_TASK (res), NULL);
+       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
+       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+       return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+/******************************************************************************/
+
+/**
  * gs_plugin_loader_get_sources_thread_cb:
  **/
 static void
@@ -2793,6 +2876,9 @@ gs_plugin_loader_review_action_async (GsPluginLoader *plugin_loader,
        case GS_REVIEW_ACTION_REMOVE:
                state->function_name = "gs_plugin_review_remove";
                break;
+       case GS_REVIEW_ACTION_DISMISS:
+               state->function_name = "gs_plugin_review_dismiss";
+               break;
        default:
                g_assert_not_reached ();
                break;
diff --git a/src/gs-plugin-loader.h b/src/gs-plugin-loader.h
index 3925dc9..2365cb0 100644
--- a/src/gs-plugin-loader.h
+++ b/src/gs-plugin-loader.h
@@ -95,6 +95,14 @@ void          gs_plugin_loader_get_distro_upgrades_async (GsPluginLoader     *plugin_loader
 GList          *gs_plugin_loader_get_distro_upgrades_finish (GsPluginLoader    *plugin_loader,
                                                         GAsyncResult   *res,
                                                         GError         **error);
+void            gs_plugin_loader_get_unvoted_reviews_async (GsPluginLoader     *plugin_loader,
+                                                        GsPluginRefineFlags flags,
+                                                        GCancellable   *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer        user_data);
+GList          *gs_plugin_loader_get_unvoted_reviews_finish (GsPluginLoader    *plugin_loader,
+                                                        GAsyncResult   *res,
+                                                        GError         **error);
 void            gs_plugin_loader_get_sources_async     (GsPluginLoader *plugin_loader,
                                                         GsPluginRefineFlags flags,
                                                         GCancellable   *cancellable,
diff --git a/src/gs-plugin.h b/src/gs-plugin.h
index 7b72782..878a8d3 100644
--- a/src/gs-plugin.h
+++ b/src/gs-plugin.h
@@ -251,6 +251,10 @@ gboolean    gs_plugin_add_featured                 (GsPlugin       *plugin,
                                                         GList          **list,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+gboolean        gs_plugin_add_unvoted_reviews          (GsPlugin       *plugin,
+                                                        GList          **list,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
 gboolean        gs_plugin_refine                       (GsPlugin       *plugin,
                                                         GList          **list,
                                                         GsPluginRefineFlags flags,
@@ -309,6 +313,11 @@ gboolean    gs_plugin_review_remove                (GsPlugin       *plugin,
                                                         GsReview       *review,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+gboolean        gs_plugin_review_dismiss               (GsPlugin       *plugin,
+                                                        GsApp          *app,
+                                                        GsReview       *review,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
 gboolean        gs_plugin_refresh                      (GsPlugin       *plugin,
                                                         guint           cache_age,
                                                         GsPluginRefreshFlags flags,
diff --git a/src/gs-review-row.c b/src/gs-review-row.c
index 0e32275..496ca4f 100644
--- a/src/gs-review-row.c
+++ b/src/gs-review-row.c
@@ -40,6 +40,7 @@ typedef struct
        GtkWidget       *text_label;
        GtkWidget       *button_yes;
        GtkWidget       *button_no;
+       GtkWidget       *button_dismiss;
        GtkWidget       *button_report;
        GtkWidget       *button_remove;
        GtkWidget       *box_vote_buttons;
@@ -83,7 +84,8 @@ gs_review_row_refresh (GsReviewRow *row)
 
        /* set actions up */
        if ((priv->actions & (1 << GS_REVIEW_ACTION_UPVOTE |
-                             1 << GS_REVIEW_ACTION_DOWNVOTE)) == 0) {
+                             1 << GS_REVIEW_ACTION_DOWNVOTE |
+                             1 << GS_REVIEW_ACTION_DISMISS)) == 0) {
                gtk_widget_set_visible (priv->box_vote_buttons, FALSE);
        } else {
                gtk_widget_set_visible (priv->box_vote_buttons, TRUE);
@@ -91,6 +93,8 @@ gs_review_row_refresh (GsReviewRow *row)
                                        priv->actions & 1 << GS_REVIEW_ACTION_UPVOTE);
                gtk_widget_set_visible (priv->button_no,
                                        priv->actions & 1 << GS_REVIEW_ACTION_DOWNVOTE);
+               gtk_widget_set_visible (priv->button_dismiss,
+                                       priv->actions & 1 << GS_REVIEW_ACTION_DISMISS);
        }
        gtk_widget_set_visible (priv->button_remove,
                                priv->actions & 1 << GS_REVIEW_ACTION_REMOVE);
@@ -159,6 +163,7 @@ gs_review_row_class_init (GsReviewRowClass *klass)
        gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, text_label);
        gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, button_yes);
        gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, button_no);
+       gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, button_dismiss);
        gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, button_report);
        gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, button_remove);
        gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, box_vote_buttons);
@@ -186,6 +191,13 @@ gs_review_row_button_clicked_report_cb (GtkButton *button, GsReviewRow *row)
 }
 
 static void
+gs_review_row_button_clicked_dismiss_cb (GtkButton *button, GsReviewRow *row)
+{
+       g_signal_emit (row, signals[SIGNAL_BUTTON_CLICKED], 0,
+                      GS_REVIEW_ACTION_DISMISS);
+}
+
+static void
 gs_review_row_button_clicked_remove_cb (GtkButton *button, GsReviewRow *row)
 {
        g_signal_emit (row, signals[SIGNAL_BUTTON_CLICKED], 0,
@@ -235,6 +247,9 @@ gs_review_row_new (GsReview *review)
        g_signal_connect_object (priv->button_no, "clicked",
                                 G_CALLBACK (gs_review_row_button_clicked_downvote_cb),
                                 row, 0);
+       g_signal_connect_object (priv->button_dismiss, "clicked",
+                                G_CALLBACK (gs_review_row_button_clicked_dismiss_cb),
+                                row, 0);
        g_signal_connect_object (priv->button_report, "clicked",
                                 G_CALLBACK (gs_review_row_button_clicked_report_cb),
                                 row, 0);
diff --git a/src/gs-review-row.ui b/src/gs-review-row.ui
index 95f5e67..bd3d993 100644
--- a/src/gs-review-row.ui
+++ b/src/gs-review-row.ui
@@ -139,6 +139,19 @@
                 <property name="position">2</property>
               </packing>
             </child>
+            <child>
+              <object class="GtkButton" id="button_dismiss">
+                <property name="label" translatable="yes">Meh</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
           </object>
           <packing>
             <property name="left_attach">0</property>
diff --git a/src/gs-review.h b/src/gs-review.h
index 2d2edd2..aadfd38 100644
--- a/src/gs-review.h
+++ b/src/gs-review.h
@@ -36,6 +36,7 @@ typedef enum {
        GS_REVIEW_ACTION_DOWNVOTE,
        GS_REVIEW_ACTION_REPORT,
        GS_REVIEW_ACTION_REMOVE,
+       GS_REVIEW_ACTION_DISMISS,
        GS_REVIEW_ACTION_LAST
 } GsReviewAction;
 
diff --git a/src/gs-shell-moderate.c b/src/gs-shell-moderate.c
new file mode 100644
index 0000000..11580e4
--- /dev/null
+++ b/src/gs-shell-moderate.c
@@ -0,0 +1,313 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013 Matthias Clasen <mclasen redhat com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include "gs-app.h"
+#include "gs-app-row.h"
+#include "gs-review-row.h"
+#include "gs-shell.h"
+#include "gs-shell-moderate.h"
+#include "gs-utils.h"
+
+struct _GsShellModerate
+{
+       GsPage                   parent_instance;
+
+       GsPluginLoader          *plugin_loader;
+       GtkBuilder              *builder;
+       GCancellable            *cancellable;
+       GtkSizeGroup            *sizegroup_image;
+       GtkSizeGroup            *sizegroup_name;
+       GsShell                 *shell;
+
+       GtkWidget               *list_box_install;
+       GtkWidget               *scrolledwindow_install;
+       GtkWidget               *spinner_install;
+       GtkWidget               *stack_install;
+};
+
+G_DEFINE_TYPE (GsShellModerate, gs_shell_moderate, GS_TYPE_PAGE)
+
+/**
+ * gs_shell_moderate_app_set_review_cb:
+ **/
+static void
+gs_shell_moderate_app_set_review_cb (GObject *source,
+                                    GAsyncResult *res,
+                                    gpointer user_data)
+{
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+       g_autoptr(GError) error = NULL;
+
+       if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
+               g_warning ("failed to set review: %s", error->message);
+               return;
+       }
+}
+
+static void
+gs_shell_moderate_review_clicked_cb (GsReviewRow *row,
+                                    GsReviewAction action,
+                                    GsShellModerate *self)
+{
+       GsApp *app = g_object_get_data (G_OBJECT (row), "GsApp");
+       gs_plugin_loader_review_action_async (self->plugin_loader,
+                                             app,
+                                             gs_review_row_get_review (row),
+                                             action,
+                                             self->cancellable,
+                                             gs_shell_moderate_app_set_review_cb,
+                                             self);
+       gtk_widget_set_visible (GTK_WIDGET (row), FALSE);
+}
+
+static void
+gs_shell_moderate_add_app (GsShellModerate *self, GsApp *app)
+{
+       GPtrArray *reviews;
+       GtkWidget *app_row;
+       guint i;
+
+       /* this hides the action button */
+       gs_app_set_kind (app, GS_APP_KIND_UNKNOWN);
+       gs_app_set_kind (app, GS_APP_KIND_SYSTEM);
+
+       /* add top level app */
+       app_row = gs_app_row_new (app);
+       gs_app_row_set_colorful (GS_APP_ROW (app_row), FALSE);
+       gtk_container_add (GTK_CONTAINER (self->list_box_install), app_row);
+       gs_app_row_set_size_groups (GS_APP_ROW (app_row),
+                                   self->sizegroup_image,
+                                   self->sizegroup_name);
+
+       /* add reviews */
+       reviews = gs_app_get_reviews (app);
+       for (i = 0; i < reviews->len; i++) {
+               GsReview *review = g_ptr_array_index (reviews, i);
+               GtkWidget *row = gs_review_row_new (review);
+               gtk_widget_set_margin_start (row, 250);
+               gtk_widget_set_margin_end (row, 250);
+               gs_review_row_set_actions (GS_REVIEW_ROW (row),
+                                          1 << GS_REVIEW_ACTION_UPVOTE |
+                                          1 << GS_REVIEW_ACTION_DOWNVOTE |
+                                          1 << GS_REVIEW_ACTION_DISMISS |
+                                          1 << GS_REVIEW_ACTION_REPORT);
+               g_signal_connect (row, "button-clicked",
+                                 G_CALLBACK (gs_shell_moderate_review_clicked_cb), self);
+               g_object_set_data_full (G_OBJECT (row), "GsApp",
+                                       g_object_ref (app),
+                                       (GDestroyNotify) g_object_unref);
+               gtk_container_add (GTK_CONTAINER (self->list_box_install), row);
+       }
+       gtk_widget_show (app_row);
+}
+
+/**
+ * gs_shell_moderate_get_unvoted_reviews_cb:
+ **/
+static void
+gs_shell_moderate_get_unvoted_reviews_cb (GObject *source_object,
+                                         GAsyncResult *res,
+                                         gpointer user_data)
+{
+       GList *l;
+       GsApp *app;
+       GsShellModerate *self = GS_SHELL_MODERATE (user_data);
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsAppList) list = NULL;
+
+       gs_stop_spinner (GTK_SPINNER (self->spinner_install));
+       gtk_stack_set_visible_child_name (GTK_STACK (self->stack_install), "view");
+
+       list = gs_plugin_loader_get_unvoted_reviews_finish (plugin_loader,
+                                                           res,
+                                                           &error);
+       if (list == NULL) {
+               if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                       g_warning ("failed to get moderate apps: %s", error->message);
+               return;
+       }
+       for (l = list; l != NULL; l = l->next) {
+               app = GS_APP (l->data);
+               gs_shell_moderate_add_app (self, app);
+       }
+}
+
+/**
+ * gs_shell_moderate_load:
+ */
+static void
+gs_shell_moderate_load (GsShellModerate *self)
+{
+       /* remove old entries */
+       gs_container_remove_all (GTK_CONTAINER (self->list_box_install));
+
+       /* get unvoted reviews as apps */
+       gs_plugin_loader_get_unvoted_reviews_async (self->plugin_loader,
+                                                   GS_PLUGIN_REFINE_FLAGS_DEFAULT |
+                                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
+                                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
+                                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
+                                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION |
+                                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENCE |
+                                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS,
+                                                   self->cancellable,
+                                                   gs_shell_moderate_get_unvoted_reviews_cb,
+                                                   self);
+       gs_start_spinner (GTK_SPINNER (self->spinner_install));
+       gtk_stack_set_visible_child_name (GTK_STACK (self->stack_install), "spinner");
+}
+
+/**
+ * gs_shell_moderate_reload:
+ */
+void
+gs_shell_moderate_reload (GsShellModerate *self)
+{
+       gs_shell_moderate_load (self);
+}
+
+/**
+ * gs_shell_moderate_switch_to:
+ **/
+void
+gs_shell_moderate_switch_to (GsShellModerate *self, gboolean scroll_up)
+{
+       if (gs_shell_get_mode (self->shell) != GS_SHELL_MODE_MODERATE) {
+               g_warning ("Called switch_to(moderate) when in mode %s",
+                          gs_shell_get_mode_string (self->shell));
+               return;
+       }
+       if (gs_shell_get_mode (self->shell) == GS_SHELL_MODE_MODERATE)
+               gs_grab_focus_when_mapped (self->scrolledwindow_install);
+       gs_shell_moderate_load (self);
+}
+
+/**
+ * gs_shell_moderate_list_header_func
+ **/
+static void
+gs_shell_moderate_list_header_func (GtkListBoxRow *row,
+                                    GtkListBoxRow *before,
+                                    gpointer user_data)
+{
+       GtkWidget *header;
+       gtk_list_box_row_set_header (row, NULL);
+       if (before == NULL)
+               return;
+       if (GS_IS_REVIEW_ROW (before) && GS_IS_APP_ROW (row)) {
+               header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+               gtk_list_box_row_set_header (row, header);
+       }
+}
+
+/**
+ * gs_shell_moderate_setup:
+ */
+void
+gs_shell_moderate_setup (GsShellModerate *self,
+                         GsShell *shell,
+                         GsPluginLoader *plugin_loader,
+                         GtkBuilder *builder,
+                         GCancellable *cancellable)
+{
+       g_return_if_fail (GS_IS_SHELL_MODERATE (self));
+
+       self->shell = shell;
+       self->plugin_loader = g_object_ref (plugin_loader);
+       self->builder = g_object_ref (builder);
+       self->cancellable = g_object_ref (cancellable);
+
+       gtk_list_box_set_header_func (GTK_LIST_BOX (self->list_box_install),
+                                     gs_shell_moderate_list_header_func,
+                                     self, NULL);
+
+       /* chain up */
+       gs_page_setup (GS_PAGE (self), shell, plugin_loader, cancellable);
+}
+
+/**
+ * gs_shell_moderate_dispose:
+ **/
+static void
+gs_shell_moderate_dispose (GObject *object)
+{
+       GsShellModerate *self = GS_SHELL_MODERATE (object);
+
+       g_clear_object (&self->sizegroup_image);
+       g_clear_object (&self->sizegroup_name);
+
+       g_clear_object (&self->builder);
+       g_clear_object (&self->plugin_loader);
+       g_clear_object (&self->cancellable);
+
+       G_OBJECT_CLASS (gs_shell_moderate_parent_class)->dispose (object);
+}
+
+/**
+ * gs_shell_moderate_class_init:
+ **/
+static void
+gs_shell_moderate_class_init (GsShellModerateClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+       object_class->dispose = gs_shell_moderate_dispose;
+
+       gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/Software/gs-shell-moderate.ui");
+
+       gtk_widget_class_bind_template_child (widget_class, GsShellModerate, list_box_install);
+       gtk_widget_class_bind_template_child (widget_class, GsShellModerate, scrolledwindow_install);
+       gtk_widget_class_bind_template_child (widget_class, GsShellModerate, spinner_install);
+       gtk_widget_class_bind_template_child (widget_class, GsShellModerate, stack_install);
+}
+
+/**
+ * gs_shell_moderate_init:
+ **/
+static void
+gs_shell_moderate_init (GsShellModerate *self)
+{
+       gtk_widget_init_template (GTK_WIDGET (self));
+
+       self->sizegroup_image = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+       self->sizegroup_name = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+}
+
+/**
+ * gs_shell_moderate_new:
+ **/
+GsShellModerate *
+gs_shell_moderate_new (void)
+{
+       GsShellModerate *self;
+       self = g_object_new (GS_TYPE_SHELL_MODERATE, NULL);
+       return GS_SHELL_MODERATE (self);
+}
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-shell-moderate.h b/src/gs-shell-moderate.h
new file mode 100644
index 0000000..4a74492
--- /dev/null
+++ b/src/gs-shell-moderate.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GS_SHELL_MODERATE_H
+#define __GS_SHELL_MODERATE_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "gs-page.h"
+#include "gs-plugin-loader.h"
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_SHELL_MODERATE (gs_shell_moderate_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsShellModerate, gs_shell_moderate, GS, SHELL_MODERATE, GsPage)
+
+GsShellModerate        *gs_shell_moderate_new          (void);
+void            gs_shell_moderate_switch_to    (GsShellModerate        *self,
+                                                gboolean               scroll_up);
+void            gs_shell_moderate_reload       (GsShellModerate        *self);
+void            gs_shell_moderate_setup        (GsShellModerate        *self,
+                                                GsShell                *shell,
+                                                GsPluginLoader         *plugin_loader,
+                                                GtkBuilder             *builder,
+                                                GCancellable           *cancellable);
+
+G_END_DECLS
+
+#endif /* __GS_SHELL_MODERATE_H */
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-shell-moderate.ui b/src/gs-shell-moderate.ui
new file mode 100644
index 0000000..a781526
--- /dev/null
+++ b/src/gs-shell-moderate.ui
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.10"/>
+  <template class="GsShellModerate" parent="GsPage">
+    <child internal-child="accessible">
+      <object class="AtkObject" id="moderate-accessible">
+        <property name="accessible-name" translatable="yes">Moderate page</property>
+      </object>
+    </child>
+    <child>
+      <object class="GtkStack" id="stack_install">
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkSpinner" id="spinner_install">
+            <property name="visible">True</property>
+            <property name="width_request">32</property>
+            <property name="height_request">32</property>
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+          </object>
+          <packing>
+            <property name="name">spinner</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box_install">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkScrolledWindow" id="scrolledwindow_install">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="hscrollbar_policy">never</property>
+                <property name="vscrollbar_policy">automatic</property>
+                <property name="vexpand">True</property>
+                <property name="shadow_type">none</property>
+                <child>
+                  <object class="GsFixedSizeBin" id="gs_fixed_bin">
+                    <property name="visible">True</property>
+                    <property name="preferred-width">860</property>
+                    <child>
+                      <object class="GtkListBox" id="list_box_install">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="selection_mode">none</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">view</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/gs-shell.c b/src/gs-shell.c
index 62aff1b..37bcb14 100644
--- a/src/gs-shell.c
+++ b/src/gs-shell.c
@@ -29,6 +29,7 @@
 #include "gs-shell.h"
 #include "gs-shell-details.h"
 #include "gs-shell-installed.h"
+#include "gs-shell-moderate.h"
 #include "gs-shell-search.h"
 #include "gs-shell-overview.h"
 #include "gs-shell-updates.h"
@@ -46,6 +47,7 @@ static const gchar *page_name[] = {
        "details",
        "category",
        "extras",
+       "moderate",
 };
 
 typedef struct {
@@ -63,6 +65,7 @@ typedef struct
        GsShellMode              mode;
        GsShellOverview         *shell_overview;
        GsShellInstalled        *shell_installed;
+       GsShellModerate         *shell_moderate;
        GsShellSearch           *shell_search;
        GsShellUpdates          *shell_updates;
        GsShellDetails          *shell_details;
@@ -189,6 +192,9 @@ gs_shell_change_mode (GsShell *shell,
        case GS_SHELL_MODE_INSTALLED:
                gs_shell_installed_switch_to (priv->shell_installed, scroll_up);
                break;
+       case GS_SHELL_MODE_MODERATE:
+               gs_shell_moderate_switch_to (priv->shell_moderate, scroll_up);
+               break;
        case GS_SHELL_MODE_SEARCH:
                widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "entry_search"));
                text = gtk_entry_get_text (GTK_ENTRY (widget));
@@ -647,6 +653,12 @@ gs_shell_setup (GsShell *shell, GsPluginLoader *plugin_loader, GCancellable *can
                                  priv->plugin_loader,
                                  priv->builder,
                                  priv->cancellable);
+       priv->shell_moderate = GS_SHELL_MODERATE (gtk_builder_get_object (priv->builder, "shell_moderate"));
+       gs_shell_moderate_setup (priv->shell_moderate,
+                                shell,
+                                priv->plugin_loader,
+                                priv->builder,
+                                priv->cancellable);
        priv->shell_search = GS_SHELL_SEARCH (gtk_builder_get_object (priv->builder, "shell_search"));
        gs_shell_search_setup (priv->shell_search,
                               shell,
diff --git a/src/gs-shell.h b/src/gs-shell.h
index 7f64277..ab41856 100644
--- a/src/gs-shell.h
+++ b/src/gs-shell.h
@@ -50,6 +50,7 @@ typedef enum {
        GS_SHELL_MODE_DETAILS,
        GS_SHELL_MODE_CATEGORY,
        GS_SHELL_MODE_EXTRAS,
+       GS_SHELL_MODE_MODERATE,
        GS_SHELL_MODE_LAST
 } GsShellMode;
 
diff --git a/src/plugins/gs-plugin-xdg-app-reviews.c b/src/plugins/gs-plugin-xdg-app-reviews.c
index b86a157..e3a1f02 100644
--- a/src/plugins/gs-plugin-xdg-app-reviews.c
+++ b/src/plugins/gs-plugin-xdg-app-reviews.c
@@ -319,47 +319,6 @@ xdg_app_review_parse_success (const gchar *data,
        return TRUE;
 }
 
-#if 0
-/**
- * xdg_app_review_get_moderate:
- */
-static GPtrArray *
-xdg_app_review_get_moderate (SoupSession *session,
-                            const gchar *user_hash,
-                            GError **error)
-{
-       guint status_code;
-       g_autofree gchar *uri = NULL;
-       g_autoptr(SoupMessage) msg = NULL;
-       g_autoptr(GFile) cachefn_file = NULL;
-       g_autoptr(GPtrArray) reviews = NULL;
-
-       /* create the GET data *with* the machine hash so we can later
-        * review the application ourselves */
-       uri = g_strdup_printf ("%s/moderate/%s",
-                              plugin->priv->review_server,
-                              user_hash);
-       msg = soup_message_new (SOUP_METHOD_GET, uri);
-       status_code = soup_session_send_message (session, msg);
-       if (status_code != SOUP_STATUS_OK) {
-               if (!xdg_app_review_parse_success (msg->response_body->data,
-                                                  msg->response_body->length,
-                                                  error))
-                       return NULL;
-               /* not sure what to do here */
-               g_set_error_literal (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_FAILED,
-                                    "status code invalid");
-               return NULL;
-       }
-       g_debug ("xdg-app-review returned: %s", msg->response_body->data);
-       return xdg_app_review_parse_reviews (msg->response_body->data,
-                                            msg->response_body->length,
-                                            error);
-}
-#endif
-
 /**
  * gs_plugin_xdg_app_reviews_json_post:
  */
@@ -970,6 +929,21 @@ gs_plugin_review_downvote (GsPlugin *plugin,
 }
 
 /**
+ * gs_plugin_review_dismiss:
+ */
+gboolean
+gs_plugin_review_dismiss (GsPlugin *plugin,
+                         GsApp *app,
+                         GsReview *review,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+       g_autofree gchar *uri = NULL;
+       uri = g_strdup_printf ("%s/dismiss", plugin->priv->review_server);
+       return gs_plugin_xdg_app_reviews_vote (plugin, review, uri, error);
+}
+
+/**
  * gs_plugin_review_remove:
  */
 gboolean
@@ -983,3 +957,67 @@ gs_plugin_review_remove (GsPlugin *plugin,
        uri = g_strdup_printf ("%s/remove", plugin->priv->review_server);
        return gs_plugin_xdg_app_reviews_vote (plugin, review, uri, error);
 }
+
+/**
+ * gs_plugin_add_unvoted_reviews:
+ */
+gboolean
+gs_plugin_add_unvoted_reviews (GsPlugin *plugin,
+                              GList **list,
+                              GCancellable *cancellable,
+                              GError **error)
+{
+       const gchar *app_id_last = NULL;
+       guint status_code;
+       guint i;
+       g_autofree gchar *uri = NULL;
+       g_autoptr(GFile) cachefn_file = NULL;
+       g_autoptr(GPtrArray) reviews = NULL;
+       g_autoptr(GsApp) app_current = NULL;
+       g_autoptr(SoupMessage) msg = NULL;
+
+       /* create the GET data *with* the machine hash so we can later
+        * review the application ourselves */
+       uri = g_strdup_printf ("%s/moderate/%s",
+                              plugin->priv->review_server,
+                              plugin->priv->user_hash);
+       msg = soup_message_new (SOUP_METHOD_GET, uri);
+       status_code = soup_session_send_message (plugin->soup_session, msg);
+       if (status_code != SOUP_STATUS_OK) {
+               if (!xdg_app_review_parse_success (msg->response_body->data,
+                                                  msg->response_body->length,
+                                                  error))
+                       return FALSE;
+               /* not sure what to do here */
+               g_set_error_literal (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_FAILED,
+                                    "status code invalid");
+               return FALSE;
+       }
+       g_debug ("xdg-app-review returned: %s", msg->response_body->data);
+       reviews = xdg_app_review_parse_reviews (msg->response_body->data,
+                                               msg->response_body->length,
+                                               error);
+       if (reviews == NULL)
+               return FALSE;
+
+       /* look at all the reviews; faking application objects */
+       for (i = 0; i < reviews->len; i++) {
+               GsReview *review;
+               const gchar *app_id;
+
+               /* same app? */
+               review = g_ptr_array_index (reviews, i);
+               app_id = gs_review_get_metadata_item (review, "app_id");
+               if (g_strcmp0 (app_id, app_id_last) != 0) {
+                       g_clear_object (&app_current);
+                       app_current = gs_app_new (app_id);
+                       gs_plugin_add_app (list, app_current);
+                       app_id_last = app_id;
+               }
+               gs_app_add_review (app_current, review);
+       }
+
+       return TRUE;
+}


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